مطالب
پر کردن مقادیر 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 

مطالب
CheckBoxList برای فیلد Enum Flags مدل در ASP.Net MVC
قبلا مطالبی در سایت راجع به نوع داده شمارشی یا Enum و همچنین CheckBoxList و RadioButtonList وجود دارد. اما در این مطلب قصد دارم تا یک روش متفاوت را برای تولید و بهره گیری از CheckBoxList با استفاده از نوع داده‌های شمارشی برای شما ارائه کنم.
فرض کنید بخواهید به کاربر این امکان را بدهید تا بتواند چندین گزینه را برای یک فیلد انتخاب کند. به عنوان یک مثال ساده فرض کنید گزینه ای از مدل، پارچه‌های مورد علاقه یک نفر هست. کاربر می‌تواند چندین پارچه را انتخاب کند. و این فرض را هم بکنید که به لیست پارچه‌ها گزینه دیگری اضافه نخواهد شد. پارچه (Fabric) را مثلا می‌توانیم به صورت زیر تقسیم بندی کنیم :
  1. پنبه (Cotton)
  2. ابریشم (Silk)
  3. پشم (Wool)
  4. ابریشم مصنوعی (Rayon)
  5. پارچه‌های دیگر (Other)

با توجه به اینکه دیگر قرار نیست به این لیست گزینه دیگری اضافه شود می‌توانیم آنرا به صورت یک نوع داده شمارشی (Enum) تعریف کنیم. مثلا بدین صورت:

public enum Fabric
{
    [Description("پنبه")]
    Cotton,

    [Description("ابریشم")]
    Silk,

    [Description("پشم")]
    Wool,

    [Description("ابریشم مصنوعی")]
    Rayon,

    [Description("پارچه‌های دیگر")]
    Other
}

حال فرض کنید View Model زیر فیلدی از نوع نوع داده شمارشی Fabric دارد:
public class MyViewModel
{
    public Fabric Fabric { get; set; }
}

توجه داشته باشید که فیلد Fabric از کلاس MyViewModel باید چند مقدار را در خود نگهداری کند. یعنی می‌تواند هر کدام از گزینه‌های Cotton، Silk، Wool، Rayon، Other به صورت جداگانه یا ترکیبی باشد. اما در حال حاضر با توجه به اینکه یک فیلد Enum معمولی فقط می‌تواند یک مقدار را در خودش ذخیره کند قابلیت ذخیره ترکیبی مقادیر در فیلد Fabric از View Model بالا وجود ندارد.

اما راه حل این مشکل استفاده از پرچم (Flags) در تعریف نوع داده شمارشی هست. با استفاده از پرچم نوع داده شمارشی بالا به صورت زیر باید تعریف شود:
[Flags]
public enum Fabric
{
    [Description("پنبه")]
    Cotton = 1,

    [Description("ابریشم")]
    Silk = 2,

    [Description("پشم")]
    Wool = 4,

    [Description("ابریشم مصنوعی")]
    Rayon = 8,

    [Description("پارچه‌های دیگر")]
    Other = 128
}
همان طور که می‌بینید از عبارت [Flags] قبل از تعریف enum استفاده کرده ایم. همچنین هر کدام از مقادیر ممکن این نوع داده شمارشی با توانهایی از 2 تنظیم شده اند. در این صورت یک نمونه از این نوع داده می‌تواند چندین مقدار را در خودش ذخیره کند.

برای آشنایی بیشتر با این موضوع به کدهای زیر نگاه کنید:
Fabric cotWool = Fabric.Cotton | Fabric.Wool;
int cotWoolValue = (int) cotWool;
به وسیله عملگر | می‌توان چندین مقدار را در یک نمونه از نوع Fabric ذخیره کرد. مثلا متغیر cotWool هم دارای مقدار Fabric.Cotton و هم دارای مقدار Fabric.Wool هست. مقدار عددی معادل متغیر cotWool برابر 5 هست که از جمع مقدار عددی Fabric.Cotton و Fabric.Wool به دست آمده است.

حال فرض کنید فیلد Fabric از View Model ذکر شده (کلاس MyViewModel) را به صورت لیستی از چک باکس‌ها نمایش دهیم. مثل زیر:

شکل (الف)
سپس بخواهیم تا کاربر بعد از انتخاب گزینه‌های مورد نظرش از لیست بالا و پست کردن فرم مورد نظر، بایندر وارد عمل شده و فیلد Fabric را بر اساس گزینه هایی که کاربر انتخاب کرده مقداردهی کند.

برای این کار از پروژه MVC Enum Flags کمک خواهیم گرفت. این پروژه شامل یک Html Helper برای تبدیل یه Enum به یک CheckBoxList و همچنین شامل Model Binder مربوطه هست. البته بعضی از کدهای Html Helper آن احتیاج به تغییر داشت که آنرا انجام دادم ولی بایندر آن بسیار خوب کار می‌کند.

خوب html helper مربوط به آن به صورت زیر می‌باشد:
public static IHtmlString CheckBoxesForEnumFlagsFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
    ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    Type enumModelType = metadata.ModelType;

    // Check to make sure this is an enum.
    if (!enumModelType.IsEnum)
    {
        throw new ArgumentException("This helper can only be used with enums. Type used was: " + enumModelType.FullName.ToString() + ".");
    }

    // Create string for Element.
    var sb = new StringBuilder();

    foreach (Enum item in Enum.GetValues(enumModelType))
    {
        if (Convert.ToInt32(item) != 0)
        {
            var ti = htmlHelper.ViewData.TemplateInfo;
            var id = ti.GetFullHtmlFieldId(item.ToString());

            //Derive property name for checkbox name
            var body = expression.Body as MemberExpression;
            var propertyName = body.Member.Name;
            var name = ti.GetFullHtmlFieldName(propertyName);
                    
            //Get currently select values from the ViewData model
            TEnum selectedValues = expression.Compile().Invoke(htmlHelper.ViewData.Model);

            var label = new TagBuilder("label");
            label.Attributes["for"] = id;
            label.Attributes["style"] = "display: inline-block;";
            var field = item.GetType().GetField(item.ToString());

            // Add checkbox.
            var checkbox = new TagBuilder("input");
            checkbox.Attributes["id"] = id;
            checkbox.Attributes["name"] = name;
            checkbox.Attributes["type"] = "checkbox";
            checkbox.Attributes["value"] = item.ToString();
                    
            if ((selectedValues as Enum != null) && ((selectedValues as Enum).HasFlag(item)))
            {
                checkbox.Attributes["checked"] = "checked";
            }
            sb.AppendLine(checkbox.ToString());

            // Check to see if DisplayName attribute has been set for item.
            var displayName = field.GetCustomAttributes(typeof(DisplayNameAttribute), true)
                .FirstOrDefault() as DisplayNameAttribute;
            if (displayName != null)
            {
                // Display name specified.  Use it.
                label.SetInnerText(displayName.DisplayName);
            }
            else
            {
                // Check to see if Display attribute has been set for item.
                var display = field.GetCustomAttributes(typeof(DisplayAttribute), true)
                    .FirstOrDefault() as DisplayAttribute;
                if (display != null)
                {
                    label.SetInnerText(display.Name);
                }
                else
                {
                    label.SetInnerText(item.ToDescription());
                }
            }
            sb.AppendLine(label.ToString());

            // Add line break.
            sb.AppendLine("<br />");
        }
    }

    return new HtmlString(sb.ToString());
}

در کدهای بالا از متد الحاقی ToDescription نیز برای تبدیل معادل انگلیسی به فارسی یک مقدار از نوع داده شمارشی استفاده کرده ایم.
public static string ToDescription(this Enum value)
{
    var attributes = (DescriptionAttribute[])value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
    return attributes.Length > 0 ? attributes[0].Description : value.ToString();
}

برای استفاده از این Html Helper در View کد زیر را می‌نویسیم:
@Html.CheckBoxesForEnumFlagsFor(x => x.Fabric)

که باعث تولید خروجی که در تصویر (الف) نشان داده شد می‌شود. و همچنین مدل بایندر مربوط به آن به صورت زیر هست:
public class FlagEnumerationModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (bindingContext == null) throw new ArgumentNullException("bindingContext");

        if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
        {
            var values = GetValue<string[]>(bindingContext, bindingContext.ModelName);

            if (values.Length > 1 && (bindingContext.ModelType.IsEnum && bindingContext.ModelType.IsDefined(typeof(FlagsAttribute), false)))
            {
                long byteValue = 0;
                foreach (var value in values.Where(v => Enum.IsDefined(bindingContext.ModelType, v)))
                {
                    byteValue |= (int)Enum.Parse(bindingContext.ModelType, value);
                }

                return Enum.Parse(bindingContext.ModelType, byteValue.ToString());
            }
            else
            {
                return base.BindModel(controllerContext, bindingContext);
            }
        }

        return base.BindModel(controllerContext, bindingContext);
    }

    private static T GetValue<T>(ModelBindingContext bindingContext, string key)
    {
        if (bindingContext.ValueProvider.ContainsPrefix(key))
        {
            ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(key);
            if (valueResult != null)
            {
                bindingContext.ModelState.SetModelValue(key, valueResult);
                return (T)valueResult.ConvertTo(typeof(T));
            }
        }
        return default(T);
    }
}

این مدل بایندر را باید به این صورت در متد Application_Start فایل Global.asax فراخوانی کنیم:
ModelBinders.Binders.Add(typeof(Fabric), new FlagEnumerationModelBinder());

