مطالب
پر کردن مقادیر Enum در کلاینت

در برنامه‌های وب امروز نیازی به فراخوانی ثوابت که در طول حیات برنامه انگشت شمار تغیر میکنند نیست و با توجه به استفاده از فرامین و متدهای سمت کلاینت احتیاج هست تا این ثوابت بار اول لود صفحه به کلاینت پاس داده شوند.

میتوان در این گونه موارد از قابلیت‌های گوناگونی استفاده کرد که در اینجا ما با استفاده از یک فیلد مخفی و json مقدار را به کلاینت پاس میدهیم و در این مثال  در سمت کلاینت نیز دراپ دان را با این مقادیر پر میکنیم: 

    public enum PersistType
    {
        Persistable = 1,
        NotPersist = 2,
        AlwaysPersist = 3
    }

لیست را باید قبل از پر کردن در فیلد مخفی به json بصورت serialize شده تبدیل کرد، برای این منظور از JavaScriptSerializer موجود در اسمبلی‌های دات نت در متد زیر استفاده شده:

        public static string ConvertEnumToJavascript(Type t)
        {
            if (!t.IsEnum) throw new Exception("Type must be an enumeration");

            var values = System.Enum.GetValues(t);
            var dict = new Dictionary<int, string>();

            foreach (object obj in values)
            {
                string name = System.Enum.GetName(t, obj);
                dict.Add(Convert.ToInt32(System.Enum.Format(t, obj, "D")), name);
            }

            return new JavaScriptSerializer().Serialize(dict);
        }

با توجه به اینکه در سمت کلاینت مقدار json ذخیره شده در فیلد مخفی را میتوان به صورت آبجکت برخورد کرد پس یک متد در سمت کلاینت این آبجکت را در loop قراد داده و درمتغییری در فایل جاوا اسکریپت نگهداری میکنیم:

var Enum_PersistType = null;

function SetEnumTypes() {
    Enum_PersistType = JSON.parse($('#hfJsonEnum_PersistType').val());
}

و در هر قسمت که نیاز به مقدار enum بود با توجه به ایندکس مقدار را برای نمایش ازاین متغییر بیرون میکشیم:

function GetPersistTypeTitle_Concept(enumId) {
    return Enum_PersistType[enumId];
}

برای مثال در dropdown در سمت کلاینت این نوع استفاده شده و در حالتی از صفحه فقط برای نمایش عنوان آن احتیاج به دریافت آن از سمت سرور باشد میتوان از این روش کمک گرفت

و یا در سمت javascript میتوان با استفاده از jQuery مقادیر متغییر را در dropdown پر کرد.

function FillDropdown() {
    $("#ddlPersistType").html("");
    $.each(Enum_PersistType, function (key, value) {
        $("#ddlPersistType").append($("<option></option>").val(key).html(value));
    });
}

FillDropdownListOnClient.zip 

مطالب
Minimal API's در دات نت 6 - قسمت سوم - ایجاد endpoints مقدماتی
در دو قسمت قبل، ساختار ابتدایی برنامه‌ی Minimal API's بلاگ دهی را ایجاد کردیم. در این قسمت می‌خواهیم بررسی کنیم، معادل‌های کنترلرهای MVC و اکشن متدهای آن‌ها در سیستم جدید Minimal API، به چه صورتی ایجاد می‌شوند.


ایجاد اولین endpoint از نوع Get مبتنی بر Minimal API

برای افزودن اولین endpoint برنامه، به فایل Program.cs برنامه مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
// ...

app.UseHttpsRedirection();

app.MapGet("/api/authors", async (MinimalBlogDbContext ctx) =>
{
    var authors = await ctx.Authors.ToListAsync();
    return authors;
});

app.Run();
app.MapGet، معادل یک اکشن متد کنترلرهای MVC را که از نوع HttpGet هستند، ارائه می‌دهد. در همینجا می‌توان آدرس دقیق این endpoint را به عنوان پارامتر اول، مشخص کرد که پس از فراخوانی آن در مرورگر، یک Delegate که هندلر نام دارد (پارامتر دوم این متد)، اجرا می‌شود تا Response ای را ارائه دهد.
همانطور که مشاهده می‌کنید می‌توان در اینجا، این Delegate را از نوع Lambda expressions تعریف کرد و با ذکر MinimalBlogDbContext به صورت یک پارامتر آن، کار تزریق وابستگی‌های خودکار آن نیز صورت می‌گیرد. شبیه به حالتی که می‌توان یک سرویس را به عنوان پارامتر یک اکشن متد، با ذکر ویژگی [FromServices] در کنترلرهای MVC معرفی کرد؛ البته در اینجا بدون نیاز به ذکر این ویژگی (هرچند هنوز هم قابل ذکر است). مزیت آن این است که هر endpoint، تنها سرویس‌های مورد نیاز خودش را دریافت می‌کند و نه یک لیست قابل توجه از تمام سرویس‌هایی که قرار است در قسمت‌های مختلف یک کنترلر استفاده شوند.
پس از آن می‌توان با Context ای که در اختیار داریم، عملیات مدنظر را پیاده سازی کرده و یک خروجی را ارائه دهیم. در اینجا دیگر نیازی به تعریف IActionResult‌ها و امثال آن نیست و همه چیز ساده شده‌است.


ایجاد اولین endpoint از نوع Post مبتنی بر Minimal API

app.MapPost، معادل یک اکشن متد کنترلرهای MVC را که از نوع HttpPost هستند، ارائه می‌دهد:
//...

app.UseHttpsRedirection();

//...

app.MapPost("/api/authors", async (MinimalBlogDbContext ctx, AuthorDto authorDto) =>
{
    var author = new Author();
    author.FirstName = authorDto.FirstName;
    author.LastName = authorDto.LastName;
    author.Bio = authorDto.Bio;
    author.DateOfBirth = authorDto.DateOfBirth;

    ctx.Authors.Add(author);
    await ctx.SaveChangesAsync();

    return author;
});

app.Run();

internal record AuthorDto(string FirstName, string LastName, DateTime DateOfBirth, string? Bio);
در ابتدا یک Dto را که حاوی اطلاعات نویسنده‌ی جدیدی است، معادل خواص مدل Author دومین برنامه، تعریف می‌کنیم. سپس می‌توان این Dto را نیز به صورت یک پارامتر جدید به Lambda Expression متد app.MapPost معرفی کرد تا کار نگاشت اطلاعات دریافتی به آن، به صورت خودکار انجام شود (حالت پیش‌فرض آن [FromBody] است که نیازی به ذکر آن نیست).
سعی شده‌است تا این مثال در ساده‌ترین شکل ممکن خودش ارائه شود. در ادامه کار نگاشت خواص Dto را به مدل دومین برنامه، توسط AutoMapper انجام خواهیم داد.
مابقی نکات متد app.MapPost نیز مانند متد app.MapGet است؛ برای مثال در اینجا نیز تعریف مسیر endpoint، توسط اولین پارامتر این متد صورت می‌گیرد و نحوه‌ی تزریق سرویس DbContext برنامه نیز یکی است.


آزمایش برنامه‌ی Minimal API's

برنامه‌ی Minimal API's تهیه شده، به همراه یک Swagger از پیش تنظیم شده نیز هست. به همین جهت برای کار با این API الزاما نیازی به استفاده‌ی از مثلا برنامه‌ی Postman یا راه حل‌های مشابه نیست. بنابراین فقط کافی است تا برنامه‌ی API را اجرا کرده و در رابط کاربری ظاهر شده در آدرس https://localhost:7085/swagger/index.html، بر روی دکمه‌ی Try it out هر کدام از endpointها کلیک کنیم. برای مثال اگر چنین کاری را در قسمت Post انجام دهیم، به تصویر زیر می‌رسیم:



در اینجا پس از ویرایش اطلاعات شیء JSON ای که برای ما تدارک دیده‌است، فقط کافی است بر روی دکمه‌ی execute ذیل آن کلیک کنیم تا اطلاعات این Dto را به app.MapPost متناظر فوق ارسال کند و برای نمونه خروجی بازگشتی از سرور را نیز در همینجا نمایش می‌دهد که در آن، Id رکورد نیز پس از ثبت در بانک اطلاعاتی، مشخص است:



شروع به Refactoring و خلوت کردن فایل Program.cs

