مطالب
ایجاد ویژگی‌های اعتبارسنجی سفارشی در ASP.NET Core 3.1 به همراه اعتبارسنجی سمت کلاینت آن‌ها
اگر بخواهیم یک Attribute سفارشی را برای اعتبارسنجی ایجاد کنیم، معمولا یک کلاس را ایجاد کرده و از ValidationAttribute ارث بری می‌کنیم و سپس متد IsValid آن‌را override میکنیم؛ با توجه به نیازی که به آن Attribute داریم. به عنوان مثال در ادامه یک Attribute را ایجاد کرده‌ایم که عمل مقایسه‌ی دو خاصیت را انجام میدهد و اگر مقدار خاصیتی که ویژگی LowerThan بر روی آن قرار دارد، از مقدار خاصیت دیگری که باید با آن مقایسه شود، کمتر نباشد، یک خطا را به ModelState اضافه میکنیم:
public class LowerThanAttribute : ValidationAttribute
{
    public LowerThanAttribute(string dependentPropertyName)
    {
        DependentPropertyName = dependentPropertyName;
    }

    public string DependentPropertyName { get; set; }
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        int? currentPropertyValue = value as int?;
        currentPropertyValue ??= 0;
        var typeInfo = validationContext.ObjectInstance.GetType();
        var dependentPropertyValue = Convert.ToInt32(typeInfo.GetProperty(DependentPropertyName)
                                        .GetValue(validationContext.ObjectInstance, null));

        var displayDependentProperyName = typeInfo.GetProperty(DependentPropertyName)
                                        .GetCustomAttributes(typeof(DisplayAttribute), false)
                                        .Cast<DisplayAttribute>()
                                        .FirstOrDefault()?.Name;

        if (!(currentPropertyValue.Value < dependentPropertyValue))
        {
            return new ValidationResult("مقدار {0} باید کمتر باشد از " + displayDependentProperyName);
        }
        return ValidationResult.Success;
    }
}
ابتدا مقدار خاصیت مورد نظر را که میخواهیم با آن مقایسه شود، با استفاده از رفلکشن گرفته‌ایم و آن را در متغییر dependentPropertyValue ذخیره میکنیم. در ادامه مقدار Name را با استفاده از رفلکشن از DisplayAttribute میخوانیم و سپس عمل مقایسه را انجام میدهیم که اگر مقدار خاصیتی که ویژگی LowerThan بر روی آن قرار دارد، از مقدار خاصیت مورد نظر که مقدار آن را با استفاده از رفلکشن خوانده‌ایم، کمتر نباشد، یک خطا را به ModelState اضافه میکنیم.

اما یک مشکل! این عمل فقط در سمت سرور بررسی میشود و هنگامیکه ModelState.IsValid را در اکشن متد فراخوانی میکنیم، عمل اعتبارسنجی انجام میشود. یعنی همه‌ی داده‌ها به سمت سرور ارسال میشوند و اگر خطایی در ModelState وجود داشته باشد، کاربر مجددا باید داده‌ها را ارسال کند.

اما میتوان با استفاده از اینترفیس IClientModelValidator، عمل اعتبارسنجی را برای این ویژگی در سمت کلاینت انجام داد. برای انجام این کار ابتدا باید از اینترفیس IClientModelValidator ارث بری کنیم و متد AddValidation آن را پیاده سازی کنیم.
public class LowerThanAttribute : ValidationAttribute, IClientModelValidator
{
    public LowerThanAttribute(string dependentPropertyName)
    {
        DependentPropertyName = dependentPropertyName;
    }

    public string DependentPropertyName { get; set; }