مشاهده می‌کنید که در اینجا دقیقا مشخص کرده ایم که این مدل بایندر برای نوع داده شمارشی Fabric هست. اگر نیاز دارید تا این بایندر برای نوع داده‌های شمارشی دیگری نیز به کار رود نیاز هست تا این خط کد را برای هر کدام از آنها تکرار کنید. اما راه حل بهتر این هست که کلاسی به صورت زیر تعریف کنیم و تمامی نوع داده‌های شمارشی که باید از بایندر بالا استفاده کنند را در یک پراپرتی آن برگشت دهیم. مثلا بدین صورت:
public class ModelEnums
{
    public static IEnumerable<Type> Types
    {
        get
        {
            var types = new List<Type> { typeof(Fabric) };
            return types;
        }
    }
}

سپس به متد Application_Start رفته و کد زیر را اضافه می‌کنیم:
foreach (var type in ModelEnums.Types)
{
   ModelBinders.Binders.Add(type, new FlagEnumerationModelBinder())
}

اگر گزینه‌های پشم و ابریشم مصنوعی را از CheckBoxList تولید شده انتخاب کنیم، بدین صورت:

شکل (ب)


و سپس فرم را پست کنید، موردی شبیه زیر مشاهده می‌کنید:

شکل (ج)

همچنین مقدار عددی معادل در این جا برابر 12 می‌باشد که از جمع دو مقدار Wool و Rayon به دست آمده است. بدین ترتیب در یک فیلد از مدل، گزینه‌های انتخابی توسط کاربر قرار گرفته شده اند.

پروژه مربوط به این مثال را از لینک زیر دریافت کنید:
MvcEnumFlagsProjectSample.zip

پی نوشت : پوشه‌های bin و obj و packages جهت کاهش حجم پروژه از آن حذف شده اند. برای بازسازی پوشه packages لطفا به مطلب بازسازی کامل پوشه packages بسته‌های NuGet به صورت خودکار مراجعه کنید.
مطالب
مروری کوتاه بر کارکرد Ocelot

با پیشرفت بیشتر تکنولوژی وب در سال‌های اخیر و رشد کاربران فضای اینترنتی، خدمات و پیچیدگی‌های بیشتری به نرم افزارها اضافه شده و به همین دلیل استفاده از میکروسرویس‌ها بجای حالت قدیمی مونولوتیک (یک برنامه همه کاره) طرفداران بیشتری پیدا کرد‌ه‌است. در این حالت برنامه به قسمت‌های خرد و مجزایی تبدیل شده و هر پروژه ساختار و تکنولوژی مخصوص به خود را مدیریت میکند و در این بین با استفاده روش‌های متفاوتی به ایجاد ارتباط با یکدیگر میپردازند .  

مشکلی که در این حالت میتواند رخ دهد، زیاد شدن مسیرهای متفاوت برای اتصال به هر یک از سرویس‌ها و سخت‌تر شدن به روزرسانی این مسیرها می‌باشد. به همین دلیل در این بخش، نیاز به ابزاری میباشد تا بتوان از طریق آن، مسیردهی ساده‌ای را ایجاد کرد و در پشت صحنه  مسیردهی‌های متفاوتی را کنترل نمود. با ایجاد چنین ابزاری در واقع شما   API Gateway ایجاد نموده‌اید. یکی از معروفترین کتابخانه‌های این حوزه، Ocelot میباشد. کار با این ابزار بسیار ساده بوده و امکانات بسیار زیاد و قدرتمندی را فراهم مینماید.

برای اینکار ابتدا سه پروژه را می‌سازیم که موارد زیر را شامل می‌گردد:

پروژه اول نوع Api : با دریافت Id در اکشن‌متد مورد نظر، شیء user بازگردانده میشود:

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserName { get; set; }


    public static List<User> GetUsers()
    {
        return new List<User>()
        {
            new()
            {
                Id = 1,
                FirstName = "علی",
                LastName = "یگانه مقدم",
                UserName = "yeganehaym"
            },
            new ()
            {
                Id = 2,
                FirstName = "وحید",
                LastName = "نصیری",
                UserName = "VahidN"
            },
        };
    }
}
[ApiController]
[Route("/api/[controller]/{id?}")]
public class UserController : ControllerBase
{

    [HttpGet]
    public User GetUser(int id)
    {
        var users = Users.User.GetUsers();
        var user = users.FirstOrDefault(x => x.Id == id);
        return user;
    }
}

 

پروژه دوم نوع Api : دریافت لیستی از محصولات:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
    public  int Quantity { get; set; }


    public static List<Product> GetProducts()
    {
        return new List<Product>()
        {
            new()
            {
                Id = 1,
                Name = "LCD",
                Price = 20000,
                Quantity = 10
            },
            new()
            {
                Id = 1,
                Name = "Mouse",
                Price = 320000,
                Quantity = 15
            },
            new()
            {
                Id = 1,
                Name = "Keyboard",
                Price = 50000,
                Quantity = 25
            },
        };
    }
}
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{

    [HttpGet]
    public List<Product> GetProducts()
    {
        return Product.GetProducts();
    }

}


پروژه سوم همان ApiGateway هست و همین‌که یک پروژه‌ی وب خالی باشد، کفایت میکند. در این پروژه   Ocelot  را نصب نموده  و سپس فایلی با نام  ocelot.json را با محتوای زیر به ریشه‌ی پروژه همانند فایل‌های appsettings.json اضافه میکنیم:

{
    "Routes":[
        
        {
        "DownstreamPathTemplate":"/api/User/{id}",
        "DownstreamScheme":"https",
        "DownstreamHostAndPorts":[
            {
                "Host":"localhost",
                "Port":"7279"
            }
        ],
        "UpstreamPathTemplate":"/GetUser/{id}",
        "UpstreamHttpMethod":[
            "GET"
        ]},
        {
        "DownstreamPathTemplate":"/api/Product",
        "DownstreamScheme":"https",
        "DownstreamHostAndPorts":[
            {
                "Host":"localhost",
                "Port":"7261"
            }
        ],
     
        "UpstreamPathTemplate":"/Products",
        "UpstreamHttpMethod":[
            "GET"
        ]
        }
    ]
    
   
}

این فایل‌ها شامل دو قسمتUpStream و DownStream میشوند. آپ‌استریم‌ها در واقع آدرسی است که شما قصد اتصال به آن‌را دارید و قسمت داون‌استریم، سرویس مقصدی است که ocelot باید درخواست شما را به سمت آن ارسال نماید. به‌عنوان مثل شما با ارسال درخواستی به آدرس Products ، در پشت صحنه به آدرس localhost:7261/api/product ارسال میگردد. بدین صورت سیستم نهایی تنها به یک دامنه و آدرس منسجم ارسال شده، ولی در پشت صحنه این آدرس‌ها ممکن است به تعداد زیادی سرویس در آدرس‌های متفاوتی ارسال گردند.

جهت راه اندازی نهایی، کد زیر را به فایل Program.cs اضافه میکنیم:

builder.Services.AddOcelot();
app.UseOcelot();


پس از اضافه کردن پیکربندی و middleware آن، کد زیر را نیز جهت شناسایی فایل ocelot به فایل Program.cs نیز اضافه مینماییم:

builder.Configuration.SetBasePath(builder.Environment.ContentRootPath)    
    .AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);

همچنین در صورت تمایل میتوانید کد را به شکل زیر هم نوشته تا بتوانید تنظیمات متفاوتی را برای محیط اجرایی متفاوتی ایجاد نمایید:

builder.Configuration.SetBasePath(builder.Environment.ContentRootPath)    
    .AddJsonFile("ocelot.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"ocelot.{builder.Environment.EnvironmentName}.json", optional: false, reloadOnChange: true);

هر سه برنامه را با هم اجرا نمایید و با استفاده از برنامه‌ی PostMan درخواستی را برای هر یک از موارد مورد نظر /Products و /GetUser/{1,2} به سمت پروژه ApiGateway ارسال نمایید.

Ocelot موارد دیگری از قبیل تنظیم Load Balancer بین سرویس ها، اتصال به سرویس‌های Service Discoveryچون Consul   یا  یوریکا  و کش کردن و ... را نیز فراهم می‌نماید.


عملیات کشینگ

جهت بحث کشینگ، ابتدا بسته زیر را اضافه نمایید:

Install-Package Ocelot.Cache.CacheManager

سپس پیکربندی ابتدایی را به شکل زیر تغییر دهید:

builder.Services.AddOcelot()
    .AddCacheManager(x => x.WithDictionaryHandle());

در ادامه در فایل Ocelot جیسون، برای هر بخشی که مدنظر شماست تا کشی را انجام دهد، کد زیر اضافه نمایید:

"FileCacheOptions":{
      "TtlSeconds":30,
       "Region":"custom"
}

TtlSeconds : مدت زمان کش به ثانیه

Region : یک عبارت رشته‌ای همانند یک عنوان یا نام که بعدا میتوانید از طریق api ‌ها به آن متصل شوید و عملیاتی چون خالی کردن کش را صادر نمایید.

حال برای بخش محصولات این تنظیمات ذکر میگردد:

{
    "Routes":[
        
        {
        "DownstreamPathTemplate":"/api/User/{id}",
        "DownstreamScheme":"https",
        "DownstreamHostAndPorts":[
            {
                "Host":"localhost",
                "Port":"7279"
            }
        ],
        "UpstreamPathTemplate":"/GetUser/{id}",
        "UpstreamHttpMethod":[
            "GET"
        ]
        },
        {
        "DownstreamPathTemplate":"/api/Product",
        "DownstreamScheme":"https",
        "DownstreamHostAndPorts":[
            {
                "Host":"localhost",
                "Port":"7261"
            }
        ],
     
        "UpstreamPathTemplate":"/Products",
        "UpstreamHttpMethod":[
            "GET"
        ],
            "FileCacheOptions":{
                "TtlSeconds":30,
                "Region":"custom"
            }
        }
    ]
    
   
}

 برای اینکه متوجه عملکرد آن شوید یک نقطه توقف را در اکشن دریافت محصول قرار دهید و سپس برنامه را در حالت دیباگ اجرا نمایید. در مرتبه اول باید نقطه توقف بتواند اجرای کد را به شما نمایش دهد ولی تا 30 ثانیه آینده هر چقدر از طریق Postman درخواستی را ارسال نمایید نقطه توقف اجرا نخواهد گردید، ولی نتیجه‌ی قبل برای شما ارسال خواهد شد.

این مورد را برای بخش کاربران هم انجام دهید و می‌بینید که برای هر userId و هر شکل  Url، یک پاسخ منحصر به فرد، دریافت و کش خواهد شد.


جلوگیری از درخواست‌های بیش از حد

یکی دیگر از ویژگی‌های Ocelot، جلوگیری از درخواست بیش از حد میباشد. به همین علت ابتدا کد زیر را به هر درخواستی که مدنظر شماست اضافه نمایید:

       "RateLimitOptions":{
                "ClientWhitelist":[
                ],
                "EnableRateLimiting":true,
                "Period":"5s",
                "PeriodTimespan":1,
                "Limit":1,
                "HttpStatusCode":429
            }


WhiteClients : برای مشخص کردن کلاینت‌هایی که نباید اعمال محدودیت روی آن‌ها صورت بگیرد.

EnableRateLimiting   : این مورد باعث فعالسازی آن میگردد.

Period: مدت زمانیکه حداکثر تعداد درخواست باید در آن بازه صورت بگیرد. به ترتیب برای ثانیه، دقیقه، ساعت و روز حروف s - m - h و d استفاده میگردد.

PeriodTimespan: بعد از محدود شدن، بعد از چه مدتی دوباره بتواند درخواستی را ارسال نماید. در اینجا بعد از محدودیت ارسال درخواست، بعد از یک ثانیه مجدد اجازه ارسال درخواست باز میگردد.

Limit: در بازه زمانی مشخص شده چند درخواست مورد قبول واقع میشود و بعد از آن دیگر اجازه ارسال درخواست را نخواهد داشت.

HttpStatusCode: در صورت فیلتر شدن درخواست‌های رسیده، چه کد وضعیتی باید برگردانده شود که عدد 429 به معنای Too Many Request میباشد.

با تنظیمات بالا هر کلاینت میتواند در 5 ثانیه، نهایتا یک درخواست را ارسال نماید و با ارسال بقیه درخواست‌ها، Ocelot بجای هدایت درخواست به سرویس مربوطه، کد وضعیت 429 را باز میگرداند و یک ثانیه بعد از گذشت 5 ثانیه میتواند مجددا درخواست خود را ارسال نماید.

در نهایت به یک فایل مشابه زیر می‌رسیم:

{
    "Routes":[
        
        {
        "DownstreamPathTemplate":"/api/User/{id}",
        "DownstreamScheme":"https",
        "DownstreamHostAndPorts":[
            {
                "Host":"localhost",
                "Port":"7279"
            }
        ],
        "UpstreamPathTemplate":"/GetUser/{id}",
        "UpstreamHttpMethod":[
            "GET"
        ],
        "FileCacheOptions":{
            "TtlSeconds":30,
            "Region":"custom"
        }
        },
        {
        "DownstreamPathTemplate":"/api/Product",
        "DownstreamScheme":"https",
        "DownstreamHostAndPorts":[
            {
                "Host":"localhost",
                "Port":"7261"
            }
        ],
     
        "UpstreamPathTemplate":"/Products",
        "UpstreamHttpMethod":[
            "GET"
        ],
            "RateLimitOptions":{
                "ClientWhitelist":[
                ],
                "EnableRateLimiting":true,
                "Period":"5s",
                "PeriodTimespan":1,
                "Limit":1,
                "HttpStatusCode":429
            }
        }
    ],
    "DangerousAcceptAnyServerCertificateValidator": true
    
   
}

برای تست آن با استفاد از PostMan مرتبا به آدرس Products/ درخواست ارسال نمایید. 

فایل پروژه : Ocelot.zip

مطالب
ELMAH و حملات XSS
ASP.NET جهت مقابله با حملات XSS بطور پیش‌فرض از ورود تگ‌های HTML جلوگیری می‌کند و در صورتی که ورودی کاربر شامل این تگ‌ها باشد، HttpRequestValidationException صادر می‌گردد. لاگ کردن و بررسی این خطاها جهت آگاهی از وجود حمله بی اهمیت نیست. اما متأسفانه ELMAH که به عنوان معمول‌ترین ابزار ثبت خطاها کاربرد دارد این نوع Exceptionها را ثبت نمی‌کند. دلیل آن هم این است که ELMAH در رویه‌های درونی خود اقدام به خواندن ورودی‌های کاربر می‌کند و در این هنگام اگر ورودی کاربر نامعتبر باشد، Exception مذکور صادر می‌شود و فرصتی برای ادامه روند و ثبت خطا باقی نمی‌ماند. به هر حال ضروری است که این نقیصه را خودمان جبران کنیم. راه حل افزودن یک فیلتر سفارشی برای ثبت خطاها به شکل زیر است (ASP.NET MVC):
public class ElmahRequestValidationErrorFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context.Exception is HttpRequestValidationException)
           ErrorLog.GetDefault(HttpContext.Current).Log(new Error(context.Exception));
    }
}
فیلتر فوق باید در Global.asax معرفی شود:
public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahRequestValidationErrorFilter());
    filters.Add(new HandleErrorAttribute());
}
به این ترتیب HttpRequestValidationException هم بعد از این در سیستم ELMAH ثبت خواهد شد.
مطالب
آشنایی با Fluent Html Helpers در MVC
همان طور که قبلا نیز اشاره شد اینجا  به صورت خلاصه هدف FluentAPI  فراهم آوردن روشی است که بتوان متدها را زنجیر وار فراخوانی کرد و به این ترتیب خوانایی کد نوشته شده را بالا برد.
اما در این مقاله سعی شده تا کاربرد آن در یک برنامه MVC رو به صورت استفاده در helper‌ها شرح دهیم.  
در اینجا مثالی رو شرح میدهیم که در آن کنترل هایی از جنس input به صورت helper ساخته و برای فرستادن ویژگی‌های اچ تی ام ال ( HTML Attributes) آن از Fluent Html Helpers بهره میگیریم بدیهی است که از این Fluent  میتوان برای helperهای دیگر هم استفاده کرد.
در ابتدا یک نگاه کلی به کد ایجاد شده می‌اندازیم :
       @Html.RenderInput(
        Attributes.Configure()
            .AddType("text")
            .AddId("UserId")
            .AddName("UserId")
            .AddCssClass("TxtBoxCssClass")
    )
که باعث ساخته شدن یک کنترل تکست باکس با مشخصات زیر در صفحه می‌شود ، همان طور که مشاهده می‌کنیم تمام ویژگی‌ها برای کنترل ساخته شده اند.
<input class="TxtBoxCssClass" id="UserId" name="UserId" type="text">
در ادامه به سراغ پیاده سازی کلاس Attributes برای این مثال می‌رویم ، این کلاس باید از کلاس RouteValueDictionary ارث بری داشته باشد، این کلاس یک دیکشنری برای ما آماده می‌کند تا مقادیرمون رو در آن بریزیم در اینجا برای ویژگی‌ها و مقادیرشون. 
public class Attributes : RouteValueDictionary    {
        /// <summary>
        /// Configures this instance.
        /// </summary>
        /// <returns></returns>
        public static Attributes Configure()
        {
            return new Attributes();
        }

        /// <summary>
        /// Adds the type.
        /// </summary>
        /// <param name="value">The value.</param>
        public Attributes AddType(string value)
        {
            this.Add("type", value);
            return this;
        }

        /// <summary>
        /// Adds the name.
        /// </summary>
        /// <param name="value">The value.</param>
        public Attributes AddName(string value)
        {
            this.Add("name", value);
            return this;
        }

        /// <summary>
        /// Adds the id.
        /// </summary>
        /// <param name="value">The value.</param>
        public Attributes AddId(string value)
        {
            this.Add("id", value);
            return this;
        }

        /// <summary>
        /// Adds the value.
        /// </summary>
        /// <param name="value">The value.</param>
        public Attributes AddValue(string value)
        {
            this.Add("value", value);
            return this;
        }

        /// <summary>
        /// Adds the CSS class.
        /// </summary>
        /// <param name="value">The value.</param>
        public Attributes AddCssClass(string value)
        {
            this.Add("class", value);
            return this;
        }
    }
متد استاتیک Configure همیشه به عنوان شروع کننده fluent  و  از اون برای ساختن یک وهله جدید از کلاس Attributes  استفاده میشه و بقیه متدها هم کار اضافه کردن مقادیر رو مثل یک دیکشنری انجام میدهند.
حالا به سراغ پیاده سازی helper extension مون میرویم 
        public static MvcHtmlString RenderInput(this HtmlHelper htmlHelper, Attributes attributes)
        {
            TagBuilder input = new TagBuilder("input");
            input.MergeAttributes(attributes);
            return new MvcHtmlString(input.ToString(TagRenderMode.SelfClosing));
        }