اگر بخواهیم به همین نحو تمام endpoints و dtoها را داخل فایل Program.cs اضافه کنیم، پس از مدتی به یک فایل بسیار حجیم و غیرقابل نگهداری خواهیم رسید. بنابراین در مرحله‌ی اول، تنظیمات سرویس‌ها و میان افزارها را به خارج از آن منتقل می‌کنیم. برای این منظور پوشه‌ی جدید Extensions را به همراه دو کلاس زیر ایجاد می‌کنیم:
using Microsoft.EntityFrameworkCore;
using MinimalBlog.Dal;

namespace MinimalBlog.Api.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services,
        WebApplicationBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();

        var connectionString = builder.Configuration.GetConnectionString("Default");
        builder.Services.AddDbContext<MinimalBlogDbContext>(opt => opt.UseSqlServer(connectionString));

        return services;
    }
}
کار این متد الحاقی، خارج کردن تنظیمات سرویس‌های برنامه از کلاس Program است.

همچنین نیاز به متد الحاقی دیگری برای خارج کردن تنظیمات میان‌افزارها داریم:
namespace MinimalBlog.Api.Extensions;

public static class WebApplicationExtensions
{
    public static WebApplication ConfigureApplication(this WebApplication app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();

        return app;
    }
}
پس از این تغییرات، اکنون ابتدای کلاس Program برنامه‌ی Api به صورت زیر تغییر می‌کند و خلاصه می‌شود:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationServices(builder);

var app = builder.Build();
app.ConfigureApplication();

در قسمت بعد، endpoints را از این کلاس آغازین برنامه خارج می‌کنیم.
مطالب دوره‌ها
کار با AutoMapper زمانیکه نوع منبع داده مورد استفاده مشخص نیست
در سناریوهای متداول نگاشت اشیاء، مشخص است که نوع ViewModel برنامه چیست و معادل Model آن کدام است. اما حالت‌هایی مانند کار با anonymous objects و یا data reader و data table و امثال آن نیز وجود دارند که در این حالت‌ها، نوع منبع داده‌ی مورد استفاده، شیء مشخصی نیست که بتوان آن‌را در قسمت CreateMap مشخص کرد. برای مدیریت یک چنین حالت‌هایی، متد DynamicMap طراحی شده‌است.

مثال اول: تبدیل یک DataTable به لیست جنریک معادل

فرض کنید یک DataTable را با ساختار و داده‌های ذیل در اختیار داریم:
var dataTable = new DataTable("SalaryList");
dataTable.Columns.Add("User", typeof (string));
dataTable.Columns.Add("Month", typeof (int));
dataTable.Columns.Add("Salary", typeof (decimal));
 
var rnd = new Random();
for (var i = 0; i < 200; i++)
  dataTable.Rows.Add("User " + i, rnd.Next(1, 12), rnd.Next(400, 2000));
نوع این DataTable کاملا پویا است و می‌تواند هربار در قسمت‌های مختلف برنامه تعریف متفاوتی داشته باشد.
در ادامه معادل کلاس ساختار ستون‌های این DataTable را به صورت ذیل تهیه می‌کنیم.
public class SalaryList
{
  public string User { set; get; }
  public int Month { set; get; }
  public decimal Salary { set; get; }
}
اکنون می‌خواهیم اطلاعات DataTable را به لیستی جنریک از SalaryList نگاشت کنیم. برای اینکار تنها کافی است از متد DaynamicMap استفاده نمائیم:
var salaryList = AutoMapper.Mapper.DynamicMap<IDataReader, List<SalaryList>>(dataTable.CreateDataReader());
منبع داده را از نوع IDataReader بر اساس متد CreateDataReader مشخص کرده‌ایم. به این ترتیب AutoMapper قادر خواهد بود تا اطلاعات این DataTable را به صورت خودکار پیمایش کند. سپس مقصد را نیز لیست جنریکی از کلاس SalaryList تعیین کرده‌ایم. مابقی کار را متد DynamicMap انجام می‌دهد.
کار با AutoMapper نسبت به راه حل‌های Reflection متداول بسیار سریعتر است. زیرا AutoMapper از مباحث Fast reflection به صورت توکار استفاده می‌کند.


مثال دوم: تبدیل لیستی از اشیاء anonymous به لیستی جنریک

در اینجا قصد داریم یک شیء anonymous را به شیء معادل SalaryList آن نگاشت کنیم. این‌کار را نیز می‌توان توسط متد DynamicMap انجام داد:
var anonymousObject = new
{
  User = "User 1",
  Month = 1,
  Salary = 100000
};
var salary = Mapper.DynamicMap<SalaryList>(anonymousObject);
و یا نمونه‌ی دیگر آن تبدیل یک لیست anonymous به معادل جنریک آن است که به نحو ذیل قابل انجام است:
var anonymousList = new[]
{
  new
  {
   User = "User 1",
   Month = 1,
   Salary = 100000
  },
  new
  {
   User = "User 2",
   Month = 1,
   Salary = 300000
  }
};
var salaryList = anonymousList.Select(item => Mapper.DynamicMap<SalaryList>(item)).ToList();
این نکته در مورد حاصل کوئری‌های LINQ یا IQueryable‌ها نیز صادق است.


مثال سوم: نگاشت پویا به یک اینترفیس

فرض کنید یک چنین اینترفیسی، در برنامه تعریف شده‌است و همچنین دارای هیچ نوع پیاده سازی هم در برنامه نیست:
public interface ICustomerService
{
  string Code { get; set; }
  string Name { get; set; }
}
اکنون قصد داریم یک شیء anonymous را به آن نگاشت کنیم:
var anonymousObject = new
{
  Code = "111",
  Name = "Test 1"
};
var result = Mapper.DynamicMap<ICustomerService>(anonymousObject);
در این حالت خاص، AutoMapper با استفاده از یک Dynamic Proxy به نام LinFu (که با اسمبلی آن Merge شده‌است)، پیاده سازی پویایی را از اینترفیس مشخص شده تهیه کرده و سپس کار نگاشت را انجام می‌دهد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: 
AM_Sample05.zip
مطالب
واژه‌های کلیدی جدید and، or و not در C# 9.0
یکی از ویژگی‌های زبان VB، شباهت بیش از اندازه‌ی آن به زبان انگلیسی است. برای مثال در این زبان با استفاده از not و and:
If Not a And b Then
  ...
Else
  ...
EndIf
می‌توان if‌های خواناتری را نسبت به #C ایجاد کرد:
if(!(a) && b)
{
...
}
else
{
}
در ادامه خواهیم دید که چگونه C# 9.0، این آرزوی دیرین را برآورده می‌کند! البته مایکروسافت در جای دیگری هم عنوان کرده‌است که زبان VB را دیگر پیگیری نمی‌کند و تغییر خاصی را در آن شاهد نخواهید بود. شاید به همین دلیل و جذب برنامه نویس‌های VB به #C، یک چنین تغییراتی رخ داده‌اند!


معرفی واژه‌ی کلیدی جدید not در C# 9.0

در ابتدا اینترفیس نمونه‌ای را به همراه دو کلاس مشتق شده‌ی از آن درنظر بگیرید:
public interface ICommand
{
}

public class Command1 : ICommand
{
}

public class Command2 : ICommand
{
}
اکنون اگر وهله‌ای از Command1 را ایجاد کرده و بخواهیم بررسی کنیم که آیا از نوع کلاس Command2 هست یا خیر، با استفاده از pattern matching و واژه‌ی کلیدی if می‌توان به صورت زیر عمل کرد:
ICommand command = new Command1();
if (!(command is Command2))
{

}
در C# 9.0، برای خواناتر کردن یک چنین بررسی‌هایی، می‌توان از pattern matching بهبود یافته‌ی آن و واژه‌ی کلیدی جدید not نیز استفاده کرد:
if (command is not Command2)
{

}


معرفی واژه‌های کلیدی جدید and و or در C# 9.0

واژه‌های کلیدی جدید and و or نیز درک و نوشتن عبارات pattern matching را بسیار ساده می‌کنند. برای نمونه قطعه کد متداول زیر را درنظر بگیرید:
if ((command is ICommand) && !(command is Command2))
{

}
اکنون در C# 9.0 با استفاده از واژه‌های کلیدی جدید and، or و not، می‌توان قطعه کد فوق را بسیار ساده کرد:
if (command is ICommand and not Command2)
{

}
نه تنها این قطعه کد ساده‌تر شده‌است، بلکه خوانایی آن افزایش یافته‌است و مانند یک سطر نوشته شده‌ی به زبان انگلیسی به نظر می‌رسد. همچنین در این حالت نیازی هم به تکرار command، در هر بار مقایسه نیست.
و یا حتی در اینجا در صورت نیاز می‌توان از واژه‌ی کلیدی جدید or نیز استفاده کرد:
if (command is Command1 or Command2)
{

}


