مطالب
Content Negotiation در WCF
Content Negotiation ، مکانیزمی است که طی آن مصرف کننده یک سرویس http تعیین می‌کند که خروجی مورد نظر از سرویس به چه فرمتی در اختیار آن قرار گیرد. این قابلیت بسیار زیبا در Asp.Net Web Api فراهم می‌باشد. اما از آن جا که در  WCF به صورت توکار مکانیزمی جهت پیاده سازی این قابلیت در نظر گرفته نشده است می‌توان از طریق یک کتابخانه ثالث به نام WCFRestContrib به این مهم دست یافت.

به صورت معمول برای پیاده سازی Content Negotiation، مصرف کننده باید در Accept هدر درخواست، برای سرویس مورد نظر، نوع Content-Type را نیز تعیین نمایید. از طرفی سرویس دهنده نیز باید معادل Mime Type درخواست شده، یک Formatter جهت سریالایز داده‌ها در اختیار داشته باشد. در WCF از طریق کتابخانه WcfRestContrib می‌توانیم به صورت زیر Content Negotiation را پیاده سازی نماییم:

ابتدا از طریق Nuget کتابخانه زیر را نصب کنید:
install-package WcfRestContrib
حال فرض کنید سرویسی به صورت زیر داریم:
[ServiceContract]
public interface IBooksService
{    
    [OperationContract]
    void AddBook(string isbn, Book book);
}
کد‌های بالا روشی مرسوم برای تعریف Service Contract‌های WCF است. برای اینکه سرویس WCF بالا به صورت Rest طراحی شود و از طرفی قابلیت سریالاز داده‌ها به چندین فرمت را داشته باشد باید به صورت زیر عمل نماییم:
[ServiceContract]
public interface IBooksService
{
    [WebInvoke(UriTemplate = "/{isbn}", Method=Verbs.Put)]
    [WebDispatchFormatter]
    [OperationContract]
    void AddBook(string isbn, Book book);
    ....
}
وظیفه WebDispatchFormatterAttribute تعریف شده برای Operation بالا این است که نوع فرمت مورد نیاز را از Accept هدر درخواست واکشی کرده و با توجه به MimeType‌های تعریف شده در سرویس، داده‌ها را به آن فرمت سریالاز نماید. در صورتی که MimeType درخواست شده از سوی مصرف کننده، سمت سرور تعریف نشده بود، MimeType پیش فرض انتخاب می‌شود.
گام بعدی مشخص کردن انواع MimeType‌ها برای این سرویس است. در WcfRestContrib به صورت پیش فرض چهار Formatter تعبیه شده است:
»Xml : از DataContractSerializer موجود در WCF برای سریالاز و دی سریالایز داده‌ها استفاده می‌کند.
»Json : از طریق DataContactJsonSerializer برای سریالاز و دی سریالایز داده‌ها استفاده می‌کند.
POX : همانند مورد اول از DataContractSerializer استفاده می‌کند با این تفاوت که DataContract‌ها بدون Namesapce و Attribute و DataMember‌ها نیز بدون Order می‌باشند.
»Form Url Encoded

در صورتی که نیاز به formatter دیگری دارید می‌توانید با استفاده از CustomFormatter موجود در این کتابخانه، Formatter دلخواه خود را پیاده سازی نمایید.

همان طور که در بالا ذکر شد، در صورتی که MimeType درخواست شده از سوی مصرف کننده، سمت سرور تعریف نشده باشد، MimeType پیش فرض انتخاب می‌شود. برای تعریف MimeType پیش فرض می‌توان از خاصیت WebDispatchFormatterConfigurationAttribute که در فضای نام  WcfRestContrib.ServiceModel.Description  قرار دارد استفاده کرد. تعاریف سایر MimeType‌ها نیز با استفاده از WebDispatchFormatterMimeTypeAttribute انجام می‌شود. به صورت زیر:

[WebDispatchFormatterConfiguration("application/xml")]
[WebDispatchFormatterMimeType(typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.PoDataContract), "application/xml",  "text/xml")]
[WebDispatchFormatterMimeType( typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.DataContractJson),  "application/json")]
[WebDispatchFormatterMimeType( typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.FormUrlEncoded), "application/x-www-form-urlencoded")]
public class Books : IBooksService 
{    
   public void AddBook(string isbn, Book book)
   {
   }
}
همانند سایر تنظیمات WCF می‌توان تمامی این موارد را در فایل Config پروژه سرویس نیز تعریف کرد: برای مثال:
<system.serviceModel>
    <extensions>
        <behaviorExtensions>
            <add name="webFormatter" 
                 type="WcfRestContrib.ServiceModel.Configuration.WebDispatchFormatter.ConfigurationBehaviorElement, WcfRestContrib, 
                       Version=x.x.x.x, Culture=neutral, PublicKeyToken=89183999a8dc93b5"/>
        </behaviorExtensions>
    </extensions>
    <serviceBehaviors>
        <behavior name="Rest">
          <webFormatter>
            <formatters defaultMimeType="application/xml">
              <formatter mimeTypes="application/xml,text/xml" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.PoxDataContract, WcfRestContrib"/>
              <formatter mimeTypes="application/json" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.DataContractJson, WcfRestContrib"/>
              <formatter mimeTypes="application/x-www-form-urlencoded" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.FormUrlEncoded, WcfRestContrib"/>
            </formatters>
          </webFormatter>
        </behavior>
    </serviceBehaviors>
</system.serviceModel>
نکته:
در صورتی که قصد داشته باشیم که باتوجه به direction مورد نظر (نظیر Outgoing یا Incoming) داده‌ها سریالایز/ دی سریالایز شوند، می‌توان این مورد را در هنگام تعریف OperationContract تعیین کرد:
[WebDispatchFormatter(WebDispatchFormatter.FormatterDirection.Outgoing)]  

مطلب تکمیلی:

  مشاهده پیاده سازی Content Negotiation در Asp.Net MVC
نظرات مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
یک نکته‌ی تکمیلی: روش تهیه‌ی ویژگی‌های سفارشی اعتبارسنجی، در برنامه‌های Blazor

اگر ویژگی‌های پیش‌فرض مهیا، پاسخگوی اعتبارسنجی مدنظر نبودند، می‌توان یک attribute سفارشی را تهیه کرد:
using System.ComponentModel.DataAnnotations;

namespace CustomValidators
{
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]
    public class EmailDomainValidator : ValidationAttribute
    {
        public string AllowedDomain { get; set; }

        protected override ValidationResult IsValid(object value, 
            ValidationContext validationContext)
        {
            string[] strings = value.ToString().Split('@');
            if (strings[1].ToUpper() == AllowedDomain.ToUpper())
            {
                return null;
            }

            return new ValidationResult($"Domain must be {AllowedDomain}",
            new[] { validationContext.MemberName });
        }
    }
}
توضیحات:
- کار با ارث بری از کلاس پایه‌ی ValidationAttribute شروع می‌شود و باید متد IsValid آن‌را بازنویسی کرد.
- اگر متد IsValid، نال برگرداند، یعنی مشکلی نیست؛ در غیراینصورت خروجی آن باید از نوع ValidationResult باشد.
- پارامتر validationContext اطلاعاتی مانند نام خاصیت در حال بررسی را ارائه می‌دهد.
- در اینجا متد ()ValidationContext.GetService نال را بر می‌گرداند؛ یعنی فعلا از تزریق وابستگی‌ها در آن پشتیبانی نمی‌شود.

و در آخر روش استفاده‌ی از آن، همانند سایر ویژگی‌های اعتبارسنجی است:
public class Employee
{
    [EmailDomainValidator(AllowedDomain = "site.com")]
    public string Email { get; set; }
}
مطالب
آشنایی با جنریک‌ها #1
طبق این معرفی ، جنریک‌ها باعث می‌شوند که نوع داده‌ای (data type) المان‌های برنامه در زمان استفاده از آن‌ها در برنامه مشخص شوند. به عبارت دیگر، جنریک به ما اجازه می‌دهد کلاس‌ها یا متدهایی بنویسیم که می‌توانند با هر نوع داده‌ای کار کنند.

نکاتی از جنریک‌ها:
  • برای به حداکثر رسانی استفاده مجدد از کد، type safety و کارایی است.
  • بیشترین استفاده مشترک از جنریک‌ها جهت ساختن کالکشن کلاس‌ها (collection classes) است.
  • تا حد ممکن از جنریک کالکشن کلاسها (generic collection classes) جدید فضای نام System.Collections.Generic بجای کلاس‌هایی مانند ArrayList در فضای نام System.Collections استفاده شود.
  • شما می‌توانید اینترفیس جنریک ، کلاس جنریک ، متد جنریک و عامل جنریک سفارشی خودتان تهیه کنید.
  • جنریک کلاس‌ها، ممکن است در دسترسی به متدهایی با نوع داده‌ای خاص محدود شود.
  • بوسیله reflection، می‌توانید اطلاعاتی که در یک جنریک در زمان اجرا (run-time) قرار دارد بدست آورید.