با استفاده از کلاس tagbuilder تگ input را ساخته و ویژگی‌های فرستاده شده به helper رو با اون ادغام می‌کنیم (با استفاده از MergeAttributes )
این یک مثال ساده بود از این کار امیدوارم مفید واقع شده باشد
مثال پروژه
مطالب
C# 12.0 - Primary Constructors
قابلیتی تحت عنوان Primary Constructors به C# 12 اضافه شده‌است که ... البته جدید نیست! این قابلیت از زمان C# 9، با ارائه‌ی رکوردها، به زبان #C اضافه شد و در طی چند نگارش بعدی، توسعه و تکامل یافت (برای مثال اضافه شدن records for structs به C# 10) تا در C# 12، به کلاس‌های معمولی نیز تعمیم پیدا کرد. این ویژگی را در ادامه با جزئیات بیشتری بررسی می‌کنیم.


Primary Constructors چیست؟

Primary Constructors، قابلیتی است که به C# 12 اضافه شده‌است تا توسط آن بتوان خواص را مستقیما توسط پارامترهای سازنده‌ی یک کلاس تعریف و همچنین مقدار دهی کرد. هدف از آن، کاهش قابل ملاحظه‌ی یکسری کدهای تکراری و مشخص است تا به کلاس‌هایی زیباتر، کم‌حجم‌تر و خواناتر برسیم. برای مثال کلاس متداول زیر را درنظر بگیرید:
public class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime HireDate { get; set; }
    public decimal Salary { get; set; }

    public Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
    {
        FirstName = firstName;
        LastName = lastName;
        HireDate = hireDate;
        Salary = salary;
    }
}
در زبان ‍#C، سازنده، متد ویژه‌ای است که در حین ساخت نمونه‌ای از یک کلاس، فراخوانی می‌شود. هدف از آن‌، آغاز و مقدار دهی حالت شیء ایجاد شده‌است که عموما با مقدار دهی خواص آن شیء، انجام می‌شود.
اکنون اگر بخواهیم همین کلاس را با استفاده از ویژگی Primary Constructor اضافه شده به C# 12.0 بازنویسی کنیم، به قطعه کد زیر می‌رسیم:
public class Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
{
    public string FirstName { get; set; } = firstName;
    public string LastName { get; set; } = lastName;
    public DateTime HireDate { get; set; } = hireDate;
    public decimal Salary { get; set; } = salary;
}
و نحوه‌ی نمونه سازی از آن به صورت زیر است:
var employee = new Employee("John", "Doe", new DateTime(2020, 1, 1), 50000);

یک نکته: اگر از Rider و یا ReSharper استفاده می‌کنید، یک چنین Refactoring توکاری جهت سهولت کار، به آن‌ها اضافه شده‌است و به سرعت می‌توان این تبدیلات را توسط آن‌ها انجام داد.




توضیحات:
- متد سازنده در این حالت، به ظاهر حذف شده و به قسمت تعریف کلاس منتقل شده‌است.
- تمام مقدار دهی‌های آغازین موجود در متد سازنده‌ی پیشین نیز حذف شده‌اند و مستقیما به قسمت تعریف خواص، منتقل شده‌اند.
در نتیجه از یک کلاس 15 سطری، به کلاسی 7 سطری رسیده‌ایم که کاهش حجم قابل ملاحظه‌ای را پیدا کرده‌است.

نکته 1: هیچ ضرورتی وجود ندارد که به همراه یک primary constructor، خواصی هم مانند مثال فوق ارائه شوند؛ چون پارامترهای آن در تمام اعضای این کلاس، به همین شکل، قابل دسترسی هستند. در این مثال صرفا جهت بازسازی کد قبلی، این خواص اضافی را مشاهده می‌کنید. یعنی اگر تنها قرار بود، کار تزریق وابستگی‌ها صورت گیرد که عموما به همراه تعریف فیلدهایی جهت انتساب پارامترهای متد سازنده به آن‌ها است، استفاده از یک primary constructor، کدهای فوق را بیش از این هم فشرده‌تر می‌کرد و ... یک سطری می‌شد.

نکته 2: استفاده از پارامترهای سازنده‌ی اولیه، صرفا جهت مقدار دهی خواص عمومی یک کلاس، یک code smell هستند! چون می‌توان یک چنین کارهایی را به نحو شکیل‌تری توسط required properties معرفی شده‌ی در C# 11، پیاده سازی کرد.


بررسی تاریخچه‌ی primary constructors

همانطور که در مقدمه‌ی بحث نیز عنوان شد، primary constructors قابلیت جدیدی نیست و برای نمونه به همراه C# 9 و مفهوم جدید رکوردهای آن، ارائه شد:
public record class Book(string Title, string Publisher);
مثال فوق که به positional syntax هم معروف است، به همراه بکارگیری primary constructors است. در اینجا کامپایلر به صورت خودکار، کار تولید کدهای خواص متناظر را که از نوع get و init دار هستند، انجام می‌دهد. در این حالت به علت استفاده از init accessors، پس از نمونه سازی شیءای از آن، دیگر نمی‌توان مقدار خواص متناظر را تغییر داد.
پس از آن در C# 10، این توسعه ادامه یافت و به امکان تعریف record structها، بسط یافت که در اینجا هم قابلیت تعریف primary constructors وجود دارد:
public record struct Color(int R, int G, int B);
که البته در این حالت برخلاف record classها، کامپایلر، کدی را که برای خواص تولید می‌کند، get و set دار است. در اینجا اگر نیاز است به همان حالت خواص get و init دار رسید، می‌توان یک readonly record struct را تعریف کرد.

پس از این مقدمات، اکنون در C# 12 نیز می‌توان primary constructors را به تمام کلاس‌ها و structهای معمولی هم اعمال کرد؛ با این تفاوت که در اینجا برخلاف رکوردها، کدهای خواص‌های متناظر، به صورت خودکار تولید نمی‌شوند و اگر به آن‌ها نیاز دارید، باید آن‌ها را همانند مثال ابتدای بحث، خودتان به صورت دستی تعریف کنید.


primary constructors کلاس‌ها و structهای معمولی، با primary constructors رکوردها یکی نیست

در C# 12 و به همراه معرفی primary constructors مخصوص کلاس‌ها و structهای معمولی آن، از روش متفاوتی برای دسترسی به پارامترهای تعریف شده، استفاده می‌کند که به آن capturing semantics هم می‌گویند. در این حالت پارامترهای تعریف شده‌ی در یک primary constructor، توسط هر عضوی از آن کلاس قابل استفاده‌است که یکی از کاربردهای آن، ساده کردن تعاریف تزریق وابستگی‌ها است. در این حالت دیگر نیازی نیست تا ابتدا یک فیلد را برای انتساب به پارامتر تزریق شده تعریف کرد و سپس از آن فیلد، استفاده نمود؛ مستقیما می‌توان با همان پارامتر تعریف شده، در متدها و اعضای کلاس، کار کرد.
برای مثال سرویس زیر را که از تزریق وابستگی‌ها، در سازنده‌ی خود استفاده می‌کند، درنظر بگیرید:
public class MyService
{
    private readonly IDepedent _dependent;
  
    public MyService(IDependent dependent)
    {
        _dependent = dependent;
    }
  
    public void Do() 
    {
        _dependent.DoWork();
    }
}
این کلاس در C# 12 به صورت زیر خلاصه شده و پارامتر dependent تعریف شده‌ی در سازنده‌ی اولیه‌ی آن، به همان شکل و بدون نیاز به کد اضافی، در سایر متدهای این کلاس قابل استفاده‌است:
public class MyService(IDependent dependent)
{
    public void Do() 
    {
        dependent.DoWork();
    }
}

البته مفهوم Captures هم در زبان #C جدید نیست و در ابتدا به همراه anonymous methods و بعدها به همراه lambda expressions، معرفی و بکار گرفته شد. برای مثال درون یک lambda expression، اگر از متغیری خارج از آن lambda expressions استفاده شود، کامپایلر یک capture از آن متغیر را تهیه کرده و استفاده می‌کند.