امکان اعمال واژه‌های کلیدی جدید and، or و not به سایر نوع‌ها نیز وجود دارند

تا اینجا مثال‌هایی را که بررسی کردیم، در مورد بررسی نوع اشیاء بود. اما می‌توان این واژه‌های کلیدی جدید در C# 9.0 را به هر نوع ممکنی نیز اعمال کرد. برای نمونه، مثال ساده‌ی زیر را که در مورد بررسی اعداد است، درنظر بگیرید:
var number = new Random().Next(1, 10);
if (number > 2 && number < 8)
{

}
اکنون در C# 9.0 و با استفاده از امکانات جدید pattern matching آن می‌توان شرط متداول فوق را به صورت زیر ساده کرد:
if (number is > 2 and < 8)
{

}
در اینجا تنها یکبار نیاز به ذکر number است و از واژه‌های کلیدی is و and استفاده شده‌است.

یک مثال دیگر: متد زیر را در نظربگیرید که با استفاده از && و || متداول #C نوشته شده‌است:
public static bool IsLetterOrSeparator(char c) =>
   (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '.' || c == ',';
روش ارائه‌ی C# 9.0 ای آن به صورت زیر است:
public static bool IsLetterOrSeparator(char c) =>
   c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';


امکان اعمال واژه‌های کلیدی جدید and، or و not به switchها نیز وجود دارد

برای نمونه قطعه کد if/else دار متداول زیر را درنظر بگیرید که قابلیت تبدیل به یک سوئیچ را نیز دارد:
 var number = new Random().Next(1, 10);
 if (number <= 0)
 {
     Console.WriteLine("Less than or equal to 0");
 }
 else if (number > 0 && number <= 10)
 {
     Console.WriteLine("More than 0 but less than or equal to 10");
 }
 else
 {
     Console.WriteLine("More than 10");
 }
اگر بخواهیم همین قطعه کد را به کمک واژه‌های کلیدی جدید C# 9.0 و pattern matching بهبود یافته‌ی آن تبدیل به یک سوئیچ کنیم، به قطعه کد زیر خواهیم رسید:
// C#9.0
 switch (number)
 {
     case <= 0:
         Console.WriteLine("Less than or equal to 0");
         break;
     case > 0 and <= 10:
         Console.WriteLine("More than 0 but less than or equal to 10");
         break;
     default:
         Console.WriteLine("More than 10");
         break;
 }
تا پیش از C# 7.0، سوئیچ‌های #C امکان بررسی باز‌ه‌ای از مقادیر را نداشتند. از آن زمان با معرفی pattern matching، چنین محدودیتی برطرف شد و اکنون می‌توان syntax قدیمی آن‌را توسط C# 9.0، بسیار خلاصه‌تر کرد. در ذیل، معادل قطعه کد فوق را بر اساس امکانات C# 7.0 مشاهده می‌کنید که خوانایی کمتری را داشته و حجم کد نویسی بیشتری را دارد:
// C#7.0
 switch (number)
 {
     case int value when value <= 0:
         Console.WriteLine("Less than or equal to 0");
         break;
     case int value when value > 0 && value <= 10:
         Console.WriteLine("More than 0 but less than or equal to 10");
         break;
     default:
         Console.WriteLine("More than 10");
         break;
 }

و یا حتی می‌توان سوئیچ C# 9.0 را توسط switch expression بهبود یافته‌ی C# 8.0 نیز به شکل زیر بازنویسی کرد:
 var message = number switch
 {
     <= 0 => "Less than or equal to 0",
     > 0 and <= 10 => "More than 0 but less than or equal to 10",
     _ => "More than 10"
 };


انواع pattern matching‌های اضافه شده‌ی به C# 9.0

در این مطلب سعی شد مفاهیم pattern matching اضافه شده‌ی به C# 9.0، ذیل عنوان واژه‌های کلیدی جدید آن بحث شوند؛ اما هر کدام دارای نام‌های خاصی هم هستند:
الف) relational patterns: امکان استفاده‌ی از <, >, <= and >= را در الگوها میسر می‌کنند. مانند نمونه‌های سوئیچی که نوشته شد.
ب) logical patterns: امکان استفاده‌ی از واژه‌های کلیدی and، or و not را در الگوها ممکن می‌کنند.
ج) not pattern: امکان استفاده‌ی از واژه‌ی کلیدی not را در عبارات if میسر می‌کند.
د) Simple type pattern: در مثال‌های زیر، پس از انطباق با یک الگو، کاری با متغیر یا شیء مرتبط نداریم. در نگارش‌های قبلی برای صرفنظر کردن از آن، ذکر _ ضروری بود؛ اما در C#9.0 می‌توان آن‌را نیز ذکر نکرد:
private static int GetDiscount(Product p) => p switch
        {
            Food => 0, // Food _ => 0 before C# 9
            Book b => 75, // Book b _ => 75 before C# 9
            _ => 25
        };
مطالب
مقید سازی پارامترهای نوع جنریک
احتمالا در بیشتر مقالات (فارسی/انگلیسی) عبارات هایی مثل نمونه‌های زیر را دیده اید :
where T:clas
where T:struc
...
در این مقاله قصد داریم بپردازیم به «مقید سازی پارامتر‌های نوع جنریک» و اینکه چه کاربردی دارند و در چه زمانی بهتر است از آن‌ها استفاده کنیم و نحوه استفاده از آنها چگونه است. فرض میکنیم که خواننده‌ی محترم با مفاهیم جنریک آشنایی دارد. در صورتیکه با جنریک‌ها آشنا نیستید ابتدا مروری داشته باشید بر جنریک‌ها و بعد این مقاله را مطالعه فرمایید؛ به این دلیل که موضوع مورد بحث بر پایه‌ی جنریک‌ها می‌باشد.

همانطور که مطلع هستید هر عنصری جنریکی را که تعریف میکنید حداقل دارای یک پارامتر نوع هست و در زمان بکارگیری آن جنریک باید نوع آن را مشخص نمایید. برای نمونه مثال زیر را در نظر بگیرید :
public  class MyCollection<T>
        {
            private List<T> collections = new List<T>();
            public void Add(T value)
            {
                collections.Add(value);
            }
        }
کلاس فوق یک کلاس جنریک است که در هنگام ساخت نمونه‌ای از آن، باید ابتدا data type نوعی را که که می‌خواهیم با آن کار کنیم، تعیین کنیم. برای مثال در کد فوق در هنگام ساخت نمونه‌ای از آن، نوع int را برای آن مشخص میکنیم و هر وقت بخواهیم متد Add آن را فراخوانی کنیم، فقط نوعی را قبول خواهد کرد که در ابتدا برای آن تعیین کرده ایم (int):
MyCollection<int> myintObj = new MyCollection<int>();
            myintObj.Add(12);
            myintObj.Add(33);
             myintObj.Add(33.3);// ERROR z 
سؤال: می‌خواهیم فقط نوع‌هایی را بتوان به T نسبت داد که از نوع ارجاعی (reference type) هستن و یا فقط نوع هایی را به T نسبت داد که یک سازنده دارند؛ چگونه؟

ایجاد قید‌ها یا محدودیت‌ها بر روی پارامتر‌های جنریک‌ها شامل پنج حالت می‌باشد:

حالت اول : Where T:struct
در این حالت T باید یک ساختار باشد .

حالت دوم : where T:class
T  باید یک نوع ارجاعی باشد. اگر در مثال فوق این قید را به آن اضافه کنیم، در هنگام ساخت نمونه‌ای از کلاس فوق، اگر یک نوع value type را به T نسبت دهیم، در هنگام وارد کردن یک نوع value type با خطا مواجه خواهیم شد. مثال:
public  class MyCollection<T> where T:class
        {
            private List<T> collections = new List<T>();
            public void Add(T value)
            {
                collections.Add(value);
            }
        }
و برای استفاده :
 MyCollection<int> myintObj = new MyCollection<int>(); // ERROR , int is value type