    public void AddValidation(ClientModelValidationContext context)
    {
        var displayCurrentProperyName = context.ModelMetadata.ContainerMetadata
                                            .ModelType.GetProperty(context.ModelMetadata.PropertyName)
                                            .GetCustomAttributes(typeof(DisplayAttribute), false)
                                            .Cast<DisplayAttribute>()
                                            .FirstOrDefault()?.Name;

        var displayDependentProperyName = context.ModelMetadata.ContainerMetadata
                                            .ModelType.GetProperty(DependentPropertyName)
                                            .GetCustomAttributes(typeof(DisplayAttribute), false)
                                            .Cast<DisplayAttribute>()
                                            .FirstOrDefault()?.Name;


        MergeAttribute(context.Attributes, "data-val", "true");
        MergeAttribute(context.Attributes, "data-val-lowerthan", $"{displayCurrentProperyName} باید کمتر باشد از {displayDependentProperyName}");
        MergeAttribute(context.Attributes, "data-val-dependentpropertyname", "#" + DependentPropertyName);
    }
    private  bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
    {
        if (attributes.ContainsKey(key))
        {
            return false;
        }
        attributes.Add(key, value);
        return true;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        int? currentPropertyValue = value as int?;
        currentPropertyValue ??= 0;
        var typeInfo = validationContext.ObjectInstance.GetType();
        var dependentPropertyValue = Convert.ToInt32(typeInfo.GetProperty(DependentPropertyName)
                                        .GetValue(validationContext.ObjectInstance, null));

        var displayCurrentProperyName = typeInfo.GetProperty(DependentPropertyName)
                                        .GetCustomAttributes(typeof(DisplayAttribute), false)
                                        .Cast<DisplayAttribute>()
                                        .FirstOrDefault()?.Name;

        if (!(currentPropertyValue.Value < dependentPropertyValue))
        {
            return new ValidationResult("مقدار {0} باید کمتر باشد از " + displayCurrentProperyName);
        }
        return ValidationResult.Success;
    }
}
اینترفیس IClientModelValidator، یک متد به نام AddValidation دارد که این امکان را فراهم میکند تا بتوانیم اعتبارسنجی را در سمت کلاینت انجام دهیم. در ادامه باید با استفاده از JQuery اعتبارسنجی مخصوص این ویژگی را در سمت کلاینت پیاده سازی کنیم. در متد AddValidation فقط اسم تابع و پارامتر‌های مورد نیاز در سمت کلاینت را مشخص میکنیم. به عنوان مثال در مثال بالا یک تابع را معرفی کرده‌ایم به نام lowerthan که بعدا باید آنرا در سمت کلاینت پیاده سازی کنیم و نام خاصیتی را که باید با آن مقایسه شود، با نام data-val-dependentpropertyname معرفی کرده‌ایم. در کد زیر، این اعتبار سنجی سمت کلاینت را پیاده سازی کرده ایم. lowerthan نام متدی است که آنرا در متد AddValidation اضافه کردیم. مقدار value همان مقدار خاصیتی است که ویژگی LowerThan بر روی آن قرار دارد و otherPropId نام خاصیتی است که باید با آن مقایسه شود که آنرا از element خوانده‌ایم:
jQuery.validator.addMethod("lowerthan", function (value, element, param) {
    var otherPropId = $(element).data('val-dependentpropertyname');
    if (otherPropId) {
        var otherProp = $(otherPropId);
        if (otherProp) {
            var otherVal = otherProp.val();
            if (parseInt(otherVal) > parseInt(value)) {
                return true;
            }
            return false;
        }
    }
    return true;
});
jQuery.validator.unobtrusive.adapters.addBool("lowerthan");
کدهای جاواسکریپتی بالا را در یک فایل جدید به نام LowerThan.js ذخیره کرده‌ایم که باید آن را به صفحه خود اضافه کنیم:
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/LowerThan.js"></script>
سپس برای استفاده، باید ویژگی LowerThan را بر روی خاصیت مورد نظر قرار دهیم؛ مانند زیر:
public class User
{
    [Required]
    [Display(Name ="نام کاربری")]
    public string Username { get; set; }
    [Required]
    [Display(Name = "سن")]
    public int Age { get; set; }
    [LowerThan(nameof(Age))]
    [Required]
    [Display(Name = "سابقه کار")]
    public int Experience { get; set; }
}
و در نهایت اگر مقدار خاصیت Experience که ویژگی LowerThan بر روی آن قرار دارد، از مقدار خاصیت Age که باید با آن مقایسه شود، کمتر باشد، true برگردانده میشود؛ اما اگر بزرگتر یا مساوی باشد، متن خطایی را که در متد AddValidation اضافه کردیم، نشان داده خواهد شد.
 

مطالب
C# 7 - Ref Returns and Ref Locals
C# 7 به همراه تغییرات قابل توجهی در مورد نحوه‌ی دریافت خروجی از متدها است که نمونه‌هایی از آن‌ها را مانند tuples و out variable، پیشتر بررسی کردیم. در ادامه تغییرات جدید دیگری را به نام ref locals و ref returns نیز بررسی خواهیم کرد و هدف از آن، کاهش تعداد بار کپی کردن مقادیر و یا اعمال dereferencing جهت بالا بردن کارآیی برنامه هستند.


انتقال توسط مقدار

اگر پارامتری به صورت value type تعریف شود، این مقدار درون متد، ایجاد شده و حافظه‌ای به آن اختصاص داده می‌شود و سپس در انتهای متد تخریب خواهد شد. بنابراین تغییری در مقدار آن، سبب انعکاس آن به فراخوان متد، نخواهد شد.
static void PassByValueSample()
{
    int a = 1;
    PassByValue(a);
    Console.WriteLine($"after the invocation of {nameof(PassByValue)}, {nameof(a)} = {a}");
}
 
static void PassByValue(int x)
{
    x = 2;
}
در این مثال متد PassByValue تنها یک کپی از مقدار متغیر a را دریافت می‌کند. بنابراین هر تغییری که درون متد PassByValue بر روی این مقدار دریافتی رخ دهد، به فراخوان آن منتقل نخواهد شد.


انتقال توسط ارجاع

برای بازگشت مقدار تغییر داده شده‌ی توسط یک متد، می‌توان یک نوع خروجی را برای آن تعریف کرد. راه دیگر آن تعریف یک پارامتر توسط واژه‌ی کلیدی ref است. پارامتری که به این روش تعریف شود، هم ارسال کننده‌ی مقدار و هم دریافت کننده‌ی تغییرات خواهد بود.
static void PassByReferenceSample()
{
    int a = 1;
    PassByReference(ref a);
    Console.WriteLine($"after the invocation of {nameof(PassByReference)}, {nameof(a)} = {a}");
}
 