بنابراین به صورت خلاصه primary constructors در رکوردها، با هدف تعریف خواص عمومی فقط خواندنی، ارائه شدند؛ اما primary constructors ارائه شده‌ی در C# 12 که اینبار قابل اعمال به کلاس‌ها و structs معمولی است، بیشتر هدف ساده سازی تعریف کدهای تکراری private fields را دنبال می‌کند. برای نمونه این کدی است که کامپایلر برای primary constructor مثال ابتدای بحث تولید می‌کند و در اینجا نحوه‌ی تولید خودکار این فیلدهای خصوصی را مشاهده می‌کنید:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace CS8Tests
{
  [NullableContext(1)]
  [Nullable(0)]
  public class Employee
  {
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string <FirstName>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string <LastName>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private DateTime <HireDate>k__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private Decimal <Salary>k__BackingField;

    public Employee(string firstName, string lastName, DateTime hireDate, Decimal salary)
    {
      this.<FirstName>k__BackingField = firstName;
      this.<LastName>k__BackingField = lastName;
      this.<HireDate>k__BackingField = hireDate;
      this.<Salary>k__BackingField = salary;
      base..ctor();
    }

    public string FirstName
    {
      [CompilerGenerated] get
      {
        return this.<FirstName>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<FirstName>k__BackingField = value;
      }
    }

    public string LastName
    {
      [CompilerGenerated] get
      {
        return this.<LastName>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<LastName>k__BackingField = value;
      }
    }

    public DateTime HireDate
    {
      [CompilerGenerated] get
      {
        return this.<HireDate>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<HireDate>k__BackingField = value;
      }
    }

    public Decimal Salary
    {
      [CompilerGenerated] get
      {
        return this.<Salary>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<Salary>k__BackingField = value;
      }
    }
  }
}
بنابراین آیا پارامترهای سازنده‌ی اولیه، به صورت خواص تعریف می‌شوند و قابلیت تغییر میدان دید آن‌ها میسر است؟ پاسخ: خیر. این پارامترها توسط کامپایلر، به صورت فیلدهای خصوصی در سطح کلاس، تعریف و استفاده می‌شوند. یعنی تمام اعضای کلاس، البته منهای سازنده‌های ثانویه، به این پارامترها دسترسی دارند. همچنین، این تولید کد هم بهینه‌است و صرفا برای پارامترهایی انجام می‌شود که واقعا در کلاس استفاده شده باشند؛ درغیر اینصورت، فیلد خصوصی متناظری برای آن‌ها تولید نخواهد شد.

یک نکته: برای مشاهده‌ی یک چنین کدهایی می‌توانید از منوی Tools->IL Viewer برنامه‌ی Rider استفاده کرده و در برگه‌ی ظاهر شده، گزینه‌ی #Low-Level C آن‌را انتخاب نمائید.


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

اگر به کدهای #Low-Level C تولیدی فوق دقت کنید، این کلاس، به همراه یک سازنده‌ی خالی بدون پارامتر (parameter less constructor) نیست و سازنده‌ی پیش‌فرضی (default constructor) برای آن درنظر گرفته نشده‌است ... اما اگر کلاسی به همراه یک primary constructor تعریف شد، می‌توان با استفاده از واژه‌ی کلیدی this، سازنده‌ی ثانویه‌ای را هم برای آن تعریف کرد:
public class Person(string firstName, string lastName) 
{
    public Person() : this("John", "Smith") { }
    public Person(string firstName) : this(firstName, "Smith") { }
    public string FullName => $"{firstName} {lastName}";
}
در اینجا نحوه‌ی تعریف یک Default constructor بدون پارامتر را هم ملاحظه می‌کنید.


امکان ارث‌بری و تعریف سازنده‌ی اولیه

مثال زیر را درنظر بگیرید که در آن کلاس مشتق شده‌ی از کلاس User، یک سازنده‌ی اولیه را تعریف کرده:
public class User
{
    public User(string firstName, string lastName) { }
}

public class Editor(string firstName, string lastName) : User
{
}
در این حالت برنامه با خطای «Base class 'CS8Tests.User' does not contain parameterless constructor» کامپایل نمی‌شود. عنوان می‌کند که اگر کلاس مشتق شده می‌خواهد سازنده‌ی اولیه‌ای داشته باشد، باید کلاس پایه را به همراه یک سازنده‌ی پیش‌فرض بدون پارامتر تعریف کنید.
البته این محدودیت با structها وجود ندارد؛ چون structها، value type هستند و همواره به صورت پیش‌فرض، به همراه یک سازنده‌ی پیش فرض بدون پارامتر، تولید می‌شوند.
یک مثال: قطعه کد متداول ارث‌بری زیر را درنظر بگیرید که در آن، کلاس مشتق شده به کمک واژه‌ی کلید base، امکان تعریف سازنده‌ی جدیدی را یافته و یکی از پارامترهای سازنده‌ی کلاس پایه را مقدار دهی می‌کند:
public class Automobile
{
    public Automobile(int wheels, int seats)
    {
        Wheels = wheels;
        Seats = seats;
    }

    public int Wheels { get; }
    public int Seats { get; }
}

public class Car : Automobile
{
    public Car(int seats) : base(4, seats)
    {
    }
}
این تعاریف در C# 12 به صورت زیر خلاصه می‌شوند:
public class Automobile(int wheels, int seats)
{
    public int Wheels { get; } = wheels;
    public int Seats { get; } = seats;
}

public class Car(int seats) : Automobile(4, seats);

و یا یک نمونه مثال دیگر آن به صورت زیر است که در آن، ذکر بدنه‌ی کلاس در C# 12، الزامی ندارد:
public class MyBaseClass(string s); // no body required

public class Derived(int i, string s, bool b) : MyBaseClass(s)
{
    public int I { get; set; } = i;
    public string B => b.ToString();
}


توصیه به پرهیز از double capturing

با مفهوم capture در این مطلب آشنا شدیم. در مثال زیر دوبار از پارامتر سازنده‌ی age، در دو قسمت عمومی شده، استفاده شده‌است:
public class Human(int age)
{
    // initialization
    public int Age { get; set; } = age;

    // capture
    public string Bio => $"My age is {age}!";
}
در این حالت ممکن است استفاده کننده در طول برنامه، با وضعیت ناخواسته‌ی زیر مواجه شود:
var p = new Human(42);
Console.WriteLine(p.Age); // Output: 42
Console.WriteLine(p.Bio); // Output: My age is 42!

p.Age++;
Console.WriteLine(p.Age); // Output: 43
Console.WriteLine(p.Bio); // Output: My age is 42! // !
در اینجا پس از افزودن مقداری به خاصیت عمومی Age، زمانیکه به مقدار عبارت Bio مراجعه می‌شود، خروجی قبلی را دریافت می‌کنیم!
درک بهتر آن، نیاز به #Low-Level C کلاس Human را دارد:
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace CS8Tests
{
  [NullableContext(1)]
  [Nullable(0)]
  public class Human
  {
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <age>P;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private int <Age>k__BackingField;

    public Human(int age)
    {
      this.<age>P = age;
      this.<Age>k__BackingField = this.<age>P;
      base..ctor();
    }

    public int Age
    {
      [CompilerGenerated] get
      {
        return this.<Age>k__BackingField;
      }
      [CompilerGenerated] set
      {
        this.<Age>k__BackingField = value;
      }
    }

    public string Bio
    {
      get
      {
        DefaultInterpolatedStringHandler interpolatedStringHandler = new DefaultInterpolatedStringHandler(11, 1);
        interpolatedStringHandler.AppendLiteral("My age is ");
        interpolatedStringHandler.AppendFormatted<int>(this.<age>P);
        interpolatedStringHandler.AppendLiteral("!");
        return interpolatedStringHandler.ToStringAndClear();
      }
    }
  }
}
همانطور که مشاهده می‌کنید، کامپایلر، پارامتر age را دوبار، جداگانه capture کرده‌است:
public Human(int age)
{
   this.<age>P = age;
   this.<Age>k__BackingField = this.<age>P;
   base..ctor();
}
به همین جهت است که ++p.Age، فقط بر روی یکی از فیلدهای capture شده تاثیر داشته و بر روی دیگری خیر. به این مورد، double capturing گفته می‌شود و توصیه شده از آن پرهیز کنید و بجای استفاده‌ی دوباره از پارامتر age، از خود خاصیت Age استفاده نمائید.
نظرات مطالب
جایگزین کردن jQuery با JavaScript خالص - قسمت چهارم - ایجاد تغییرات در DOM
یک نکته‌ی تکمیلی
در متد insertAdjacentHTML که امضای آن به صورت زیر است:  
node.insertAdjacentHTML(position, text)
position : یکی از گزینه‌های  (afterbegin, afterend, beforebegin , beforeend) می‌تواند باشد که محل درج را مشخص می‌کند .
var flavors = document.querySelector('.flavors');

flavors.insertAdjacentHTML('afterbegin', '<li>pistachio</li>');
flavors.insertAdjacentHTML('afterend', '<li>pistachio</li>');
flavors.insertAdjacentHTML('beforebegin', '<li>pistachio</li>');
flavors.insertAdjacentHTML('beforeend', '<li>pistachio</li>');
در اینجا
 text : یک رشته همراه با تگ‌های HTML  است.
متد insertAdjacentText  : در صورتیکه قصد درج متن تنها بدون تگ‌های HTML را داشته باشیم. 
متد insertAdjacentElement  : در صورتیکه قصد درج یک المان  را داشته باشیم، از این متد استفاده می‌کنیم که در این حالت طبق توضیحات بالا المان مورد نظر را می‌توان با استفاده document.createElement  ایجاد کرد.  
مطالب
Angular CLI - قسمت سوم - تولید کد
پس از ایجاد ساختار اولیه‌ی یک برنامه‌ی Angular توسط Angular CLI، امکان تولید کدهای کامپوننت‌ها، ماژول‌ها، سرویس‌ها و ... نیز در این ابزار پیش بینی شده‌است. کدهای تولید شده‌ی آن بر اساس یک سری blueprint (و یا همان مفهوم قالب‌های از پیش آماده در سایر ابزارهای مشابه) ایجاد می‌شوند و فرمت کلی آن نیز به صورت ذیل است:
> ng generate <blueprint> <options>


ایجاد کامپوننت‌های جدید توسط Angular CLI

دستور ایجاد یک کامپوننت جدید توسط Angular CLI به نحو زیر است:
> ng generate component customer
این دستور اندکی طولانی به نظر می‌رسد. به همین جهت برای خلاصه نویسی آن می‌توان از مفهومی به نام Alias استفاده کرد. میانبر generate در اینجا g است و میانبر component، معادل c می‌باشد. به این صورت می‌توان دستور فوق را به این شکل، خلاصه و بازنویسی کرد:
> ng g c customer