حالت سوم : ()Where T:new
نوعی که به T نسبت داده می‌شود باید یک سازنده‌ی پیش فرض داشته باشد.
داخل پرانتز : سازنده‌ی پیش فرض: زمانی که شما یک کلاس می‌نویسید اگر آن کلاس دارای هیچ سازنده‌ای نباشد، کامپایلر یک سازنده‌ی بدون پارامتر را به کلاس فوق اضافه می‌کند که کار آن مقدار دهی به فیلد‌های کلاس است. در اینجا از مقادیر پیش فرض استفاده می‌شود. مثلا برای int مقدار صفر و برای string مقدار "" و به همین ترتیب.
اگر از مقدار دهی پیش فرض توسط کامپایلر خرسند نیستید، می‌توانید سازنده پیش فرض را تغییر داده و مطابق میل خود فیلد‌ها را مقدار دهی اولیه کنید .


حالت چهارم : where T:NameOfBaseClass
نوعی که به T نسبت داده می‌شود باید از کلاس NameOfBaseClass ارث بری کرده باشد.

حالت پنجم : where T:NameOfInterface
همانند حالت چهارم می‌باشد؛ با این تفاوت: نوعی که به T نسبت داده می‌شود باید واسط NameOfInterface را پیاده سازی کرده باشد.

پنج حالت فوق نمونه‌هایی از ایجاد محدودیت بر روی پرامتر نوع اعضای جنریک بودند و اما در ادامه قصد داریم نکاتی را در این باب، بیان کنیم:

نکته اول : می‌توانید محدودیت‌های فوق را با هم ترکیب کنید برای اینکار آنها را با کاما از هم جدا کنید :
 public  class MyCollection<T> where T:class,IDisposable,new()
        {
   //content
}
نوعی که به T نسبت داده می‌شود
  • باید از نوع ارجاعی باشد.
  • باید واسط IDisposable را پیاده سازی کرده باشد.
  • باید یک سازنده‌ی پیش فرض داشته باشد.

نکته دوم : زمانیکه از چندین محدودیت استفاده می‌کنید مثل مثال فوق، باید محدودیت ()new در آخرین جایگاه محدودیت‌ها قرار گیرد؛ در غیر اینصورت با خطای زمان ترجمه روبه رو خواهید شد .

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

public void Swap<T>(ref T val1,ref T val2) where T:struct
            {
//content
            }
نکته چهارم : زمانیکه کلاس و یا متدهای شما بیش از یک نوع پارامتر از نوع جنریک را دریافت می‌کنند، باید محدودیت‌های مورد نظر را برای هر کدام به صورت جداگانه قید کنید. به طور مثال به کلاس زیر که دو پارمتر T و K را دارد، باید برای هر کدام جداگانه محدودیت‌های مورد نظر را اعمال کنیم (در صورت نیاز):
 public  class MyCollection<T,K> where T:class where K:IDisposable,new()
        {
//content
}
مطالب
آموزش MEF#1
Managed Extensibility Framework یا MEF کامپوننتی از Framework 4 است که برای ایجاد برنامه‌های توسعه پذیر (Extensible) با حجم کم کد استفاده میشه.این تکنولوژی به برنامه نویسان این امکان رو میده که توسعه‌های (Extension) برنامه رو بدون پیکربندی استفاده کنند. همچنین به توسعه دهندگان این اجازه رو می‌ده که به آسانی کدها رو کپسوله کنند .
MEF به عنوان بخشی از 4 NET. و Silverlight 4 معرفی شد. MEF یک راه حل ساده برای مشکل توسعه در حال اجرای برنامه‌ها ارائه می‌کند.تا قبل از این تکنولوژی ، هر برنامه‌ای که می‌خواست یک مدل Plugin را پشتیبانی کنه لازم بود که خودش زیر ساخت‌ها را از ابتدا ایجاد کنه . این Plugin‌ها اغلب برای برنامه‌های خاصی بودند و نمی‌توانستند در پیاده سازی‌های چندگانه دوباره استفاده شوند. ولی MEF در راستای حل این مشکلات ، روش استانداردی رو برای میزبانی برنامه‌های کاربردی پیاده کرده است. 
برای فهم بهتر مفاهیم یک مثال ساده رو با MEF پیاده سازی می‌کنم.
ابتدا یک پروژه از نوع Console Application ایجاد کنید . بعد با استفاده از Add Reference یک ارجاع به System.ComponentModel.Composition بدید. سپس یک Interface به نام IViewModel را به صورت زیر ایجاد کنید:
public interface IViewModel
    {
        string Name { get; set; }
    }

یک خاصیت به نام Name برای دسترسی به نام ViewModel ایجاد می‌کنیم.
سپس 2 تا ViewModel دیگه ایجاد می‌کنیم که IViewModel را پیاده سازی کنند. به صورت زیر:
ViewModelFirst:
[Export( typeof( IViewModel ) )]
    public class ViewModelFirst : IViewModel
    {
        public ViewModelFirst()
        {
            this.Name = "ViewModelFirst";
        }

        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }
        private string _name;
    }


ViewModelSecond:
[Export( typeof( IViewModel ) )]
    public class ViewModelSecond : IViewModel
    {
        public ViewModelSecond()
        {
            this.Name = "ViewModelSecond";
        }

        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }
        private string _name;
    }


Export Attribute استفاده شده در بالای کلاس‌های ViewModel به این معنی است که این کلاس‌ها اینترفیس IViewModel رو Export کردند تا در جای مناسب بتونیم این ViewModel ‌ها Import کنیم.(Import , Export از مفاهیم اصلی در MEF هستند)
حالا نوبت به پیاده سازی کلاس Plugin می‌رسه.
public class PluginManager
    {
        public PluginManager()
        {

        }

        public IList<IViewModel> ViewModels
        {
            get
            {
                return _viewModels;
            }
            private set
            {
                _viewModels = value;
            }
        }

        [ImportMany( typeof( IViewModel ) )]
        private IList<IViewModel> _viewModels = new List<IViewModel>();

        public void SetupManager()
        {
            AggregateCatalog aggregateCatalog = new AggregateCatalog();

            CompositionContainer container = new CompositionContainer( aggregateCatalog );

            CompositionBatch batch = new CompositionBatch();

            batch.AddPart( this );

            aggregateCatalog.Catalogs.Add( new AssemblyCatalog( Assembly.GetExecutingAssembly() ) );           

            container.Compose( batch );
        }

کلاس PluginManager برای شناسایی و استفاده از کلاس هایی که صفت‌های Export رو دارند نوشته شده(دقیقا شبیه یک UnityContainer در Microsoft Unity Application Block یا IKernel در Ninject) عمل می‌کنه با این تفاوت که نیازی به Register با Bind کردن ندارند)
ابتدا بک لیست از کلاس هایی که IViewModel رو Export کردند داریم.
بعد در متد SetupManager ابتدا یک AggregateCatalog نیاز داریم تا بتونیم Composition Part‌ها رو بهش اضافه کنیم. به کد زیر توجه کنید:
 aggregateCatalog.Catalogs.Add( new AssemblyCatalog( Assembly.GetExecutingAssembly() ) );