static void PassByReference(ref int x)
{
    x = 2;
}
در این مثال متغیر x به مقدار متغیر a اشاره می‌کند. بنابراین هر تغییری که بر روی آن صورت گیرد، به متغیر a هم اعمال و منعکس خواهد شد.


متغیرهای out

با استفاده از واژه‌ی کلیدی ref، می‌توان یک مقدار را هم به تابع ارسال کرد و هم از آن دریافت نمود. اما اگر تنها قرار است مقداری از تابع بازگشت داده شود، می‌توان از متغیرهای out استفاده کرد.
static void OutSample()
{
    Out(out int a);
    Console.WriteLine($"after the invocation of {nameof(Out)}, {nameof(a)} = {a}");
}
 
static void Out(out int x)
{
    x = 2;
}
در حالت استفاده‌ی از واژه‌ی کلیدی out، نیازی به مقدار دهی اولیه‌ی متغیر ارسالی به متد نیست و در اینجا روش جدید تعریف متغیرهای out را در C# 7 نیز مشاهده می‌کنید که نیازی نیست تا ابتدا int a را تعریف کرد و سپس در متد Out آن‌را فراخوانی نمود. می‌توان کل عملیات را به صورت خلاصه در یک سطر ذکر کرد.
البته اگر تنها قرار است یک مقدار را از یک متد دریافت کنیم، بهتر است نوع خروجی متد را مشخص کرد و از آن استفاده نمود؛ بجای استفاده‌ی از متغیرهای out. یک نمونه کاربرد مفید متغیرهای out در خود فریم ورک و توسط متد TryParse وجود دارد:
if (int.TryParse("42", out var result))
{
   Console.WriteLine($"the result is {result}");
}


Ref Locals در C# 7

در C# 7، امکان تعریف متغیرهای محلی از نوع ref نیز وجود دارد:
 int x = 3;
ref int x1 = ref x;
x1 = 2;
Console.WriteLine($"local variable {nameof(x)} after the change: {x}");
در اینجا متغیر x1 دارای ارجاعی است به متغیر x. بنابراین تغییر مقدار x1، به متغیر x نیز منعکس خواهد شد.

باید دقت داشت که نمی‌توان یک مقدار را به یک ref variable نسبت داد:
 ref int i = sequence.Count();
به این ترتیب امکان اشتباه گرفتن بین value variables و reference variables توسط کامپایلر گوشزد خواهد شد:
 ref int number1 = null; // ERROR
ref int number2 = 42; // ERROR


Ref Returns در C# 7

در C# 7، واژه‌ی کلیدی ref را به همراه نوع خروجی نیز می‌توان بکار برد:
 static ref int ReturnByReference()
{
    int[] arr = { 1 };
    ref int x = ref arr[0];
    return ref x;
}
در این مثال ارجاعی به اولین عضو آرایه‌ی تعریف شده، به عنوان خروجی متد بازگشت داده می‌شود. همچنین می‌توان این کد را به صورت ساده‌تر ذیل نیز نوشت:
static ref int ReturnByReference2()
{
   int[] arr = { 1 };
   return ref arr[0];
}
باید دقت داشت که یک متغیر int محلی را نمی‌توان به صورت ref بازگشت داد. علت اینجا است که متغیر int یک value type است و در انتهای متد تخریب خواهد شد. بنابراین امکان بازگشت آن به صورت ref میسر نیست. اما آرایه‌ها reference type بوده و بر روی heap تشکیل می‌شوند. در این حالت متغیر int داخل آرایه را می‌توان به صورت ref بازگشت داد.

هرچند امکان بازگشت یک متغیر محلی int به صورت ref وجود ندارد، اما اگر این متغیر به صورت ref به تابع ارسال شده باشد، این امکان میسر است:
static ref int ReturnByReference3(ref int x)
{
    x = 2;
    return ref x;
}

چند نکته: امکان تعریف فیلد ref و یا خاصیت get;set دار از نوع ref وجود ندارد. اما تعریف خواصی که یک ref را بازگشت می‌دهند، میسر هستند:
class Thing1
{
    ref string _Text1; /* Error */
 
    ref string Text2 { get; set; } /* Error */
 
 
    string _text = "Text";
    ref string Text3 { get { return ref _text; } } // Properties that return a reference are allowed
}


مزیت کار با ref returns

ref return‌ها شاید آنچنان در کدهای روزمره‌ی #C بکارگرفته نشوند، اما نیاز به کدهای unsafe و کار مستقیم با pointers را به حداقل می‌رسانند و به آن می‌توان لقب safe pointer را اطلاق کرد؛ از این جهت که این کد هنوز هم یک managed code است و نه یک unsafe code.
private MyBigStruct[] array = new MyBigStruct[10];
private int current;
 