گزینه‌های ایجاد کدهای جدید در Angular CLI

اگر به اولین دستور بحث جاری دقت کنید، قسمت <options> نیز برای آن درنظر گرفته شده‌است. تعدادی از مهم‌ترین گزینه‌هایی را که در اینجا می‌توان ذکر کرد به شرح زیر هستند:

گزینه 
Alias (میانبر/نام مستعار)      توضیح 
 flat--
    آیا باید برای آن پوشه‌ای ایجاد نشود؟ (flat = بدون پوشه در اینجا)
 (پیش فرض آن ایجاد یک پوشه‌ی جدید است). اگر می‌خواهیم ایجاد نشود، باید flat true-- را ذکر کرد. 
inline-template--  it-  آیا قالب کامپوننت، درون فایل ts. آن قرار گیرد؟ (پیش فرض آن، false است) 
 inline-style--  is-  آیا شیوه نامه‌ی کامپوننت، داخل فایل ts. آن قرار گیرد؟ (پیش فرض آن، false است) 
 spec--    آیا فایل spec نیز تولید شود؟
(پیش فرض آن true است) اگر می‌خواهیم این فایل ایجاد نشود باید spec false-- را ذکر کرد. 
 view-encapsulation--
 ve-  تعیین نوع استراتژی view encapsulation مورد استفاده (مانند Emulated). 
 change-detection--  cd-  تعیین استراتژی change detection مورد استفاده (مانند OnPush). 
 dry-run--  d-  گزارش فایل‌های تولیدی، بدون نوشتن و تولید آن‌ها (پیش فرض آن false است) 
 prefix--    تعیین صریح prefix مورد استفاده‌ی در حین مقدار دهی selectorها که در قسمت قبل در مورد آن بحث شد. 

برای مثال اگر خواستیم کامپوننتی را به همراه قالب‌ها و شیوه‌نامه‌های inline (قرار گرفته‌ی داخل فایل ts. آن) تولید کنیم، می‌توان از دستور ذیل کمک گرفت:
>ng generate component customer --inline-template --inline-style
که خلاصه شده‌ی آن با توجه به Alias‌های ذکر شده به صورت ذیل است:
>ng g c customer –it -is

اگر صرفا دستور ng generate component customer را اجرا کنیم (بدون هیچ گزینه‌ی اضافه‌تری)، فایل‌های ts (کلاس کامپوننت)، css (فایل شیوه نامه)، html (فایل قالب) و spec (فایل آزمون واحد کامپوننت) به صورت خودکار تولید خواهند شد.

همانطور که پیشتر نیز عنوان شد، اگر مطمئن نیستید که دستور درحال فراخوانی، چه فایل‌ها و پوشه‌هایی را ایجاد می‌کند، با ذکر پرچم dry-run-- و یا به صورت خلاصه d-، دستور مدنظر را شبیه سازی کنید تا صرفا گزارشی را از فایل‌هایی که قرار است تولید شوند، ارائه دهد.

نکته‌ی مهم دیگری که به همراه دستورات Angular CLI هستند، به روز رسانی خودکار فایل app.module.ts است:
>ng g c customer
installing component
  create src\app\customer\customer.component.css
  create src\app\customer\customer.component.html
  create src\app\customer\customer.component.spec.ts
  create src\app\customer\customer.component.ts
  update src\app\app.module.ts
برای نمونه زمانیکه دستور تولید یک کامپوننت را به نحوی که ملاحظه می‌کنید صادر کنیم، علاوه بر ایجاد 4 فایل مرتبط با آن کامپوننت، سطر به روز رسانی فایل app.module.ts را نیز در انتها ذکر کرده‌است. در اینجا تغییرات صورت گرفته را ملاحظه می‌کنید:
 import { CustomerComponent } from './customer/customer.component';

@NgModule({
  declarations: [
   AppComponent,
   CustomerComponent
]})
ابتدا به صورت خودکار سطر import این کامپوننت جدید ذکر شده‌است و سپس قسمت declarations ماژول را نیز با تعریف CustomerComponent به روز رسانی کرده‌است. بنابراین کار با Angular CLI فراتر است از صرفا کار با تعدادی قالب از پیش آماده‌ی کامپوننت‌ها و سرویس‌ها.


مشاهده‌ی تغییرات انجام شده‌ی توسط Angular CLI به کمک سورس کنترل

همانطور که در قسمت قبل نیز عنوان شد، دستور ng new، کار آغاز یک مخزن Git را نیز به صورت خودکار انجام می‌دهد. در اینجا هر دستوری که توسط Angular CLI اجرا شود، به این مخزن کد commit خواهد شد.


برای مثال اگر کل پوشه‌ی برنامه را توسط VSCode باز کنیم (کلیک راست در داخل ریشه‌ی اصلی پروژه و انتخاب گزینه‌ی Open With Code)، با مراجعه‌ی به لیست تغییرات و بررسی diff آن‌ها، به سادگی می‌توان تشخیص داد که چه تغییراتی بر روی فایل‌ها اعمال شده‌اند.


ایجاد سایر اجزای جدید برنامه توسط Angular CLI

نام جزء
 Alias  دستور
 service   s   ng g service customer-data 
 pipe  p   ng g pipe init-caps 
 class   cl   ng g class customer-model 
 directive   d   ng g directive search 
 interface   i   ng g interface orders 
 enum  e   ng g enum gender 
 module  m   ng generate module sales 

نکات تکمیلی
 - در حین ایجاد یک directive جدید، پوشه‌ای را برای آن ایجاد نمی‌کند. اگر می‌خواهید اینکار به صورت flat (بدون پوشه در اینجا) انجام نشود، گزینه‌ی flat false-- را نیز قید کنید.
 - در حین ایجاد یک سرویس جدید، اخطار «WARNING Service is generated but not provided, it must be provided to be used» را دریافت خواهید کرد. علت اینجا است که Angular CLI نمی‌داند که این سرویس را باید به کامپوننت خاصی اضافه کند یا به ماژول برنامه. به همین جهت یا باید به صورت دستی فایل src\app\app.module.ts را ویرایش و قسمت providers آن‌را بر اساس نام این سرویس جدید تکمیل کرد و یا توسط سوئیچ m می‌توان ماژول مدنظر را دقیقا ذکر کرد:
> ng g s sales -m app.module
در اینجا عنوان شده‌است که پس از ایجاد سرویس جدید sales، قسمت providers ماژول src\app\app.module.ts نیز به روز رسانی شود.
این نکته در مورد تمام اجزایی که فایل app.module را به روز رسانی می‌کنند نیز صادق است. اگر برای مثال کامپوننتی قرار است ماژول جدید دیگری را به روز رسانی کند، می‌توانید به صورت صریح نام ماژول آن‌را قید کنید؛ در غیراینصورت از همان app.module پیش فرض استفاده خواهد شد.
 - همانطور که مشاهده می‌کنید امکان تولید کلاس، اینترفیس و enum تایپ‌اسکریپتی نیز در اینجا پیش بینی شده‌است. اگر خواستید کلاسی را درون پوشه‌ی خاصی قرار دهید می‌توانید محل پوشه‌ی آن‌را دقیقا ذکر کنید (در مورد اینترفیس‌ها و enums و سایر اجزاء نیز به همین صورت):
> ng g cl models/customer
به این ترتیب فایل کلاس customer.ts درون پوشه‌ی arc/app/models تشکیل می‌شود. پوشه‌ی models نیز در صورت عدم وجود به صورت خودکار ایجاد خواهد شد.


تغییر تنظیمات پیش فرض تولید کد پروژه‌ی جاری

در قسمت قبل «تغییر پیش فرض‌های عمومی Angular CLI» را بررسی کردیم. در اینجا نیز می‌توان یکسری از خواص فایل angular-cli.json. را بازنویسی کرد؛ در قسمت defaults آن:
"defaults": {
    "styleExt": "css",
    "component": {}
}
یا از طریق خط فرمان
> ng set defaults.component.flat false
> ng set defaults.directive.flat false
> ng set defaults.styleExt sass
و یا با ویرایش فایل json تنظیمات cli به صورت مستقیم:
  "defaults": {
    "styleExt": "sass",
    "component": {
      "flat": false
    },
    "directive": {
      "flat": false
    }
  }
به این ترتیب دیگر نیازی نخواهد بود تا هربار به ازای ایجاد یک دایرکتیو جدید، پرچم flat نبودن آن‌را مقدار دهی کرد؛ چون از فایل angular-cli.json. تنظیمات خودش را دریافت می‌کند.