تو این قطعه کد من یک Assembly Catalog رو که به Assembly جاری برنامه اشاره می‌کنه به AggregateCatalog اضافه کردم.
متد (batch.AddPart(this در واقع به این معنی است که به MEF گفته می‌شود این کلاس ممکن است شامل Export هایی باشد که به یک یا چند Import وابستگی دارند.
متد (AddExport(this در CompositionBatch به این معنی است که این کلاس ممکن است شامل Exportهایی باشد که به Import وابستگی ندارند.
حالا برای مشاهده نتایج کد زیر را در کلاس Program اضافه می‌کنیم:
static void Main( string[] args )
        {
            PluginManager plugin = new PluginManager();

            Console.WriteLine( string.Format( "Number Of ViewModels Before Plugin Setup Is [ {0} ]", plugin.ViewModels.Count ) );

            Console.WriteLine( Environment.NewLine );

            plugin.SetupManager();

            Console.WriteLine( string.Format( "Number Of ViewModels After Plugin Setup Is [ {0} ]", plugin.ViewModels.Count ) );

            Console.ReadLine();
        }

در کلاس بالا ابتدا تعداد کلاس‌های موجود در لیست ViewModels رو قبل از Setup کردن Plugin نمایش داده سپس بعد از Setup  کردن Plugin  دوباره تعداد کلاس‌های موجود در لیست ViewModel رو مشاهده می‌کنیم.که خروجی به شکل زیر تولید خواهد شد.


متد SetupManager در کلاس Plugin (با توجه به AggregateCatalog) که در این برنامه فقط Assembly  جاری رو بهش اضافه کردیم تمام کلاس هایی رو که نوع IViewModel رو Export کردند پیدا کرده و در لیست اضافه می‌کنه(این کار رو با توجه به ImportMany Attribute) انجام میده. در پست‌های بعدی روش استفاده از MEF رو در Prism یا WAF توضیح می‌دم.
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config
یکی دیگر از تغییرات ASP.NET Core با نگارش‌های قبلی آن، تغییرات اساسی در مورد نحوه‌ی کار با تنظیمات برنامه و فایل‌های مرتبط با آن‌ها است. در ASP.NET Core می‌توانید:
- تنظیمات برنامه را از چندین منبع مختلف خوانده و آن‌ها را یکی کنید.
- تنظیمات را بر اساس تنظیمات مختلف محیطی برنامه، بارگذاری کنید.
- امکان نگاشت اطلاعات خوانده شده‌ی از فایل‌های کانفیگ به کلاس‌ها پیش بینی شده‌است.
- امکان بارگذاری مجدد فایل‌های کانفیگ درصورت تغییر، بدون ری‌استارت کل برنامه وجود دارد.
- امکان تزریق وابستگی‌های تنظیمات برنامه، به قسمت‌های مختلف آن پیش بینی شده‌است.


نصب پیشنیاز خواندن تنظیمات برنامه از یک فایل JSON

برای شروع به کار با خواندن تنظیمات برنامه در ASP.NET Core، نیاز است ابتدا بسته‌ی نیوگت Microsoft.Extensions.Configuration.Json را نصب کنیم.
برای این منظور بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن Microsoft.Extensions.Configuration.Json را جستجو کرده و نصب نمائید:


البته همانطور که در تصویر مشاهده می‌کنید، اگر صرفا Microsoft.Extensions.Configuration را جستجو کنید (بدون ذکر JSON)، بسته‌های مرتبط با خواندن فایل‌های کانفیگ از نوع XML و یا حتی INI را هم خواهید یافت.
انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
{
    "dependencies": {
         //same as before  
         "Microsoft.Extensions.Configuration.Json": "1.0.0"
    },

 
افزودن یک فایل کانفیگ JSON دلخواه

بر روی پروژه کلیک راست کرده و از طریق منوی add->new item یک فایل خالی جدید را به نام appsettings.json ایجاد کنید (نام این فایل دلخواه است)؛ با این محتوا:
{
    "Key1": "Value1",
    "Auth": {
        "Users": [ "Test1", "Test2", "Test3" ]
    },
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    }
}
در نگارش‌های پیشین ASP.NET که از web.config برای تعریف تنظیمات برنامه استفاده می‌شد، حالت پیش فرض ذکر تنظیمات برنامه می‌توانست تنها یک سطحی و با ساختار ذیل باشد (البته امکان کدنویسی و نوشتن مداخل سفارشی هم وجود داشت؛ ولی حالت پیش فرض appSettings، تنها key/valueهای یک سطحی هستند):
<appSettings>
   <add key="Logging-IncludeScopes" value="false" />
   <add key="Logging-Level-Default" value="verbose" />
   <add key="Logging-Level-System" value="Information" />
   <add key="Logging-Level-Microsoft" value="Information" />
</appSettings>
اما اکنون یک فایل JSON را با هر تعداد سطح مورد نیاز می‌توان تعریف و استفاده کرد و برای اینکار نیازی به نوشتن کدهای سفارشی تعریف مداخل خاص، وجود ندارد.
در فایل JSON فوق، نمونه‌ای از key/valueها، آرایه‌ها و اطلاعات چندین سطحی را مشاهده می‌کنید.


خواندن فایل تنظیمات appsettings.json در برنامه

پس از نصب پیشنیاز خواندن فایل‌های کانفیگ از نوع JSON، به فایل آغازین برنامه مراجعه کرده و سازنده‌ی جدیدی را به آن اضافه کنید:
public class Startup
{
    public IConfigurationRoot Configuration { set; get; }
 
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
                            .SetBasePath(env.ContentRootPath)
                            .AddJsonFile("appsettings.json");
        Configuration = builder.Build();
    }
در اینجا نحوه‌ی خواندن فایل کانفیگ جدید appsettings.json را مشاهده می‌کنید. چند نکته در اینجا حائز اهمیت هستند:
الف) این خواندن، در سازنده‌ی کلاس آغازین برنامه و پیش از تمام تنظیمات دیگر باید انجام شود.
ب) جهت در معرض دید قرار دادن اطلاعات خوانده شده، آن‌را به یک خاصیت عمومی انتساب داده‌ایم.
ج) متد SetBasePath جهت مشخص کردن محل یافتن فایل appsettings.json ذکر شده‌است. این اطلاعات را می‌توان از سرویس توکار IHostingEnvironment و خاصیت ContentRootPath آن دریافت کرد. همانطور که ملاحظه می‌کنید، این تزریق وابستگی نیز به صورت خودکار توسط ASP.NET Core مدیریت می‌شود.


دسترسی به تنظیمات خوانده شده توسط اینترفیس IConfigurationRoot

تا اینجا موفق شدیم تا تنظیمات خوانده شده را به خاصیت عمومی Configuration از نوع IConfigurationRoot انتساب دهیم. اما ساختار ذخیره شده‌ی در این اینترفیس به چه صورتی است؟


همانطور که مشاهده می‌کنید، هر سطح از سطح قبلی آن با : جدا شده‌است. همچنین اعضای آرایه، دارای ایندکس‌های 0: و 1: و 2: هستند. بنابراین برای خواندن این اطلاعات می‌توان نوشت:
var key1 = Configuration["Key1"];
var user1 = Configuration["Auth:Users:0"];
var authUsers = Configuration.GetSection("Auth:Users").GetChildren().Select(x => x.Value).ToArray();
var loggingIncludeScopes = Configuration["Logging:IncludeScopes"];
var loggingLoggingLogLevelDefault = Configuration["Logging:LogLevel:Default"];
خاصیت Configuration نیز در نهایت بر اساس key/valueها کار می‌کند و این keyها اگر چند سطحی بودند، با : از هم جدا می‌شوند و اگر نیاز به دسترسی اعضای خاصی از آرایه‌ها وجود داشت می‌توان آن ایندکس خاص را در انتهای زنجیره ذکر کرد. همچنین در اینجا نحوه‌ی استخراج تمام اعضای یک آرایه را نیز مشاهده می‌کنید.

یک نکته: خاصیت Configuration، دارای متد GetValue نیز هست که توسط آن می‌توان نوع مقدار دریافتی و یا حتی مقدار پیش فرضی را در صورت عدم وجود این key، مشخص کرد:
 var val = Configuration.GetValue<int>("key-name", defaultValue: 10);
در متد GetValue، آرگومان جنریک آن، یک کلاس را نیز می‌پذیرد. یعنی می‌توان خواص تو در توی مشخص شده‌ی با : را به یک کلاس نیز نگاشت کرد. در اینجا مقدار کلید معرفی شده، اولین سطحی خواهد بود که باید این اطلاعات از آن استخراج و نگاشت شوند.


سرویس IConfigurationRoot قابل تزریق است

در قسمت قبل، سرویس‌ها و تزریق وابستگی‌ها را بررسی کردیم. نکته‌ی جالبی را که می‌توان به آن اضافه کرد، قابلیت تزریق خاصیت عمومی
public class Startup
{
    public IConfigurationRoot Configuration { set; get; }
به تمام قسمت‌های برنامه است. برای نمونه در همان مثال قسمت قبل، قصد داریم تنظیمات برنامه را در لایه سرویس آن خوانده و مورد استفاده قرار دهیم. برای اینکار باید مراحل ذیل طی شوند:
الف) اعلام موجودیت IConfigurationRoot به IoC Container
اگر از استراکچرمپ استفاده می‌کنید، باید مشخص کنید، زمانیکه IConfigurationRoot درخواست شد، آن‌را چگونه باید از خاصیت مرتبط با آن دریافت کند:
var container = new Container();
container.Configure(config =>
{
    config.For<IConfigurationRoot>().Singleton().Use(() => Configuration);
و یا اگر از همان IoC Container توکار ASP.NET Core استفاده می‌کنید، روش انجام این‌کار در متد ConfigureServices به صورت زیر است:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
طول عمر آن هم singleton مشخص شده‌است تا تنها یکبار وهله سازی و سپس کش شود (مناسب برای کار با تنظیمات سراسری برنامه).

ب) فایل project.json کتابخانه‌ی Core1RtmEmptyTest.Services را گشوده و وابستگی Microsoft.Extensions.Configuration.Abstractions را به آن اضافه کنید:
{ 
    "dependencies": {
        //same as before 
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0"
    }
این وابستگی امکان دسترسی به اینترفیس IConfigurationRoot را در اسمبلی‌های دیگر میسر می‌کند.

ج) سپس فایل MessagesService.cs را گشوده و این اینترفیس را به سازنده‌ی سرویس MessagesService تزریق می‌کنیم:
public interface IMessagesService
{
    string GetSiteName();
}
 
public class MessagesService : IMessagesService
{
    private readonly IConfigurationRoot _configurationRoot;
 
    public MessagesService(IConfigurationRoot configurationRoot)
    {
        _configurationRoot = configurationRoot;
    }
 
    public string GetSiteName()
    {
        var key1 = _configurationRoot["Key1"];
        return $"DNT {key1}";
    }
}
در ادامه، نحوه‌ی استفاده‌ی از آن، همانند نکاتی است که در قسمت «دسترسی به تنظیمات خوانده شده توسط اینترفیس IConfigurationRoot» عنوان شد.
اکنون اگر برنامه را اجرا کنید، با توجه به اینکه میان افزار Run از این سرویس سفارشی استفاده می‌کند:
public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    IMessagesService messagesService)
{ 
    app.Run(async context =>
    {
        var siteName = messagesService.GetSiteName();
        await context.Response.WriteAsync($"Hello {siteName}");
    });
}
چنین خروجی را خواهیم داشت:



خواندن تنظیمات از حافظه

الزاما نیازی به استفاده از فایل‌های JSON و یا XML در اینجا وجود ندارد. ابتدایی‌ترین حالت کار با بسته‌ی Microsoft.Extensions.Configuration، متد AddInMemoryCollection آن است که در اینجا می‌توان لیستی از key/value‌ها را ذکر کرد:
var builder = new ConfigurationBuilder()
                    .AddInMemoryCollection(new[]
                                {
                                    new KeyValuePair<string,string>("the-key", "the-value"),
                                });
 و نحوه‌ی کار با آن نیز همانند قبل است:
 var theValue = Configuration["the-key"];


امکان بازنویسی تنظیمات انجام شده، بسته به شرایط محیطی

در اینجا محدود به یک فایل JSON و یک فایل تنظیمات برنامه، نیستیم. برای کار با ConfigurationBuilder می‌توان از Fluent interface آن استفاده کرد و به هر تعدادی که نیاز بود، متدهای خواندن از فایل‌های کانفیگ دیگر را اضافه کرد:
public class Startup
{
    public IConfigurationRoot Configuration { set; get; }
 
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
                            .SetBasePath(env.ContentRootPath)
                            .AddInMemoryCollection(new[]
                                {
                                    new KeyValuePair<string,string>("the-key", "the-value"),
                                })
                            .AddJsonFile("appsettings.json", reloadOnChange: true, optional: false)
                            .AddJsonFile($"appsettings.{env}.json", optional: true);
        Configuration = builder.Build();
    }
و نکته‌ی مهم اینجا است که تنظیمات فایل دوم، تنظیمات مشابه فایل اول را بازنویسی می‌کند.
برای مثال در اینجا آخرین AddJsonFile تعریف شده، بنابر متغیر محیطی فعلی به appsettings.development.json تفسیر شده و در صورت وجود این فایل (با توجه به optional بودن آن) اطلاعات آن دریافت گردیده و اطلاعات مشابه فایل appsettings.json قبلی را بازنویسی می‌کند.


امکان دسترسی به متغیرهای محیطی سیستم عامل

در انتهای زنجیره‌ی ConfigurationBuilder می‌توان متد AddEnvironmentVariables را نیز ذکر کرد:
 var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
این متد سبب می‌شود تا تمام اطلاعات قسمت Environment سیستم عامل، به مجموعه‌ی تنظیمات جاری اضافه شوند (در صورت نیاز) که نمونه‌ای از آن‌را در تصویر ذیل مشاهده می‌کنید:



امکان نگاشت تنظیمات برنامه به کلاس‌‌های متناظر

کار کردن با key/valueهای رشته‌ای، هرچند روش پایه‌ای استفاده‌ی از تنظیمات برنامه است، اما آنچنان refactoring friendly نیست. در ASP.NET Core امکان تعریف تنظیمات strongly typed نیز پیش بینی شده‌است. برای این منظور باید مراحل زیر طی شوند:
به عنوان نمونه تنظیمات فرضی smtp ذیل را به انتهای فایل appsettings.json اضافه کنید:
{
    "Key1": "Value1",
    "Auth": {
        "Users": [ "Test1", "Test2", "Test3" ]
    },
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    },
    "Smtp": {
        "Server": "0.0.0.1",
        "User": "user@company.com",
        "Pass": "123456789",
        "Port": "25"
    }
}
مثال جاری که بر اساس ASP.NET Core Web Application و با قالب خالی آن ایجاد شده‌است، دارای نام فرضی Core1RtmEmptyTest است. در همین پروژه بر روی پوشه‌ی src کلیک راست کرده و گزینه‌ی Add new project را انتخاب کنید و سپس یک پروژه‌ی جدید از نوع NET Core -> Class library. را به آن با نام Core1RtmEmptyTest.ViewModels اضافه کنید (تصویر ذیل).