public ref MyBigStruct GetCurrentItem()
{
   return ref array[current];
}
مهم‌ترین مزیت این قابلیت جدید را در قطعه کد فوق ملاحظه می‌کنید. در طراحی بازی‌ها عموما استفاده‌ی از آرایه‌های بزرگ از پیش تخصیص داده شده‌ی structها بسیار مرسوم است (چون میزان مصرف حافظه‌ی کمتری را نسبت به نوع‌های ارجاعی دارند و فشار کمتری را به GC وارد می‌کنند). اکنون با معرفی این قابلیت، دیگر نیازی نیست تا مدام آرایه‌های بزرگی از structها را از قسمتی به قسمت دیگر کپی کرد و سپس بر روی عناصری از آن‌ها عملیاتی را انجام داد و مجددا این حاصل را به مکان مدنظر کپی کرد. در اینجا بدون کپی کردن value types می‌توان با ایجاد ارجاعی به آن‌ها، تغییرات مدنظر را به آن‌ها اعمال کرد.
نظرات مطالب
استفاده از JSON.NET در ASP.NET MVC
نسخه‌ی بهبود یافته JsonNetValueProviderFactory را در اینجا می‌توانید مطالعه کنید. نسخه‌ی JsonNetResult آن جالب نیست چون از string استفاده کرده بجای stream.
JsonNetValueProviderFactory.cs
+ نحوه‌ی ثبت بهتر این کلاس دقیقا در همان ایندکس اصلی آن:
        public static void RegisterFactory()
        {
            var defaultJsonFactory = ValueProviderFactories.Factories
                .OfType<JsonValueProviderFactory>().FirstOrDefault();
            var index = ValueProviderFactories.Factories.IndexOf(defaultJsonFactory);
            ValueProviderFactories.Factories.Remove(defaultJsonFactory);
            ValueProviderFactories.Factories.Insert(index, new JsonNetValueProviderFactory());
        }
نظرات مطالب
پیاده سازی JSON Web Token با ASP.NET Web API 2.x
- در مورد حالت‌هایی مانند «با برنامه خودم» این مطلب را مطالعه کنید.
- لطفا تصویر خطا را ارسال نکنید. متن کامل آن‌را ارسال کنید.
- در این تصویر، به سختی می‌توان مشاهده کرد که can not read string undefined را ذکر کرده‌است. علت اینجا است که چون در فایل Index.html متغیر var jwtToken بدون مقدار دهی اولیه است، مقدار undefined را به سمت سرور ارسال می‌کند.
var bar;
typeof bar; // "undefined"
این بررسی را به ابتدای متدهای سمت کاربر اضافه کنید:
if(jwtToken == null) // will check if the value is undefined or null.
{
  alert('you need to login first');
  return;
}
مطالب
بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net

شاید بعضی از سایت‌ها را دیده باشید که در حین ثبت نام، پس از وارد کردن یک نام کاربری و سپس مشغول شدن به پر کردن فیلد کلمه‌ی عبور، در قسمت نام کاربری شروع به جستجو در مورد آزاد بودن نام کاربری درخواستی می‌کنند یا نمونه‌ای دیگر، فرم پرداخت الکترونیکی بانک سامان. پس از اینکه شماره قبض را وارد کردید، بلافاصله بدون ریفرش صفحه به شما پیغام می‌دهد که این شماره معتبر است یا خیر. امروز قصد داریم این قابلیت را با استفاده از کتابخانه‌ی Ajax مجموعه jQuery در ASP.Net پیاده سازی کنیم (بدون استفاده از ASP.Net Ajax مایکروسافت).
ابتدا سورس کامل را ملاحظه نمائید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AjaxTest.aspx.cs" Inherits="testWebForms87.AjaxTest" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>jQuery Ajax Text</title>

<script src="jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#<%= TextBox1.ClientID %>").blur(function(event) {
$.ajax({
type: "POST",
url: "AjaxTest.aspx/IsUserAvailable",
data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
$('#valid').html("<img src='ajaxImages/waiting.gif' alt='لطفا کمی تامل کنید'>");
var delay = function() {
AjaxSucceeded(msg);
};

setTimeout(delay, 2000); //remove this
},
error: AjaxFailed
});
});
});
function AjaxSucceeded(result) {
if (result.d == true)
$('#msg').html("<img src='ajaxImages/available.gif' alt='نام کاربری درخواستی موجود است'>");
else
$('#msg').html("<img src='ajaxImages/taken.gif' alt='متاسفانه نام کاربری مورد نظر پیشتر دریافت شده‌است'>");
}
function AjaxFailed(result) {
alert(result.status + ' ' + result.statusText);
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
user name:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<span id="msg"></span>
<br />
pass:
<asp:TextBox ID="TextBox2" TextMode="Password" runat="server"></asp:TextBox>
</div>
<!-- preload -->
<div style="display: none">
<img src="ajaxImages/available.gif" alt="available" />
<img src="ajaxImages/taken.gif" alt="taken" />
<img src="ajaxImages/waiting.gif" alt="waiting" />
</div>
</form>
</body>
</html>


using System;
using System.Web.Services;

namespace testWebForms87
{
public partial class AjaxTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

[WebMethod]
public static bool IsUserAvailable(string username)
{
// این مورد را با خواندن اطلاعات از دیتابیس می‌شود تعویض کرد
return username != "test";
}
}
}