و اگر VSCode استفاده می‌کنید، به همراه intellisense کاملی در مورد اجزای مختلف این فایل json است (این intellisense را به صورت خودکار بر اساس اسکیمای این فایل و سرویس زبان Angular تهیه می‌کند).
مطالب
HTML5 Offline Web Applications
وب به سمتی پیش رفته که کاربران زیادی از تلفن همراه ، تبلت‌ها و دیگر عامل ها(Agent) جهت مرور صفحات وب استفاده می‌کنند. در نتیجه تعداد کاربرانی که مدام در حال حرکت به مرور صفحات وب و استفاده از سرویس‌های برخط می‌پردازند رو به افزایش است. برنامه‌های خارج از شبکه‌ی HTML 5 یا به عبارتی HTML5 Offline Web Applications توسعه دهنگان را قادر می‌سازد تا نرم افزار‌های تحت وبی ارائه دهند که در حالت قطع بودن اینترنت و یا شبکه همچنان به سرویس دادن به کاربران ادامه دهد. دیگر اینگونه نیست که وب تنها در حالت برخط بودن معنی پیدا کند. یک نرم افزار مدیریت هزینه‌ی تحت وب را بررسی کنید که روی تلفن همراه شما اجرا شده ، در محلی که دسترسی به اینترنت نیست قصد استفاده از آن را دارید. چه قدر خوب می‌شود که این نرم افزار به گونه ای پیاده سازی شده باشد که بتواند در حالت برون خطی (Offline) به سرویس دهی ادامه دهد به طور مثال قادر به ذخیره‌ی داده‌های شما به صورت برون خط و همزمان سازی آنها پس از اتصال به اینترنت باشد.
برنامه‌های تحت وب برون خط با کمک قابلیتی به نام نهانگاه برنامه (Application Cache) کار می‌کنند. این قابلیت می‌تواند تمامی بخش‌های سایت را به شکل برون خط و خارج از شبکه، ذخیره کند. با به کار گیری این ویژگی می‌توان تمامی فایل‌های ایستا (JavaScript , HTML , CSS , Image) بر روی ابزار کاربر ذخیره نمود.

 نهانگاه برنامه چه تفاوتی با نهانگاه مرورگر (Browser cache) دارد ؟

مرورگر‌ها برای افزایش سرعت بارگذاری سایت از نهانگاه مخصوص به خود استفاده می‌کنند. مرورگر اگر فایلی را در اختیار داشته باشد دیگر برای دفعات بعدی آن فایل را از سرور درخواست نمی‌کند. این قابلیت اگر دسترسی به شبکه قطع باشد کاربردی ندارد ، همچنین به عنوان توسعه دهنده کنترلی بر روی این قابلیت نداریم اما زمانی که از تکنولوژی برنامه‌های آفلاین استفاده می‌شود این توانایی در اختیار توسعه دهنده است که به مرورگر بگوید کدام فایل‌ها در نهانگاه نگهداری شود ، کدام فایل‌ها  در هر بار اتصال از سرور درخواست شود و حتی اگر دریافت فایل‌ها از مخزن با مشکل مواجه شد چه اقدامی صورت پذیرد.

برای استفاده از این ویژگی اولین باید فایلی به نام cache.manifest ایجاد کرد. این فایل باید با نوع محتوای (Mime type) مناسب برای کاربر ارسال شود. این فایل یک فایل متنی می‌باشد که لیست فایل‌های مورد نظر ما با قواعد خاصی در آن قرار می‌گیرد.
همیشه اولین خط این فایل عبارت CACHE MANIFEST قرار دارد ، پس از این خط عبارت CACHE: وارد می‌شود و فایل‌های مد نظر ما لیست می‌شود.
CACHE MANIFEST
 
#Cache Section
CACHE:
/Content/Images/icons-18-white.png
/Content/Images/icons-36-white.png
/Content/Images/ajax-loader.png
/Content/css
/Scripts/js
ذخیره‌ی برخی فایل‌ها روی سیستم کاربر ضرورتی ندارد ، برای مثال اسکریپتی که از یک وب سرویس برخط اطلاعات آب و هوا را دریافت می‌کند در حالت Offline کاربردی ندارد. برای مشخص کردن این فایل‌ها یک لیست سفید آماده می‌کنیم.
NETWORK
webService.Js
در بخش معرفی فایل‌های آفلاین بازید همه‌ی فایل‌های مد نظر ما معرفی شوند اما در لیست سفید با گذاشتن ستاره به مرورگر اعلام می‌کنیم که هر فایل و مسیری که در بخش قبلی (CACHE) نیامده را همواره از سرور درخواست کن. درج ستاره در بخش NETWORK ضروری است زیرا همه‌ی آدرس‌های سایت باید در یکی از بخش‌های cache.manifest قرار بگیرد ، اگر آدرسی در cache.manifest قرار نگیرد هیچگاه بارگذاری نخواهد شد.
در فایل cache.manifest می‌توان یادداشت هم اضافه کرد ، تمامی کاراکتر هایی که بعد از # قرار گیرند پردازش نمی‌شوند. یادداشت‌ها مفید هستند ، می‌تونید اطلاعات نسخه‌ی فعلی فایل cache.manifest را در آنها قرار دهید.
مثال :
CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
css
images/
stylesheet
.logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
login.php
/myapi
http://api.twitter.com
گفته شد فایل Cache.manifest باید با نوع محتوای مناسب ارسال شود ، برای تعیین نوع محتوا در وب سرور apache باید خط زیر به فایل .htaaccess اضافه شود :
AddType text/cache-manifest .appcache
در ASP.NET Web Forms می‌توان از یک Generic Handler برای ارسال فایل با نوع محتوای مناسب استفاده کرد : 
using System.Web;
 
namespace JavaScriptReference {
 
    public class Manifest : IHttpHandler {
 
        public void ProcessRequest(HttpContext context) {
            context.Response.ContentType = "text/cache-manifest";
            context.Response.WriteFile(context.Server.MapPath("Manifest.txt"));
        }
 
        public bool IsReusable {
            get {
                return false;
            }
        }
    }
}
یا می‌توان در یک فایل ASPX به صورتی دستی نوع محتوا را مشخص کرد : 
CACHE MANIFEST

# Version Jesus 3!

CACHE:

index.html
js/Custom.js
js/Utility.js
styles/index.css
styles/kendo.common.min.css
styles/BlueOpal/loading.gif
styles/BlueOpal/slider-h.gif
styles/BlueOpal/slider-v.gif

NETWORK:
*

<%@ Page Language="VB" ContentType="text/cache-manifest"  ResponseEncoding="utf-8" AutoEventWireup="true" CodeFile="manifest.aspx.vb" Inherits="Configuration_manifest" %>
در ASP.NET MVC علاوه بر اینکه می‌توان دستی نوع محتوای Response را مشخص کرد، می‌توان یک ActionResult منحصر به فرد ایجاد کرد. یک نمونه پیاده سازی شده را اینجا مشاهده کنید.
پس از انجام همه‌ی این پیش نیاز‌ها باید فایل cache manifest را به خصیصه‌ی manifest برچسب html ارجاع دهیم. 
<!DOCTYPE html>
<html manifest="/manifest.aspx">
اکنون قسمت‌های مد نظر سایت ما در حالت عدم دسترسی به شبکه نیز قابل استفاده می‌باشد. حتی این ویژگی در حالت برخط صفحات ما با سرعت بالاتری بارگذاری شوند.
خصیصه‌ی manifest تمامی فایل‌های HTML باید مقدار گیرد در غیر این صورت ممکن است صفحات درون نهانگاه قرار نگیرند.  
برای بررسی مرورگرهایی که این ویژگی را پشتیبانی می‌کنند این لینک  را مشاهده کنید.
مطالب
آشنایی با NHibernate - قسمت هفتم

مدیریت بهینه‌ی سشن فکتوری

ساخت یک شیء SessionFactory بسیار پر هزینه و زمانبر است. به همین جهت لازم است که این شیء یکبار حین آغاز برنامه ایجاد شده و سپس در پایان کار برنامه تخریب شود. انجام اینکار در برنامه‌های معمولی ویندوزی (WinForms ،WPF و ...)، ساده است اما در محیط Stateless وب و برنامه‌های ASP.Net ، نیاز به راه حلی ویژه وجود خواهد داشت و تمرکز اصلی این مقاله حول مدیریت صحیح سشن فکتوری در برنامه‌های ASP.Net است.

برای پیاده سازی شیء سشن فکتوری به صورتی که یکبار در طول برنامه ایجاد شود و بارها مورد استفاده قرار گیرد باید از یکی از الگوهای معروف طراحی برنامه نویسی شیء گرا به نام Singleton Pattern استفاده کرد. پیاده سازی نمونه‌ی thread safe آن که در برنامه‌های ذاتا چند ریسمانی وب و همچنین برنامه‌های معمولی ویندوزی می‌تواند مورد استفاده قرار گیرد، در آدرس ذیل قابل مشاهده است:



از پنجمین روش ذکر شده در این مقاله جهت ایجاد یک lazy, lock-free, thread-safe singleton استفاده خواهیم کرد.

بررسی مدل برنامه

در این مدل ساده ما یک یا چند پارکینگ داریم که در هر پارکینگ یک یا چند خودرو می‌توانند پارک شوند.


یک برنامه ASP.Net را آغاز کرده و ارجاعاتی را به اسمبلی‌های زیر به آن اضافه نمائید:
FluentNHibernate.dll
NHibernate.dll
NHibernate.ByteCode.Castle.dll
NHibernate.Linq.dll
و همچنین ارجاعی به اسمبلی استاندارد System.Data.Services.dll دات نت فریم ورک سه و نیم

تصویر نهایی پروژه ما به شکل زیر خواهد بود:



پروژه ما دارای یک پوشه domain ، تعریف کننده موجودیت‌های برنامه جهت تهیه نگاشت‌های لازم از روی ‌آن‌ها است. سپس یک پوشه جدید را به نام NHSessionManager به آن جهت ایجاد یک Http module مدیریت کننده سشن‌های NHibernate در برنامه اضافه خواهیم کرد.

ساختار دومین برنامه (مطابق کلاس دیاگرام فوق):

namespace NHSample3.Domain
{
public class Car
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Color { get; set; }
}
}