در این کتابخانه‌ی جدید که محل نگهداری ViewModelهای برنامه خواهد بود، کلاس معادل قسمت smtp فایل config فوق را اضافه کنید:
namespace Core1RtmEmptyTest.ViewModels
{
    public class SmtpConfig
    {
        public string Server { get; set; }
        public string User { get; set; }
        public string Pass { get; set; }
        public int Port { get; set; }
    }
}
از این جهت این کلاس را در یک library جداگانه قرار داده‌ایم تا بتوان از آن در لایه‌ی سرویس و همچنین خود برنامه استفاده کرد. اگر این کلاس را در برنامه‌ی اصلی قرار می‌دادیم، امکان دسترسی به آن در لایه‌ی سرویس میسر نمی‌شد.
سپس به پروژه‌ی Core1RtmEmptyTest مراجعه کرده و بر روی گره references آن کلیک راست کنید. در اینجا گزینه‌ی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.ViewModels را انتخاب کنید، تا اسمبلی آن‌را بتوان در پروژه‌ی جاری استفاده کرد.
انجام اینکار معادل است با افزودن یک سطر ذیل به فایل project.json پروژه:
{
    "dependencies": {
        // same as before        
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*"
    },
اکنون با فرض وجود تنظیمات خواندن فایل appsettings.json در سازنده‌ی کلاس آغازین برنامه، نیاز است بسته‌ی نیوگت Microsoft.Extensions.Configuration.Binder را نصب کنید:


و سپس در کلاس آغازین برنامه و متد ConfigureServices آن، نحوه‌ی نگاشت قسمت Smtp فایل کانفیگ را مشخص کنید:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
   services.Configure<SmtpConfig>(options => Configuration.GetSection("Smtp").Bind(options));
در اینجا مشخص شده‌است که کار وهله سازی کلاس SmtpConfig بر اساس اطلاعات قسمت smtp فایل کانفیگ تامین می‌شود. متغیر Configuration ایی که در اینجا استفاده شده‌است همان خاصیت عمومی public IConfigurationRoot Configuration کلاس آغازین برنامه است.

سپس برای استفاده از این تنظیمات strongly typed (برای نمونه در لایه سرویس برنامه)، ابتدا ارجاعی را به پروژه‌ی Core1RtmEmptyTest.ViewModels به لایه‌ی سرویس برنامه اضافه می‌کنیم (بر روی گره references آن کلیک راست کنید. در اینجا گزینه‌ی add reference را انتخاب کرده و سپس Core1RtmEmptyTest.ViewModels را انتخاب کنید).
در ادامه نیاز است بسته‌ی نیوگت جدیدی را به نام Microsoft.Extensions.Options به لایه‌ی سرویس برنامه اضافه کنیم. به این ترتیب قسمت وابستگی‌های فایل project.json این لایه چنین شکلی را پیدا می‌کند:
    "dependencies": {
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*",
        "Microsoft.Extensions.Configuration.Abstractions": "1.0.0",
        "Microsoft.Extensions.Options": "1.0.0",
        "NETStandard.Library": "1.6.0"
    }
پس از ذخیره سازی این کلاس و بازیابی خودکار وابستگی‌های آن، اکنون برای دسترسی به این تنظیم باید از اینترفیس ویژه‌ی IOptions استفاده کرد (به همین جهت بسته‌ی جدید نیوگت Microsoft.Extensions.Options را نصب کردیم):
public interface IMessagesService
{
    string GetSiteName();
}
 
public class MessagesService : IMessagesService
{
    private readonly IConfigurationRoot _configurationRoot;
    private readonly IOptions<SmtpConfig> _settings;
 
    public MessagesService(IConfigurationRoot configurationRoot, IOptions<SmtpConfig> settings)
    {
        _configurationRoot = configurationRoot;
        _settings = settings;
    }
 
    public string GetSiteName()
    {
        var key1 = _configurationRoot["Key1"];
        var server = _settings.Value.Server;
        return $"DNT {key1} - {server}";
    }
}
همانطور که ملاحظه می‌کنید <IOptions<SmtpConfig به سازنده‌ی کلاس تزریق شده‌است و سپس از طریق خاصیت Value آن می‌توان به تمام اطلاعات کلاس SmtpConfig به شکل strongly typed دسترسی یافت.

اکنون اگر برنامه را جرا کنید، این خروجی را می‌توان مشاهده کرد (که در آن آدرس Server دریافت شده‌ی از فایل کانفیگ نیز مشخص است):


البته همانطور که در قسمت قبل نیز عنوان شد، این تزریق وابستگی‌ها در تمام قسمت‌های برنامه کار می‌کند. برای مثال در کنترلرها هم می‌توان <IOptions<SmtpConfig را به همین نحو تزریق کرد.


نحوه‌ی واکنش به تغییرات فایل‌های کانفیگ

در نگارش‌های قبلی ASP.NET، هر تغییری در فایل web.config، سبب ری‌استارت شدن کل برنامه می‌شد که این مساله نیز خود سبب بروز مشکلات زیادی مانند از دست رفتن سشن تمام کاربران می‌شد.
در ASP.NET Core، برنامه‌ی وب ما دیگر متکی به فایل web.config نبوده و همچنین می‌توان چندین و چند نوع فایل config داشت. به علاوه در اینجا متدهای مرتبط معرفی فایل‌های کانفیگ دارای پارامتر مخصوص reloadOnChange نیز هستند:
 .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
این پارامتر در صورت true بودن، به صورت خودکار سبب بارگذاری مجدد اطلاعات فایل کانفیگ می‌شود (بدون ری‌استارت کل برنامه).
نظرات مطالب
مدیریت سراسری خطاها در یک برنامه‌ی Angular

خطا به صورت رشته ارسال نمیشه . نوع error رو ببینید


 و نحوه فراخوانی به صورت زیر است 

در کامپوننت

 this.dynamicGridService.getMasters(queryModel).subscribe(res => {

      console.log(res);
      this.data = res;

    });

سرویس:

 return this.labApiService.get(`Master/GetMasterColumns`).pipe(
      map(response => response));

سرویس اصلی

 get(url: string): Observable<any> {

    return this.http
      .get<any>(url)
      .pipe(
      map(response => response || {}),
      catchError(this.handleError));
    
  }
 public handleError(error: HttpErrorResponse): Observable<any> {
    console.error("observable error: ", error);
    return observableThrowError(error);
  }

اما باز هم متن اصلی خطارو نشون نمیده 

مطالب
معرفی System.Text.Json در NET Core 3.0.
معروفترین کتابخانه‌ی کار با JSON در دات نت، Json.NET است که این روزها، جزء جدایی ناپذیر حداقل، تمام برنامه‌های وب مبتنی بر دات نت می‌باشد. برای مثال ASP.NET Core 2x/1x و همچنین ASP.NET Web API پیش از NET Core.، به صورت پیش‌فرض از این کتابخانه برای کار با JSON استفاده می‌کنند. این کتابخانه 10 سال پیش ایجاد شد و در طول زمان، قابلیت‌های زیادی به آن اضافه شده‌است. همین حجم بالای کار صورت گرفته سبب شده‌است که برای مثال شروع به استفاده‌ی از <Span<T در آن برای بالابردن کارآیی، بسیار مشکل شده و نیاز به تغییرات اساسی در آن داشته باشد. به همین جهت خود تیم CoreFX دات نت Core گزینه‌ی دیگری را برای کار با JSON در فضای نام جدید System.Text.Json ارائه داده‌است که برای کار با آن نیاز به نصب وابستگی ثالثی نیست و همچنین کارآیی آن به علت استفاده‌ی از ویژگی‌های جدید زبان، مانند ref struct و Span، به طور میانگین دو برابر کتابخانه‌ی Json.NET است. برای مثال استفاده‌ی از string (حالت پیش‌فرض کتابخانه‌ی Json.NET) یعنی کار با رشته‌هایی از نوع UTF-16؛ اما کار با Span، امکان دسترسی مستقیم به رشته‌هایی از نوع UTF-8 را میسر می‌کند که نیازی به تبدیل به رشته‌هایی از نوع UTF-16 را ندارند.


ASP.NET Core 3x دیگر به صورت پیش‌فرض به همراه Json.NET ارائه نمی‌شود

در برنامه‌های ASP.NET Core 3x، وابستگی ثالث Json.NET حذف شده‌است و از این پس هر نوع خروجی JSON آن، مانند بازگشت مقادیر مختلف از اکشن متدهای کنترلرها، به صورت خودکار در پشت صحنه از امکانات ارائه شده‌ی در System.Text.Json استفاده می‌کند و دیگر Json.NET، کتابخانه‌ی پیش‌فرض کار با JSON آن نیست. بنابراین برای کار با آن نیاز به تنظیم خاصی نیست. همینقدر که یک پروژه‌ی جدید ASP.NET Core 3x را ایجاد کنید، یعنی در حال استفاده‌ی از System.Text.Json هستید.


روش بازگشت به Json.NET در ASP.NET Core 3x

اگر به هر دلیلی هنوز نیاز به استفاده‌ی از کتابخانه‌ی Json.NET را دارید، آداپتور ویژه‌ی آن نیز تدارک دیده شده‌است. برای اینکار:
الف) ابتدا باید بسته‌ی نیوگت Microsoft.AspNetCore.Mvc.NewtonsoftJson را نصب کنید.
ب) سپس در کلاس Startup، باید این کتابخانه را به صورت یک سرویس جدید، با فراخوانی متد AddNewtonsoftJson، معرفی کرد:
 public void ConfigureServices(IServiceCollection services)
 {
     services.AddControllers()
            .AddNewtonsoftJson()
     // ...
}
یکی از دلایل بازگشت به Json.NET می‌تواند عدم پشتیبانی از OpenAPI / Swagger در حین کار با System.Text.Json باشد و این مورد قرار نیست در نگارش نهایی 3.0، حضور داشته باشد و انطباق با آن به نگارش‌های بعدی موکول شده‌است.