توضیحات:
همانطور که ملاحظه می‌کنید صفحه‌ی ASP.Net ما بسیار ساده است و از دو تکست باکس استاندارد تشکیل می‌شود، به همراه تصاویر مربوط به Ajax که یک سری تصاویر ساده چرخان معروف منتظر بمانید ، یافت شد یا موجود نیست می‌باشند. این تصاویر در یک div مخفی (display: none) در صفحه قرار گرفته‌اند و در هنگام بارگذاری صفحه، این‌ها نیز بارگذاری شده و حاضر و آماده خواهند بود. بنابراین هنگام استفاده از آن‌ها، کاربر تاخیری را مشاهده نخواهد کرد. همچنین یک span با id مساوی msg‌ را هم پس از تکست باکس اضافه کرده‌ایم تا تصاویر مربوط به رخدادهای Ajax را با استفاده از توانایی‌های jQuery به آن اضافه کنیم.

اسکریپت Ajax ما با دراختیار گرفتن روال رخداد گردان blur شیء textBox1 شروع می‌شود. همانطور که در مقالات پیشین سایت نیز ذکر شد، روش صحیح دریافت ID یک کنترل ASP.Net در کدهای سمت کلاینت جاوا اسکریپتی، بر اساس خاصیت ClientID آن است که در اولین سطر کدهای ما مشخص است (زیرا در ASP.Net نام و ID یک کنترل در هنگام رندر شدن به همراه ID کنترل‌های دربرگیرنده آن نیز خواهد بود، بنابراین بهتر است این مورد را داینامیک کرد).

کار بررسی موجود بودن نام کاربری (یا مثلا یک شماره قبض و امثال آن) توسط WebMethod ایی به نام IsUserAvailable در code behind صفحه انجام می‌شود که پیاده سازی آن‌را ملاحظه می‌کنید. بدیهی است در این مثال ساده، تنها نام کاربری از پیش رزرو شده، کلمه‌ی test است و در یک کد واقعی این مورد با مقایسه‌ی نام کاربری با اطلاعات موجود در دیتابیس باید صورت گیرد (و حملات تزریق اس کیوال را هم فراموش نکنید. برای رهایی از آن‌ها "حتما" باید از پارامترهای ADO.Net استفاده کرد و گرنه کد شما مستعد به این نوع حملات خواهد بود).

سؤال: چرا از web method استفاده شد و همچنین چرا این متد static است؟
زمانیکه یک متد با کلمه کلیدی static‌ مشخص می‌شود حالت state less پیدا می‌کند یعنی مستقل از وهله‌ی کلاس عمل می‌کند. در این حالت نیازی به ارسال ViewState نبوده (بنابراین در کوئری مورد نظر ما بسیار بهینه و سبک عمل می‌کنند) و همچنین نیازی به ایجاد یک وهله‌ای از کلاس صفحه‌ی ما نیز نخواهد بود. برای توضیحات بیشتر به این مقاله مراجعه نمائید. (به صورت خلاصه، دلیل اصلی، کارآیی بالا و بهینه بودن این روش در این مساله ویژه است و در ASP.Net Ajax مایکروسافت به صورت گسترده‌ای در پشت صحنه مورد استفاده قرار می‌گیرد)
استفاده از ویژگی WebMethod عملکرد صفحه‌ی ما را شبیه به یک وب سرویس خواهد کرد و امکان دسترسی به آن در متدهای استاندارد POST به صورت ارسال دیتا به آدرس WebService.asmx/WebMethodName‌ خواهد بود. یک مثال ساده و عملی

بررسی تابع Ajax بکار رفته:
این تابع هنگام فراخوانی رخداد blur تکست‌باکس ما (مطابق کد فوق) فراخوانی می‌شود. ساختار ساده‌ای دارد که به شرح زیر است:
type: "POST"
نحوه‌ی ارسال داده را به متد وب سرویس ما مشخص می‌کند.

url: "AjaxTest.aspx/IsUserAvailable"
اطلاعات پست شده، به صفحه‌ی AjaxTest.aspx و وب متد IsUserAvailable ارسال خواهد شد

data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
داده‌ای که به آرگومان username ما ارسال می‌شود، همان مقدار تایپ شده در TextBox1 است (که باز هم دریافت ID آن به صورت داینامیک صورت گرفته تا مشکل زا نشود)

contentType: "application/json; charset=utf-8",
dataType: "json",
در این دو سطر از نوع داده‌ی json استفاده شده است که فرمت بسیار سبک و بهینه‌ای برای تبادل اطلاعات در وب به‌شمار می‌آید و توسط کتابخانه‌های جاوا اسکریپتی به سادگی پردازش شده و تبدیل به اشیاء مورد نظر خواهند شد. برای مثال اگر خروجی یک وب سرویس در حالت xml به صورت زیر باشد:

<xx yy="nn"></xx>
معادل json آن به شرح زیر است:
{ "xx": {"yy":"nn"} }
success: function(msg)
Success ، پس از موفقیت آمیز بودن عملیات ajax در jQuery فراخوانی می‌شود
error: AjaxFailed
و اگر در این بین خطایی رخ داده باشد، قسمت error فراخوانی می‌شود.

در این مثال برای نمایش بهتر عملیات، یک وقفه‌ی 2 ثانیه‌ای توسط setTimeout ایجاد شده و بدیهی است در یک مثال واقعی باید آن‌را حذف نمود.

نکته: با استفاده از افزونه‌ی فایرباگ فایرفاکس، می‌توان جزئیات این عملیات را بهتر مشاهده نمود:





نظرات مطالب
باگ Directory Traversal در سایت
- این مورد چطور؟
var path = Server.MapPath("~/App_Data/../web.config");
حتما یکبار خروجی آن‌را دیباگ کنید؛ جالب است.
کاربر هم بجای مسیر یک تصویر یا فایل، مسیر زیر را وارد کرده:
../web.config

+ عرض کردم در راه‌حل‌های عنوان شده.
اولین بررسی دریافتی از کاربر باید این مورد باشد:
var fileName = Path.GetFileName("~/web.config");
و نه استفاده مستقیم از نام دریافتی از وب.
خروجی متد فوق (web.config خالی) دیگر به ریشه سایت و یا هیچ مسیری اشاره نخواهد کرد.
اشتراک‌ها
Charts.css فریم‌ورکی برای رسم نمودار با CSS

Charts.css is a really nice CSS data visualization framework. It comes with a 71,719 byte size CSS file and allows to dynamically convert tabular data to beautiful charts by just adding a few attributes (like class and scope)  is a really nice CSS data visualization framework. 

Charts.css فریم‌ورکی برای رسم نمودار با CSS
اشتراک‌ها
افزونه NET MAUI Community Toolkit C# Markup Extensions

اگر علاقه ای به توسعه برنامه هایی که با Net MAUI. نوشته خواهند شد با استفاده از XAML ندارید به کمک این افزونه میتوانید همان دستورات را به زبان #C بنویسید.

using System;
using CommunityToolkit.Maui.Markup;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Essentials;
using static CommunityToolkit.Maui.Markup.GridRowsColumns;

namespace HelloMauiMarkup;

class MainPage : ContentPge
{
    public MainPage()
    {
        BindingContext = new MainViewModel();

        Content = new Grid
        {
            RowSpacing = 25,
            ColumnSpacing = 0,

            Padding = Device.RuntimePlatform switch
            {
                Device.iOS => new Thickness(30, 60, 30, 30),
                _ => new Thickness(30)
            },

            RowDefinitions = Rows.Define(
                (Row.HelloWorld, 44),
                (Row.Welcome, Auto),
                (Row.Count, Auto),
                (Row.ClickMeButton, Auto),
                (Row.Image, Star)),

            ColumnDefinitions = Columns.Define(
            (Column.Text, Star),
                (Column.Number, Star)),

            Children =
            {
                new Label { Text = "Hello World" }
.Row(Row.HelloWorld).ColumnSpan(All<Column>())
.Font(size: 32)
.CenterHorizontal().TextCenter(),

                new Label { Text = "Welcome to .NET MAUI Markup Community Toolkit Sample" }
                .Row(Row.Welcome).ColumnSpan(All<Column>())
                    .Font(size: 18)
                .CenterHorizontal().TextCenter(),

new Label { Text = "Current Count: " }
                .Row(Row.Count).Column(Column.Text)
.Font(bold: true)
.End().TextEnd(),

new Label()
                .Row(Row.Count).Column(Column.Number)
                    .Font(bold: true)
                    .Start().TextStart()
                    .Bind<Label, int, string>(Label.TextProperty, nameof(MainViewModel.ClickCount), convert: count => count.ToString())

                new Button { Text = "Click Me" }
                .Row(Row.ClickMeButton)
                .Font(bold: true)
                .CenterHorizontal()
.BindCommand(nameof(ViewModel.ClickMeButtonCommand)),

                new Image { Source = "dotnet_bot.png", WidthRequest = 250, HeightRequest = 310 }
.Row(Row.Image).ColumnSpan(All<Column>())
.CenterHorizontal()
}
};
    }

    enum Row { HelloWorld, Welcome, Count, ClickMeButton, Image }
    enum Column { Text, Number }
}


افزونه NET MAUI Community Toolkit C# Markup Extensions
مطالب
رمزنگاری JWT و افزایش امنیت آن در ASP.NET Core
آموزش JSON Web Token (به اختصار JWT) و پیاده سازی آن در برنامه‌های ASP.NET Core درسایت موجود است.
توکن JWT در حالت عادی به صورت Base64 رمزنگاری می‌شود که این نوع رمزنگاری به راحتی قابل رمزگشایی و خواندن است. سایت‌های آنلاین زیادی برای رمزگشایی base64 موجود است؛ برای مثال کافی است توکن خود را در سایت jwt.io کپی کنید و به راحتی محتوای بدنه توکن (Payload) را مشاهده کنید.