انواع جنریک ها:
  1. کلاس‌های جنریک
  2. اینترفیس‌های جنریک
  3. متدهای جنریک
  4. عامل‌های جنریک
در قسمت اول به معرفی کلاس جنریک می‌پردازیم.

کلاس‌های جنریک
کلاس جنریک یعنی کلاسی که می‌تواند با چندین نوع داده کار کند برای آشنایی با این نوع کلاس به کد زیر دقت کنید:
using System;
using System.Collections.Generic;

namespace GenericApplication
{
    public class MyGenericArray<T>
    {
        // تعریف یک آرایه از نوع جنریک
        private T[] array;

        public MyGenericArray(int size)
        {
            array = new T[size + 1];
        }

        // بدست آوردن یک آیتم جنریک از آرایه جنریک
        public T getItem(int index)
        {
            return array[index];
        }

        // افزودن یک آیتم جنریک به آرایه جنریک
        public void setItem(int index, T value)
        {
            array[index] = value;
        }
    }
}
در کد بالا کلاسی تعریف شده است که می‌تواند بر روی آرایه‌هایی از نوع داده‌ای مختلف عملیات درج و حذف را انجام دهد. برای تعریف کلاس جنریک کافی است عبارت <T> بعد از نام کلاس خود اضافه کنید، سپس همانند سایر کلاس‌ها از این نوع داده ای در کلاس استفاده کنید. در مثال بالا یک آرایه از نوع T تعریف شده است که این نوع، در زمان استفاده مشخص خواهد شد. (یعنی در زمان استفاده از کلاس مشخص خواهد شد که چه نوع آرایه ای ایجاد می‌شود)
در کد زیر نحوه استفاده از کلاس جنریک نشان داده شده است، همانطور که مشاهده می‌کنید نوع کلاس int و char در نظر گرفته شده است (نوع کلاس، زمان استفاده از کلاس مشخص می‌شود) و سپس آرایه هایی از نوع int و char ایجاد شده است و 5 آیتم از نوع int و char به آرایه‌های هم نوع افزوده شده است. 
class Tester
{
        static void Main(string[] args)
        {
            // تعریف یک آرایه از نوع عدد صحیح
            MyGenericArray<int> intArray = new MyGenericArray<int>(5);

            // افزودن اعداد صحیح به آرایه ای از نوع عدد صحیح
            for (int c = 0; c < 5; c++)
            {
                intArray.setItem(c, c*5);
            }

            // بدست آوردن آیتم‌های آرایه ای از نوع عدد صحیح
            for (int c = 0; c < 5; c++)
            {
                Console.Write(intArray.getItem(c) + " ");
            }
            Console.WriteLine();

            // تعریف یک آرایه از نوع کاراکتر
            MyGenericArray<char> charArray = new MyGenericArray<char>(5);

            // افزودن کاراکترها به آرایه ای از نوع کاراکتر
            for (int c = 0; c < 5; c++)
            {
                charArray.setItem(c, (char)(c+97));
            }

            // بدست آوردن آیتم‌های آرایه ای از نوع کاراکتر
            for (int c = 0; c< 5; c++)
            {
                Console.Write(charArray.getItem(c) + " ");
            }
            Console.WriteLine();
            Console.ReadKey();
        }
}
زمانی که کد بالا اجرا می‌شود خروجی زیر بدست می‌آید:
0 5 10 15 20
a b c d e
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
اعتبارسنجی‌های اولیه‌، در فراخوانی base.ValidateAsync توسط خود Identity انجام می‌شود؛ این base به منطق پیش‌فرض Identity اشاره می‌کند که در اینجا لغو نشده‌است (همان قسمت «... بنابراین اگر قصد تهیه‌ی یک IUserValidator جدید را داشته باشیم، از تمام تنظیمات و بررسی‌های پیش فرض سرویس توکار UserValidator فوق محروم می‌شویم ... »  سفارشی سازی UserValidator مطلب جاری). اگر می‌خواهید این منطق پیش‌فرض را به طور کامل بازنویسی کنید، تمام ارجاعات به base را در CustomUserValidator که جایگزین کامل اعتبارسنج اصلی است، حذف کنید.
همچنین RequireUniqueEmail را هم مد نظر داشته باشید.
نظرات اشتراک‌ها
چند مطلب درباره ی SqlServer.ScriptDom
- تعدادی مطلب در همین مورد در سایت جاری
- پروژه‌ی DNT Profiler هم از این کتابخانه استفاده می‌کند. با این آنالیز کننده‌های سفارشی و این تست‌ها جهت آشنایی با نحوه‌ی عملکرد آنالیز کننده‌ها.
مطالب
پیاده سازی یک متد الحاقی برای تبدیل آدرس فیزیکی به آدرس مجازی (آدرس سرور)
سناریوی زیر را در نظر بگیرید:
در حال تهیه‌ی یک CMS هستید و طبق سفارش مشتری قسمتی را برای نمایشگاه محصولات در نظر گرفته‌اید. مشتری در نظر دارد در وب سایت خود، محصولات عرضه شده را به صورت یک گالری نمایش دهد و برای اینکار شما از یک فایل آپلودر مثل Kendo Uploader  استفاده کرده‌اید. در این حالت برای ذخیره‌ی فایلها بر روی دیسک، از متد MapPath  به صورت زیر استفاده می‌کنید:
var physicalPath = Path.Combine(Server.MapPath("~/Content/Images"), fileName);
خروجی متد بالا چیزی شبیه زیر است :
C:\\YourProject\\Content\\Images\\1.jpg 
اما برای نمایش عکسها باید بتوانیم مسیر عکس‌های ذخیره شده‌ی در فایل فیزیکی را به آدرس سرور، یا همان آدرس مجازی تبدیل کنیم. برای اینکار می‌توان از یک متد الحاقی به صورت زیر استفاده نمود :
public static class PathConverter
    {
        public static string PhysicalToVirtualPathConverter(this HttpServerUtilityBase utility, string path, HttpRequestBase context)
        {
            return path.Replace(context.ServerVariables["APPL_PHYSICAL_PATH"], "/").Replace(@"\", "/");
        }

    }
و برای استفاده از متد بالا به صورت زیر عمل می‌کنیم :
image.ImagePath = Server.PhysicalToVirtualPathConverter(PhysicalPath, Request);

مطالب
یکدست کردن «ی» و «ک» در ASP.NET Core با پیاده‌سازی یک Model Binder سفارشی
معادل مطلب جاری را برای ASP.NET MVC 5.x در مطلب «یکدست کردن "ی" و "ک" در ASP.NET MVC با پیاده‌سازی یک Model Binder» می‌توانید مطالعه کنید. در اینجا قصد داریم یک چنین قابلیتی را با توجه به تغییرات ASP.NET Core نیز تهیه کنیم.


تهیه یک binder provider پردازش رشته‌ها

کار model binding، تطابق اطلاعات رسیده‌ی از درخواست جاری، با پارامترهای اکشن متد یک کنترلر است. هر مقدار رسیده، به یک binder متناسب ارسال می‌شود تا پردازش آن مدیریت گردد. به صورت پیش فرض در ASP.NET Core، تعدد 14 عدد binder providers که اینترفیس IModelBinderProvider را پیاده سازی می‌کنند، در این بین جهت یافتن یک binder مناسب، بررسی خواهند شد. برای مثال کار یک binder، پردازش نوع‌های پیچیده‌است (complex types) و دیگری نوع‌های ساده (simple types) مانند int و string را پردازش می‌کند.


public class CustomStringModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
 
        if (context.Metadata.IsComplexType)
        {
            return null;
        }
 
        var fallbackBinder = new SimpleTypeModelBinder(context.Metadata.ModelType);
        if (context.Metadata.ModelType == typeof(string))
        {
            return new CustomStringModelBinder(fallbackBinder);
        }
        return fallbackBinder;
    }
}
بنابراین اولین قدم تهیه‌ی یک model binder سفارشی، تهیه‌ی یک تامین کننده‌ی سفارشی است که با پیاده سازی اینترفیس IModelBinderProvider ارائه می‌شود. در اینجا چون می‌خواهیم نوع‌های ساده‌ی رشته‌ای را پردازش کنیم، اگر نوع جاری رسیده، یک نوع پیچیده بود (context.Metadata.IsComplexType) نال را بازگشت می‌دهیم تا model binder بعدی ثبت شده‌ی در لیست تامین کننده‌های مرتبط، مورد آزمایش قرار گیرد.
سپس اگر نوع مدل جاری رشته‌ای بود، وهله‌ای از CustomStringModelBinder را بازگشت می‌دهیم (کلاسی است که آن‌را در ادامه تهیه خواهیم کرد). درغیراینصورت همان SimpleTypeModelBinder توکار این فریم‌ورک را بازگشت خواهیم داد.