using System.Collections.Generic;

namespace NHSample3.Domain
{
public class Parking
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Location { get; set; }
public virtual IList<Car> Cars { get; set; }

public Parking()
{
Cars = new List<Car>();
}
}
}
مدیریت سشن فکتوری در برنامه‌های وب

در این قسمت قصد داریم Http Module ایی را جهت مدیریت سشن‌های NHibernate ایجاد نمائیم.

در ابتدا کلاس Config را در پوشه مدیریت سشن NHibernate با محتویات زیر ایجاد کنید:

using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;

namespace NHSessionManager
{
public class Config
{
public static FluentConfiguration GetConfig()
{
return
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(x => x.FromConnectionStringWithKey("DbConnectionString"))
)
.ExposeConfiguration(
x => x.SetProperty("current_session_context_class", "managed_web")
)
.Mappings(
m => m.AutoMappings.Add(
new AutoPersistenceModel()
.Where(x => x.Namespace.EndsWith("Domain"))
.AddEntityAssembly(typeof(NHSample3.Domain.Car).Assembly))
);
}

public static void CreateDb()
{
bool script = false;//آیا خروجی در کنسول هم نمایش داده شود
bool export = true;//آیا بر روی دیتابیس هم اجرا شود
bool dropTables = false;//آیا جداول موجود دراپ شوند
new SchemaExport(GetConfig().BuildConfiguration()).Execute(script, export, dropTables);
}
}
}
با این کلاس در قسمت‌های قبل آشنا شده‌اید. در این کلاس با کمک امکانات Auto mapping موجود در Fluent Nhibernate (مطلب قسمت قبلی این سری آموزشی) اقدام به تهیه نگاشت‌های خودکار از کلاس‌های قرار گرفته در پوشه دومین خود خواهیم کرد (فضای نام این پوشه به دومین ختم می‌شود که در متد GetConfig مشخص است).
دو نکته جدید در متد GetConfig وجود دارد:
الف) استفاده از متد FromConnectionStringWithKey ، بجای تعریف مستقیم کانکشن استرینگ در متد مذکور که روشی است توصیه شده. به این صورت فایل وب کانفیگ ما باید دارای تعریف کلید مشخص شده در متد GetConfig به نام DbConnectionString باشد:

<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true" />
</connectionStrings>
ب) قسمت ExposeConfiguration آن نیز جدید است.
در اینجا به AutoMapper خواهیم گفت که قصد داریم از امکانات مدیریت سشن مخصوص وب فریم ورک NHibernate استفاده کنیم. فریم ورک NHibernate دارای کلاسی است به نام NHibernate.Context.ManagedWebSessionContext که جهت مدیریت سشن‌های خود در پروژه‌های وب ASP.Net پیش بینی کرده است و از این متد در Http module ایی که ایجاد خواهیم کرد جهت ردگیری سشن جاری آن کمک خواهیم گرفت.

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



سپس کلاس SingletonCore را جهت تهیه تنها و تنها یک وهله از شیء سشن فکتوری در کل برنامه ایجاد خواهیم کرد (همانطور که عنوان شده، ایده پیاده سازی این کلاس thread safe ، از مقاله معرفی شده در ابتدای بحث گرفته شده است):

using NHibernate;

namespace NHSessionManager
{
/// <summary>
/// lazy, lock-free, thread-safe singleton
/// </summary>
public class SingletonCore
{
private readonly ISessionFactory _sessionFactory;

SingletonCore()
{
_sessionFactory = Config.GetConfig().BuildSessionFactory();
}

public static SingletonCore Instance
{
get
{
return Nested.instance;
}
}

public static ISession GetCurrentSession()
{
return Instance._sessionFactory.GetCurrentSession();
}

public static ISessionFactory SessionFactory
{
get { return Instance._sessionFactory; }
}

class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}

internal static readonly SingletonCore instance = new SingletonCore();
}
}
}
اکنون می‌توان از این Singleton object جهت تهیه یک Http Module کمک گرفت. برای این منظور کلاس SessionModule را به برنامه اضافه کنید:

using System;
using System.Web;
using NHibernate;
using NHibernate.Context;

namespace NHSessionManager
{
public class SessionModule : IHttpModule
{
public void Dispose()
{ }

public void Init(HttpApplication context)
{
if (context == null)
throw new ArgumentNullException("context");

context.BeginRequest += Application_BeginRequest;
context.EndRequest += Application_EndRequest;
}

private void Application_BeginRequest(object sender, EventArgs e)
{
ISession session = SingletonCore.SessionFactory.OpenSession();
ManagedWebSessionContext.Bind(HttpContext.Current, session);
session.BeginTransaction();
}

private void Application_EndRequest(object sender, EventArgs e)
{
ISession session = ManagedWebSessionContext.Unbind(
HttpContext.Current, SingletonCore.SessionFactory);
if (session == null) return;

try
{
if (session.Transaction != null &&
!session.Transaction.WasCommitted &&
!session.Transaction.WasRolledBack)
{
session.Transaction.Commit();
}
else
{
session.Flush();
}
}
catch (Exception)
{
session.Transaction.Rollback();
}
finally
{
if (session != null && session.IsOpen)
{
session.Close();
session.Dispose();
}
}
}
}
}
کلاس فوق کار پیاده سازی اینترفیس IHttpModule را جهت دخالت صریح در request handling pipeline برنامه ASP.Net جاری انجام می‌دهد. در این کلاس مدیریت متدهای استاندارد Application_BeginRequest و Application_EndRequest به صورت خودکار صورت می‌گیرد.
در متد Application_BeginRequest ، در ابتدای هر درخواست یک سشن جدید ایجاد و به مدیریت سشن وب NHibernate بایند می‌شود، همچنین یک تراکنش نیز آغاز می‌گردد. سپس در پایان درخواست، این انقیاد فسخ شده و تراکنش کامل می‌شود، همچنین کار پاکسازی اشیاء نیز صورت خواهد گرفت.

با توجه به این موارد، دیگر نیازی به ذکر using جهت dispose کردن سشن جاری در کدهای ما نخواهد بود، زیرا در پایان هر درخواست اینکار به صورت خودکار صورت می‌گیرد. همچنین نیازی به ذکر تراکنش نیز نمی‌باشد، چون مدیریت آن‌را خودکار کرده‌ایم.

جهت استفاده از این Http module تهیه شده باید چند سطر زیر را به وب کانفیگ برنامه اضافه کرد:

<httpModules>
<!--NHSessionManager-->
<add name="SessionModule" type="NHSessionManager.SessionModule"/>
</httpModules>
بدیهی است اگر نخواهید از Http module استفاده کنید باید این کدها را در فایل Global.asax برنامه قرار دهید.

اکنون مثالی از نحوه‌ی استفاده از امکانات فراهم شده فوق به صورت زیر می‌تواند باشد:
ابتدا کلاس ParkingContext را جهت مدیریت مطلوب‌تر LINQ to NHibernate تشکیل می‌دهیم.

using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHSample3.Domain;

namespace NHSample3
{
public class ParkingContext : NHibernateContext
{
public ParkingContext(ISession session)
: base(session)
{ }

public IOrderedQueryable<Car> Cars
{
get { return Session.Linq<Car>(); }
}

public IOrderedQueryable<Parking> Parkings
{
get { return Session.Linq<Parking>(); }
}
}
}
سپس در فایل Default.aspx.cs برنامه ، برای نمونه تعدادی رکورد را افزوده و نتیجه را در یک گرید ویوو نمایش خواهیم داد:

using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHSample3.Domain;
using NHSessionManager;

namespace NHSample3
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
//ایجاد دیتابیس در صورت نیاز
//Config.CreateDb();

//ثبت یک سری رکورد در دیتابیس
ISession session = SingletonCore.GetCurrentSession();

Car car1 = new Car() { Name = "رنو", Color = "مشکلی" };
session.Save(car1);
Car car2 = new Car() { Name = "پژو", Color = "سفید" };
session.Save(car2);

Parking parking1 = new Parking()
{
Location = "آدرس پارکینگ مورد نظر",
Name = "پارکینگ یک",
Cars = new List<Car> { car1, car2 }
};

session.Save(parking1);

//نمایش حاصل در یک گرید ویوو
ParkingContext db = new ParkingContext(session);
var query = from x in db.Cars select new { CarName = x.Name, CarColor = x.Color };
GridView1.DataSource = query.ToList();
GridView1.DataBind();
}
}
}
مدیریت سشن فکتوری در برنامه‌های غیر وب

در برنامه‌های ویندوزی مانند WinForms ، WPF و غیره، تا زمانیکه یک فرم باز باشد، کل فرم و اشیاء مرتبط با آن به یکباره تخریب نخواهند شد، اما در یک برنامه ASP.Net جهت حفظ منابع سرور در یک محیط چند کاربره، پس از پایان نمایش یک صفحه وب، اثری از آثار اشیاء تعریف شده در کدهای آن صفحه در سرور وجود نداشته و همگی بلافاصله تخریب می‌شوند. به همین جهت بحث‌های ویژه state management در ASP.Net در اینباره مطرح است و مدیریت ویژه‌ای باید روی آن صورت گیرد که در قسمت قبل مطرح شد.
از بحث فوق، تنها استفاده از کلاس‌های Config و SingletonCore ، جهت استفاده و مدیریت بهینه‌ی سشن فکتوری در برنامه‌های ویندوزی کفایت می‌کنند.

دریافت سورس برنامه قسمت هفتم

ادامه دارد ....