پس توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد.
ساده‌ترین راه حل، رمزنگاری دستی بدنه توکن می‌باشد که مثلا بر اساس کلیدی (که فقط سمت سرور نگهداری و مراقبت می‌شود) توکن را رمزنگاری کرده و به هنگام خواندن، آن را با همان کلید رمزگشایی کنیم. ولی این روش ضمن استاندارد نبودن، مشکلات خاص خودش را دارد و نیاز به سفارشی سازی زیادی، هم به هنگام تولید توکن و هم به هنگام خواندن توکن دارد.
 اصولی‌ترین راه، استفاده از رمزنگاری توکن به روش JSON Web Encryption (یا به اختصار JWE) است که در آن مشابه روش بالا ولی به صورت استاندارد تعریف شده (و قابل فهم برای همه استفاده کنندگانی که با این استاندارد آشنایی دارند) است.
نکته :
  1. اگر از JWE استفاده نمی‌کنید، بهتر است اطلاعات حساسی مانند شماره تلفن کاربر (و شاید در مواردی حتی آیدی کاربر) را در بدنه توکن قرار ندهیم چرا که قابل خوانده شدن است (که در این صورت استفاده از Guid برای آیدی کاربر می تواند کمی مفید باشد چرا که حداقل آیدی بقیه کاربران قابل پیش بینی نمی‌باشد).
  2. توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد؛ ولی به لطف امضای (signature) آن، در برابر تغییر محتوا، ایمن است؛ چرا که در صورت تغییر محتوای آن، دیگر مقدار hash محتوا با امضای آن همخوانی نداشته و عملا از اعتبار ساقط می‌گردد.
برای رمزنگاری JWT باید در هر دو مرحله‌ی "تولید توکن" و "اعتبارسنجی توکن" کلید و الگوریتم لازم برای رمزنگاری را مشخص کنیم. بدین منظور در جایی که توکن را تولید می‌کنیم، خواهیم داشت :
var secretKey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey"); // must be 16 character or longer 
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature);

var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey"); //must be 16 character
var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encryptionkey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256);

var claims = new List<Claim>
{
   new Claim(ClaimTypes.Name, "UserName"), //user.UserName
   new Claim(ClaimTypes.NameIdentifier, "123"), //user.Id
};

var descriptor = new SecurityTokenDescriptor
{
   Issuer = _siteSetting.JwtSettings.Issuer,
   Audience = _siteSetting.JwtSettings.Audience,
   IssuedAt = DateTime.Now,
   NotBefore = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.NotBeforeMinutes),
   Expires = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.ExpirationMinutes),
   SigningCredentials = signingCredentials,
   EncryptingCredentials = encryptingCredentials,
   Subject = new ClaimsIdentity(claims)
};

var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(descriptor);
string encryptedJwt = tokenHandler.WriteToken(securityToken);
کد بالا، مانند کد تولید یک توکن jwt معمولی است؛ تنها تفاوت آن، ایجاد و معرفی شیء encryptingCredentials است.
در خط چهارم، آرایه بایتی کلید لازم برای رمزنگاری (encryptionkey) گرفته شده و از روی آن encryptingCredentials ایجاد شده‌است. این کلید باید 16 کاراکتر باشد؛ در غیر اینصورت به هنگام تولید توکن، خطا دریافت خواهید کرد. رمزنگاری توکن، توسط این کلید و الگوریتم مشخص شده انجام خواهد شد.
سپس شیء تولید شده، به خاصیت EncryptingCredentials کلاس SecurityTokenDescriptor معرفی شده‌است و  نهایتا متد tokenHandler.WriteToken توکن رمزنگاری شده‌ای را تولید می‌کند.
نتیجه کار این است که توکن تولید شده، بدون کلید مربوطه (که سمت سرور نگهداری می‌شود) قابل رمز گشایی نیست و اگر آن را در سایت jwt.io کپی کنید، جوابی دریافت نخواهید کرد.

در ادامه لازم است در مرحله اعتبار سنجی و رمزگشایی توکن در سمت سرور، کلید و الگوریتم لازم را به آن معرفی کنیم تا middleware مربوطه بتواند توکن دریافتی را رمزگشایی و سپس اعتبار سنجی کند. بدین منظور در متد ConfigureServices کلاس Startup.cs خواهیم داشت:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
   var secretkey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey");
   var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey");

   var validationParameters = new TokenValidationParameters
   {
      ClockSkew = TimeSpan.Zero, // default: 5 min
      RequireSignedTokens = true,

      ValidateIssuerSigningKey = true,
      IssuerSigningKey = new SymmetricSecurityKey(secretkey),

      RequireExpirationTime = true,
      ValidateLifetime = true,

      ValidateAudience = true, //default : false
      ValidAudience = "MyWebsite",

      ValidateIssuer = true, //default : false
      ValidIssuer = "MyWebsite",

      TokenDecryptionKey = new SymmetricSecurityKey(encryptionkey)
   };

   options.RequireHttpsMetadata = false;
   options.SaveToken = true;
   options.TokenValidationParameters = validationParameters;
});