روش کار مستقیم با System.Text.Json

اگر در قسمتی از برنامه‌ی خود نیاز به کار مستقیم با اشیاء JSON را داشته باشید و یا حتی بخواهید از این قابلیت در برنامه‌های کنسول و یا کتابخانه‌ها نیز استفاده کنید، روش انتقال کدهایی که از Json.NET استفاده می‌کنند به System.Text.Json، به صورت زیر است:
public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public DateTime? BirthDay { get; set; }
}
تبدیل رشته‌ی JSON حاوی اطلاعات شخص، به شیء متناظر با آن و یا حالت عکس آن:
using System;
using System.Text.Json.Serialization;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Person person = JsonSerializer.Parse<Person>(...);
            string json = JsonSerializer.ToString(person);
        }
    }
}
در اینجا از کلاس System.Text.Json.Serialization.JsonSerializer، روش کار با دو متد Parse را برای Deserialization و ToString را برای Serialization مشاهده می‌کنید.
کلاس JsonSerializer دارای overloadهای زیر برای کار با متدهای Parse و ToString است:
namespace System.Text.Json.Serialization
{
    public static class JsonSerializer
    {
        public static object Parse(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null);
        public static object Parse(string json, Type returnType, JsonSerializerOptions options = null);
        public static TValue Parse<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null);
        public static TValue Parse<TValue>(string json, JsonSerializerOptions options = null);

        public static string ToString(object value, Type type, JsonSerializerOptions options = null);
        public static string ToString<TValue>(TValue value, JsonSerializerOptions options = null);
    }
}
یک نکته: کارآیی متد Parse با امضای ReadOnlySpan<byte> utf8Json، بیشتر است از نمونه‌ای که string json را می‌پذیرد. از این جهت که چون با آرایه‌ای از بایت‌های رشته‌ای از نوع UTF-8 کار می‌کند، نیاز به تبدیل به UTF-16 را مانند متدی که string را می‌پذیرد، ندارد. برای تولید آرایه‌ی بایت‌های utf8Json از روی یک شیء، می‌توانید از متد JsonSerializer.ToUtf8Bytes استفاده کنید و یا برای تولید آن از روی یک رشته، از متد Encoding.UTF8.GetBytes استفاده کنید.