تهیه‌ی یک model binder سفارشی پردازش رشته‌ها

تا اینجا تامین کننده‌ای را که مشخص می‌کند چه model binder ایی قرار است بازگشت داده شود، تهیه کردیم. مرحله‌ی بعد، پیاده سازی CustomStringModelBinder با پیاده سازی اینترفیس IModelBinder است:
public class CustomStringModelBinder : IModelBinder
{
    private readonly IModelBinder _fallbackBinder;
    public CustomStringModelBinder(IModelBinder fallbackBinder)
    {
        if (fallbackBinder == null)
        {
            throw new ArgumentNullException(nameof(fallbackBinder));
        }
        _fallbackBinder = fallbackBinder;
    }
 
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }
 
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (valueProviderResult != ValueProviderResult.None)
        {
            bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
 
            var valueAsString = valueProviderResult.FirstValue;
            if (string.IsNullOrWhiteSpace(valueAsString))
            {
                return _fallbackBinder.BindModelAsync(bindingContext);
            }
 
            var model = valueAsString.Replace((char)1610, (char)1740).Replace((char)1603, (char)1705);
            bindingContext.Result = ModelBindingResult.Success(model);
            return Task.CompletedTask;
        }
 
        return _fallbackBinder.BindModelAsync(bindingContext);
    }
}
عملیات اصلی پردازشی یک Model binder در متد BindModelAsync آن صورت می‌گیرد. ابتدا مقداری را که در حال پردازش است دریافت می‌کنیم (توسط ValueProvider.GetValue). سپس ی و ک آن‌را یکدست کرده و به عنوان نتیجه‌ی عملیات تنظیم خواهیم کرد. این کار سبب خواهد شد تا هر مقداری را که کاربر وارد و ارسال کند، پیش از رسیدن به اکشن متد و پارامترهای آن، مورد پردازش و یکدست سازی قرار گیرد.
در اینجا تمام مواردی را که نمی‌خواهیم پردازش کنیم، به همان SimpleTypeModelBinder که از طریق سازنده‌ی کلاس دریافت می‌کنیم، واگذار خواهیم کرد.