کد بالا مانند کد فعال سازی احراز هویت توسط JWT معمولی در ASP.NET Core است؛ با این تفاوت که:

ابتدا آرایه بایتی همان کلید رمزنگاری (encryptionkey) که قبلا توکن را با آن رمزنگاری کرده بودیم، گرفته شده و سپس توسط مقداردهی خاصیت TokenDecryptionKey کلاس TokenValidationParameters، معرفی شده است. 

ولی شاید این سؤال برایتان پیش آید که چرا الگوریتم رمزنگاری مشخص نشده است؟ پس سرور از کجا می‌فهمد که این توکن بر اساس چه الگوریتمی رمزنگاری شده است؟ 

دلیل آن این است که به هنگام تولید توکن، اسم الگوریتم مربوطه، داخل بخش header توکن نوشته می‌شود. اگر تصویر قبل را مشاهده کنید مقدار header توکن به شرح زیر است.

{
  "alg": "A128KW",
  "enc": "A128CBC-HS256",
  "typ": "JWT"
}

پس سرور بر اساس این قسمت از توکن (header)، که هیچگاه رمزنگاری نمی‌شود، می‌فهمد که توسط چه الگوریتمی باید توکن را رمزگشایی کند که در اینجا A128CBC-HS256 (اختصار AES-128-CBC و HMAC-SHA256) است.

مثال کامل و قابل اجرای این مطلب را می‌توانید از این ریپازیتوری دریافت کنید.

مطالب
یافتن سرویس‌هایی که بیش از یکبار در برنامه‌های ASP.NET Core ثبت شد‌ه‌اند
ممکن است در حین توسعه‌ی یک برنامه، یکبار سرویس‌های مدنظر را توسط قابلیت اسکن کتابخانه‌هایی مانند Scrutor به برنامه اضافه کنید و یکبار هم به اشتباه تعدادی از آن‌ها را دستی ثبت کنید و یا ممکن است کتابخانه‌های ثالثی را که مورد استفاده قرار داده‌اید، دست آخر سبب ثبت بیش از اندازه‌ی سرویس‌های مشخصی شده‌اند. در ادامه روش گزارشگیری از این سرویس‌های تکراری ثبت شده را بررسی می‌کنیم.


یافتن سرویس‌هایی که به اشتباه بیش از یکبار ثبت شده‌اند

کلاس زیر متدهایی را برای جمع آوری و گزارش سرویس‌هایی که تکراری ثبت شده‌اند، ارائه می‌دهد:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace FindDuplicateServices.Utils
{
    public static class DuplicateServicesFinder
    {
        private static List<(Type ServiceType, int RegistrationTimes)> _duplicateServices;

        public static IHostBuilder CountDuplicateServices(this IHostBuilder hostBuilder)
        {
            hostBuilder.ConfigureServices(services =>
            {
                _duplicateServices = services.Where(
                        serviceDescriptor => !serviceDescriptor.ServiceType.Assembly.FullName.Contains("Microsoft"))
                                        .GroupBy(serviceDescriptor => serviceDescriptor.ServiceType)
                                        .Where(g => g.Count() > 1)
                                        .Select(g => (g.Key, g.Count()))
                                        .ToList();
            });
            return hostBuilder;
        }

        public static IHost ReportDuplicateServices(this IHost host)
        {
            var logger = host.Services.GetRequiredService<ILogger<Program>>();
            _duplicateServices.ForEach(item =>
                logger.LogWarning($"Service Type: `{item.ServiceType}` -> Registration times: {item.RegistrationTimes}"));
            return host;
        }

        public static void RemoveService(this IServiceCollection services, Type serviceType)
        {
            var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == serviceType);
            if (serviceDescriptor != null)
            {
                services.Remove(serviceDescriptor);
            }
        }
    }
}
متد الحاقی CountDuplicateServices که روش استفاده‌ی از آن را در فایل Program.cs نمونه‌ی زیر مشاهده می‌کنید، پس از ثبت تمام سرویس‌های برنامه فراخوانی می‌شود، لیست ServiceType‌های ثبت شده را استخراج کرده و تکراری‌ها را جمع آوری می‌کند.
سپس متد الحاقی ReportDuplicateServices که پس از متد Build در متد Main برنامه فراخوانی می‌شود، این سرویس‌ها را توسط لاگر جاری، نمایش می‌دهد:
using FindDuplicateServices.Utils;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace FindDuplicateServices
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().ReportDuplicateServices().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .CountDuplicateServices();
    }
}

برای مثال اگر سرویس فرضی IWeatherForecastService دوبار ثبت شده باشد:
namespace FindDuplicateServices
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddScoped<IWeatherForecastService, WeatherForecastService>();
            services.AddScoped<IWeatherForecastService, WeatherForecastService>();
        }
با اجرای برنامه این خروجی را مشاهده خواهیم کرد:



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: FindDuplicateServices.zip