سفارشی سازی JsonSerializer جدید

اگر به امضای متدهای Parse و ToString کلاس JsonSerializer دقت کنید، دارای یک پارامتر اختیاری از نوع JsonSerializerOptions نیز هستند که به صورت زیر تعریف شده‌است:
public sealed class JsonSerializerOptions
{
   public bool AllowTrailingCommas { get; set; }
   public int DefaultBufferSize { get; set; }
   public JsonNamingPolicy DictionaryKeyPolicy { get; set; }
   public bool IgnoreNullValues { get; set; }
   public bool IgnoreReadOnlyProperties { get; set; }
   public int MaxDepth { get; set; }
   public bool PropertyNameCaseInsensitive { get; set; }
   public JsonNamingPolicy PropertyNamingPolicy { get; set; }
   public JsonCommentHandling ReadCommentHandling { get; set; }
   public bool WriteIndented { get; set; }
}
برای نمونه معادل تنظیم NullValueHandling در Json.NET:
// Json.NET:
var settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore
};
string json = JsonConvert.SerializeObject(person, settings);
اینبار توسط خاصیت IgnoreNullValues صورت می‌گیرد:
// JsonSerializer:
var options = new JsonSerializerOptions
{
    IgnoreNullValues = true
};
string json = JsonSerializer.ToString(person, options);

در برنامه‌های ASP.NET Core که این نوع متدها در پشت صحنه فراخوانی می‌شوند، روش تنظیم JsonSerializerOptions به صورت زیر است:
services.AddControllers()
   .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);


نگاشت نام ویژه‌ی خواص در حین عملیات deserialization

در مثال فوق، فرض شده‌است که نام خاصیت BirthDay، دقیقا با اطلاعاتی که از رشته‌ی JSON دریافتی پردازش می‌شود، تطابق دارد. اگر این نام در اطلاعات دریافتی متفاوت است، می‌توان از ویژگی JsonPropertyName برای تعریف این نگاشت استفاده کرد:
[JsonPropertyName("birthdate")]
public DateTime? BirthDay { get; set; }
روش دیگر اینکار، برای نمونه تنظیم PropertyNamingPolicy به حالت CamelCase است:
var options = new JsonSerializerOptions
{
   PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string json = JsonSerializer.ToString(person, options);
و یا اگر می‌خواهید حساسیت به بزرگی و کوچکی حروف را ندید بگیرید (مانند حالت پیش‌فرض JSON.NET) از تنظیم JsonSerializerOptions.PropertyNameCaseInsensitive استفاده کنید.

در این بین اگر نمی‌خواهید خاصیتی در عملیات serialization و یا برعکس آن پردازش شود، می‌توان از تعریف ویژگی [JsonIgnore] بر روی آن استفاده کرد.
مطالب
یافتن اکشن متد‌های به اشتباه کش شده در ASP.NET MVC
مرسوم است برای کش کردن خروجی یک اکشن متد در ASP.NET MVC از ویژگی OutputCache استفاده شود. نکته‌ی مهمی که در مورد نحوه پیاده سازی آن وجود دارد، استفاده از OutputCacheModule استاندارد ASP.NET است. در این حالت پس از فراخوانی ابتدایی اکشن متد و کش شدن محتوای حاصل از آن، در دفعه‌ی بعد فراخوانی این آدرس خاص، اصلا چرخه کاری یک کنترلر روی نداده و تمام مسایل توسط OutputCacheModule به صورت مستقل و پیش از رسیدن آن به کنترلر، مدیریت می‌شوند.
خوب، تا اینجا ممکن است مشکلی به نظر نرسد و هدف از کش کردن اطلاعات یک اکشن متد نیز همین مورد است. اما اگر این اکشن متد کش شده، به اشتباه در یک کنترلر مزین شده با ویژگی Authorize قرار گیرد، چه خواهد شد؟ مثلا این کنترلر امن، برای ارائه فایل‌ها یا حتی نمایش قسمتی از صفحه یا کل صفحه، از کش استفاده کرده است. در بار اول دریافت فایل، بدیهی است که تمام مسایل اعتبارسنجی باید مطابق طول عمر یک کنترلر روی دهند. اما در بار دوم فراخوانی آدرس دریافت صفحه یا فایل، اصلا کار به فراخوانی کنترلر نمی‌رسد. به عبارتی کلیه کاربران سایت (اعم از لاگین شده، نشده، دارای دسترسی مشاهده صفحه یا آدرس امن و یا بدون دسترسی)، به این محتوای خاص بدون مشکلی دسترسی خواهند داشت (فقط کافی است که از آدرس نهایی به نحوی مطلع شوند).

سؤال: چگونه می‌توان کلیه اکشن متدهای یک پروژه ASP.NET MVC را که دارای ویژگی OutputCache در یک کنترلر امن هستند، یافت؟

using System;
using System.Linq;
using System.Reflection;
// Add a ref. to \Program Files\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Web.Mvc.dll
using System.Web.Mvc;
// Add a ref. to System.Web
using System.Web.UI;

namespace FindOutputCaches
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"D:\site\bin\Web.dll";
            var asmTarget = Assembly.LoadFrom(path);

            checkSecuredControllers(asmTarget);

            Console.WriteLine("Press a key...");
            Console.Read();
        }

        private static void checkSecuredControllers(Assembly asmTarget)
        {
            // یافتن کلیه کنترلرهایی که فیلتر اوتورایز دارند
            var securedControllers = asmTarget.GetTypes()
                                              .Where(type => typeof(IController).IsAssignableFrom(type) &&
                                                             Attribute.IsDefined(type, typeof(AuthorizeAttribute)) &&
                                                             !type.Name.StartsWith("T4MVC"))
                                              .ToList();

            foreach (var controller in securedControllers)
            {
                // یافتن کلیه اکشن متدهای کنترلر جاری
                var actionMethods = controller.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                                              .Where(method => typeof(ActionResult).IsAssignableFrom(method.ReturnType))
                                              .ToList();

                foreach (var method in actionMethods)
                {
                    // یافتن متدهایی که دارای آوت پوت کش هستند
                    var attributes = method.GetCustomAttributes(typeof(OutputCacheAttribute), true);
                    if (attributes == null || !attributes.Any())
                        continue;

                    var outputCache = (OutputCacheAttribute)attributes[0]; // AllowMultiple = false
                    if (outputCache.Location == OutputCacheLocation.None)
                        continue; //سبب عدم کش شدن شده است؛ مثلا برای کارهای ای‌جکسی

                    Console.WriteLine("Detected incorrect usage of OutputCache in:\n {0}-->{1}",
                                           controller.FullName, method.Name);
                }
            }
        }
    }
}
کدهای کامل این بررسی را در اینجا ملاحظه می‌کنید.
ابتدا مسیر اسمبلی کامپایل شده پروژه ASP.NET MVC که حاوی کنترلرهای برنامه است، باید مشخص گردد.
سپس در این اسمبلی کلیه نوع‌های تعریف شده، یافت گردیده و آن‌هایی که پیاده سازی کننده IController هستند (یعنی کلاس‌های کنترلر واقعی برنامه) و همچنین دارای ویژگی AuthorizeAttribute نیز می‌باشند، جدا خواهند شد.
در ادامه، در هر کنترلر امن یافت شده، متدهایی را بررسی خواهیم کرد که دارای خروجی از نوع ActionResult باشند (فقط اکشن متدها مدنظر هستند). اگر این اکشن متد یافت شده دارای ویژگی OutputCacheAttribute بود و همچنین Location آن به None تنظیم نشده بود ... یعنی مشکل امنیتی وجود دارد که باید برطرف شود.

البته برای تکمیل این مطلب باید دو حالت زیر هم پیاده سازی و بررسی شوند:
- کلیه Viewهای برنامه بررسی شوند. اگر در View خاصی که متعلق است به یک کنترلر یا حتی اکشن متد امن، ارجاعی به اکشن متدی کش شده در کنترلری دیگر وجود داشت، این مورد هم یک باگ امنیتی است.
- کلیه کنترلرهای عمومی که دارای اکشن متدی امن هستند نیز باید جهت یافتن OutputCache بررسی شوند.