معرفی به binder provider سفارشی به سیستم

مرحله‌ی آخر این عملیات، معرفی binder تهیه شده به سیستم است که روش آن را در ذیل مشاهده می‌کنید:
public static class CustomStringModelBinderExtensions
{
    public static MvcOptions UseCustomStringModelBinder(this MvcOptions options)
    {
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }
 
        var simpleTypeModelBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(SimpleTypeModelBinderProvider));
        if (simpleTypeModelBinder == null)
        {
            return options;
        }
 
        var simpleTypeModelBinderIndex = options.ModelBinderProviders.IndexOf(simpleTypeModelBinder);
        options.ModelBinderProviders.Insert(simpleTypeModelBinderIndex, new CustomStringModelBinderProvider());
        return options;
    }
}
در اینجا ابتدا به دنبال SimpleTypeModelBinderProvider توکار گشته و سپس آن‌را با CustomStringModelBinderProvider خود جایگزین می‌کنیم. اگر این model binder سفارشی ما در ایندکس نامناسبی در لیست options.ModelBinderProviders قرارگیرد، هیچگاه فراخوانی نخواهد شد؛ برای مثال اگر پس از SimpleTypeModelBinderProvider قرارگیرد.
در آخر تنها کافی است در کلاس آغازین برنامه، متد الحاقی UseCustomStringModelBinder فوق را به تنظیمات Mvc اضافه کنیم:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.UseCustomStringModelBinder(); 
    });
اشتراک‌ها
نحوه اجرای انتقال محتوا در ASP.NET Core 2.0

We are going to talk about:

What you get out of the box with ASP.NET Core 2.0
How to use Postman to test your API
Changing the default configuration of our project
Testing the content negotiation
Restricting media types
More about formatters
Implementing a custom formatter
Consuming API programmatically 

نحوه اجرای انتقال محتوا در ASP.NET Core 2.0