نظرات مطالب
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
در کد بالا، برای حالت ویرایش یک سلول در نوع select از mvc controller استفاده شده. من کد بالا رو برای webApi میخوام تنظیم کنم. رشته html رو تولید میکنم و پاس میدم به خروجی، اما خطا میده. 
کد رو ببینید.
 [HttpGet]
public string SelectAllJqTree()
        {
            var result = _kpiTypeService.SelectAll().Select(e => new
            {
                value = e.Id,
                text = e.Name,
            });

            var select = "<select>{0}</select>";
            var option = "<option value='{0}' >{1}</option>";

            var options = "";

            foreach (var item in result)
            {
                options += string.Format(option, item.value, item.text);
            }

            return  string.Format(select, options).Trim();
}
و در کد js ستون "kpiType" رو هم اینجوری تنظیم کردم:
 colModel: [
                    {
                        name: 'KpiTypeID', index: 'KpiTypeID', align: 'right', width: 300,
                        editable: true, edittype: 'select',
                        editoptions: {
                            dataUrl: '/api/KPIType/SelectAllJqTree'
                        },
                        editrules: {
                            required: true
                        }
                    }
              ]
خروجی این رو با جی کوئری که با صفحه اضافه میکنم، یک کنترل صحیح رو تولید میکنه.
$.ajax({
                url: "/api/KPIType/SelectAllJqTree",
                context: document.body
            }).done(function (data) {
                $("#selection").html(data);
            });

اما اینجا بهم پیام میده که 
Uncaught Error: Syntax error, unrecognized expression: "<select><option value='1' >عملکردی</option><option value='2' >بودجه ای</option>
چه نکته ای وجود داره؟ ایا مربوط به webApi هست؟
مطالب
پیاده سازی Conventional UI در ASP.NET MVC
بعد از مدتی کار کردن با فریمورک ASP.NET MVC، شاید ایجاد یک فریمورک شخصی برپایه آن، یکی از باید‌ها برای شما باشد. در این راستا، نظم بخشیدن به ویوها برای جلوگیری از تکرار یکسری کد که اکثرا مورد استفاده قرار میگیرند، نجات بخش خواهد بود.
به تصویر زیر که حاصل از ویو مربوط به ویرایش یک Issue است، توجه فرمایید:

آیا به این نتیجه رسیدید که اصل DRY  را نقض کرده‌ایم؟ بله همین طور است. تکرار کلاس‌های css مربوط به بوت استرپ، تکرار هلپرهای توکار ASP.NET MVC بارها و بارها، خوانایی کد را پایین میارود و در برخی موارد هم خسته کننده خواهد بود. اگر با مباحث مربوط به EditorTemplate‌ها قبلا آشنا شده باشید، خیلی سریع عنوان خواهید کرد که بهتر است از این امکان بهره برد؛ بله درست است. برای این منظور در مسیر Views/Shared/EditorTemplates، فایل cshtml. همنام با نوع داده مد نظر را ایجاد میکنیم.

String.cshtml

@model string
    @Html.TextBox("",ViewData.TemplateInfo.FormattedModelValue,
    new { @class="form-control",placeholder=ViewData.ModelMetadata.Watermark})

Enum.cshtml

@model Enum
@Html.EnumDropDownListFor(m => Model, new { @class = "form-control" })

حال دوباره به نتیجه حاصل از تغییرات اعمال شده توجه کنید:

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

public static class BootstrapHelpers
    {
        public static IHtmlString BootstrapLabelFor<TModel,TProp>(
            this HtmlHelper<TModel> helper,
            Expression<Func<TModel,TProp>> property)
        {
            return helper.LabelFor(property, new
            {
                @class = "col-md-2 control-label"
            });
        }
        public static IHtmlString BootstrapLabel(
            this HtmlHelper helper,
            string propertyName)
        {
            return helper.Label(propertyName, new
            {
                @class = "col-md-2 control-label"
            });
        }
    }

از کلاس بالا برای عدم تکرار کلاس‌های بوت استرپ مربوط به Label، استفاده میشود .

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

خیلی عالی؛ توانستیم از تکرار یکسری از کلاس‌های بوت استرپ خلاص شویم. اما در ادامه با استفاده از یک Object Template به عنوان EditorTemplate برای نوع داده‌های Complex، کار را تمام خواهیم کرد.

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

Object.cshtml

@model dynamic

@foreach (var prop in ViewData.ModelMetadata.Properties
                            .Where(p => p.ShowForEdit))
{
    if (prop.TemplateHint == "HiddenInput")
    {
        @Html.Hidden(prop.PropertyName)
    }
    else
    {
        <div class="form-group">
            @Html.BootstrapLabel(prop.PropertyName)
            <div class="col-md-10">
                @Html.Editor(prop.PropertyName)
                @Html.ValidationMessage(prop.PropertyName)
            </div>
        </div>
    }
}

با استفاده از ViewData.ModelMetadata میتوان به خصوصیات مدل مربوط به ویو دسترسی پیدا کرد که در بالا با استفاده از همین خصوصیت به تمام پراپرتی‌های مدل دسترسی پیدا کرده و مقداری کد تکراری باقی مانده را هم در اینجا کپسوله کردیم.

حال کافی است به شکل زیر عمل کنیم:

در ادامه میتوان با پیاده سازی یک ModelMetadataProvider سفارشی برای اعمال قرارد‌ادهای مورد نیاز، نیز استفاده کرد و همچنین با سفارشی سازی فایل‌های T4 مرتبط با ویوهای تولیدی، به نتایح خیلی بهتری هم دست یافت.
نظرات مطالب
قسمت دوم -- نگاهی دقیق تر به اولین پروژه VC++ (درک مفهوم فایلهای سرآیند و فضای نام ، ویژگیهای زبان ++C و برخی قوانین برنامه نویسی در ++C)
سلام
آیا کد native ویژوال سی پلاس پلاس در سایر سیستم عامل‌ها از جمله لینوکس (ابونتو) ران می‌شوند.
اگر نه که به نظرم جاوا خیلی بهتر از سی پلاس پلاس باشد، چون نسخه سازمانی اش توانایی تولید نرم افزار‌های native قابل اجرا در تمامی سیستم عامل‌ها رو دارد.
بازخوردهای دوره
رها سازی منابع IDisposable در StructureMap
روش پیشنهادی HybridHttpOrThreadLocalScoped است با DisposeAndClearAll
protected void Application_EndRequest(object sender, EventArgs e)
{
   SmObjectFactory.HttpContextDisposeAndClearAll();
}

public static class SmObjectFactory
{
   public static void HttpContextDisposeAndClearAll()
   {
       HttpContextLifecycle.DisposeAndClearAll();
   }
}

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


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

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


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

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


متغیرهای out

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


Ref Locals در C# 7

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

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


Ref Returns در C# 7

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

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

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


مزیت کار با ref returns

ref return‌ها شاید آنچنان در کدهای روزمره‌ی #C بکارگرفته نشوند، اما نیاز به کدهای unsafe و کار مستقیم با pointers را به حداقل می‌رسانند و به آن می‌توان لقب safe pointer را اطلاق کرد؛ از این جهت که این کد هنوز هم یک managed code است و نه یک unsafe code.
private MyBigStruct[] array = new MyBigStruct[10];
private int current;
 
public ref MyBigStruct GetCurrentItem()
{
   return ref array[current];
}
مهم‌ترین مزیت این قابلیت جدید را در قطعه کد فوق ملاحظه می‌کنید. در طراحی بازی‌ها عموما استفاده‌ی از آرایه‌های بزرگ از پیش تخصیص داده شده‌ی structها بسیار مرسوم است (چون میزان مصرف حافظه‌ی کمتری را نسبت به نوع‌های ارجاعی دارند و فشار کمتری را به GC وارد می‌کنند). اکنون با معرفی این قابلیت، دیگر نیازی نیست تا مدام آرایه‌های بزرگی از structها را از قسمتی به قسمت دیگر کپی کرد و سپس بر روی عناصری از آن‌ها عملیاتی را انجام داد و مجددا این حاصل را به مکان مدنظر کپی کرد. در اینجا بدون کپی کردن value types می‌توان با ایجاد ارجاعی به آن‌ها، تغییرات مدنظر را به آن‌ها اعمال کرد.
مطالب
ساخت یک اپلیکیشن ساده ToDo با ASP.NET Identity
یک سناریوی فرضی را در نظر بگیرید. اگر بخواهیم IdentityDbContext و دیگر DbContext‌های اپلیکیشن را ادغام کنیم چه باید کرد؟ مثلا یک سیستم وبلاگ که برخی کاربران می‌توانند پست جدید ثبت کنند، برخی تنها می‌توانند کامنت بگذارند و تمامی کاربران هم اختیارات مشخص دیگری دارند. در چنین سیستمی شناسه کاربران (User ID) در بسیاری از مدل‌ها (موجودیت‌ها و مدل‌های اپلیکیشن) وجود خواهد داشت تا مشخص شود هر رکورد به کدام کاربر متعلق است. در این مقاله چنین سناریو هایی را بررسی می‌کنیم و best practice‌های مربوطه را مرور می‌کنیم.
در این پست یک اپلیکیشن ساده ToDo خواهیم ساخت که امکان تخصیص to-do‌ها به کاربران را نیز فراهم می‌کند. در این مثال خواهیم دید که چگونه می‌توان مدل‌های مختص به سیستم عضویت (IdentityDbContext) را با مدل‌های دیگر اپلیکیشن مخلوط و استفاده کنیم.


تعریف نیازمندی‌های اپلیکیشن

  • تنها کاربران احراز هویت شده قادر خواهند بود تا لیست ToDo‌های خود را ببینند، آیتم‌های جدید ثبت کنند یا داده‌های قبلی را ویرایش و حذف کنند.
  • کاربران نباید آیتم‌های ایجاد شده توسط دیگر کاربران را ببینند.
  • تنها کاربرانی که به نقش Admin تعلق دارند باید بتوانند تمام ToDo‌های ایجاد شده را ببینند.
پس بگذارید ببینیم چگونه می‌شود اپلیکیشنی با ASP.NET Identity ساخت که پاسخگوی این نیازمندی‌ها باشد.
ابتدا یک پروژه ASP.NET MVC جدید با مدل احراز هویت Individual User Accounts بسازید. در این اپلیکیشن کاربران قادر خواهند بود تا بصورت محلی در وب سایت ثبت نام کنند و یا با تامین کنندگان دیگری مانند گوگل و فیسبوک وارد سایت شوند. برای ساده نگاه داشتن این پست ما از حساب‌های کاربری محلی استفاده می‌کنیم.
در مرحله بعد ASP.NET Identity را راه اندازی کنید تا بتوانیم نقش مدیر و یک کاربر جدید بسازیم. می‌توانید با اجرای اپلیکیشن راه اندازی اولیه را انجام دهید. از آنجا که سیستم ASP.NET Identity توسط Entity Framework مدیریت می‌شود می‌توانید از تنظیمات پیکربندی Code First برای راه اندازی دیتابیس خود استفاده کنید.
در قدم بعدی راه انداز دیتابیس را در Global.asax تعریف کنید.
Database.SetInitializer<MyDbContext>(new MyDbInitializer());


ایجاد نقش مدیر و کاربر جدیدی که به این نقش تعلق دارد

اگر به قطعه کد زیر دقت کنید، می‌بینید که در خط شماره 5 متغیری از نوع UserManager ساخته ایم که امکان اجرای عملیات گوناگونی روی کاربران را فراهم می‌کند. مانند ایجاد، ویرایش، حذف و اعتبارسنجی کاربران. این کلاس که متعلق به سیستم ASP.NET Identity است همتای SQLMembershipProvider در ASP.NET 2.0 است.
در خط 6 یک RoleManager می‌سازیم که امکان کار با نقش‌ها را فراهم می‌کند. این کلاس همتای SQLRoleMembershipProvider در ASP.NET 2.0 است.
در این مثال نام کلاس کاربران (موجودیت کاربر در IdentityDbContext) برابر با "MyUser" است، اما نام پیش فرض در قالب‌های پروژه VS 2013 برابر با "ApplicationUser" می‌باشد.
public class MyDbInitializer : DropCreateDatabaseAlways<MyDbContext>
     {
          protected override void Seed(MyDbContext context)
          {
              var UserManager = new UserManager<MyUser>(new 

                                                UserStore<MyUser>(context)); 

              var RoleManager = new RoleManager<IdentityRole>(new 
                                          RoleStore<IdentityRole>(context));
   
              string name = "Admin";
              string password = "123456";
 
   
              //Create Role Admin if it does not exist
              if (!RoleManager.RoleExists(name))
              {
                  var roleresult = RoleManager.Create(new IdentityRole(name));
              }
   
              //Create User=Admin with password=123456
              var user = new MyUser();
              user.UserName = name;
              var adminresult = UserManager.Create(user, password);
   
              //Add User Admin to Role Admin
              if (adminresult.Succeeded)
              {
                  var result = UserManager.AddToRole(user.Id, name);
              }
              base.Seed(context);
          }
      }


حال فایلی با نام Models/AppModels.cs بسازید و مدل EF Code First اپلیکیشن را تعریف کنید. از آنجا که از EF استفاده می‌کنیم، روابط کلید‌ها بین کاربران و ToDo‌ها بصورت خودکار برقرار می‌شود.
public class MyUser : IdentityUser
      {
          public string HomeTown { get; set; }
          public virtual ICollection<ToDo>
                               ToDoes { get; set; }
      }
   
      public class ToDo
      {
          public int Id { get; set; }
          public string Description { get; set; }
          public bool IsDone { get; set; }
          public virtual MyUser User { get; set; }
      }

در قدم بعدی با استفاده از مکانیزم Scaffolding کنترلر جدیدی بهمراه تمام View‌ها و متدهای لازم برای عملیات CRUD بسازید. برای اطلاعات بیشتر درباره  نحوه استفاده از مکانیزم Scaffolding به این لینک مراجعه کنید.
لطفا دقت کنید که از DbContext فعلی استفاده کنید. این کار مدیریت داده‌های Identity و اپلیکیشن شما را یکپارچه‌تر می‌کند. DbContext شما باید چیزی شبیه به کد زیر باشد.
     public class MyDbContext : IdentityDbContext<MyUser>
      {
          public MyDbContext()
              : base("DefaultConnection")
          {
           }
    
           protected override void OnModelCreating(DbModelBuilder modelBuilder)
           {
          public System.Data.Entity.DbSet<AspnetIdentitySample.Models.ToDo> 
                     ToDoes { get; set; }
      }

تنها کاربران احراز هویت شده باید قادر به اجرای عملیات CRUD باشند

برای این مورد از خاصیت Authorize استفاده خواهیم کرد که در MVC 4 هم وجود داشت. برای اطلاعات بیشتر لطفا به این لینک مراجعه کنید.
[Authorize]
public class ToDoController : Controller

کنترلر ایجاد شده را ویرایش کنید تا کاربران را به ToDo‌ها اختصاص دهد. در این مثال تنها اکشن متدهای Create و List را بررسی خواهیم کرد. با دنبال کردن همین روش می‌توانید متدهای Edit و Delete را هم بسادگی تکمیل کنید.
یک متد constructor جدید بنویسید که آبجکتی از نوع UserManager می‌پذیرد. با استفاده از این کلاس می‌توانید کاربران را در ASP.NET Identity مدیریت کنید.
 private MyDbContext db;
          private UserManager<MyUser> manager;
          public ToDoController()
          {
              db = new MyDbContext();
              manager = new UserManager<MyUser>(new UserStore<MyUser>(db));
          }

اکشن متد Create را بروز رسانی کنید

هنگامی که یک ToDo جدید ایجاد می‌کنید، کاربر جاری را در ASP.NET Identity پیدا می‌کنیم و او را به ToDo‌ها اختصاص می‌دهیم.
    public async Task<ActionResult> Create
          ([Bind(Include="Id,Description,IsDone")] ToDo todo)
          {
              var currentUser = await manager.FindByIdAsync
                                                 (User.Identity.GetUserId()); 
              if (ModelState.IsValid)
              {
                  todo.User = currentUser;
                  db.ToDoes.Add(todo);
                  await db.SaveChangesAsync();
                  return RedirectToAction("Index");
              }
   
              return View(todo);
          }

اکشن متد List را بروز رسانی کنید

در این متد تنها ToDo‌های کاربر جاری را باید بگیریم.
          public ActionResult Index()
          {
              var currentUser = manager.FindById(User.Identity.GetUserId());

               return View(db.ToDoes.ToList().Where(
                                   todo => todo.User.Id == currentUser.Id));
          }

تنها مدیران سایت باید بتوانند تمام ToDo‌ها را ببینند

بدین منظور ما یک اکشن متد جدید به کنترل مربوطه اضافه می‌کنیم که تمام ToDo‌ها را لیست می‌کند. اما دسترسی به این متد را تنها برای کاربرانی که در نقش مدیر وجود دارند میسر می‌کنیم.
     [Authorize(Roles="Admin")]
          public async Task<ActionResult> All()
          {
              return View(await db.ToDoes.ToListAsync());
          }

نمایش جزئیات کاربران از جدول ToDo ها

از آنجا که ما کاربران را به ToDo هایشان مرتبط می‌کنیم، دسترسی به داده‌های کاربر ساده است. مثلا در متدی که مدیر سایت تمام آیتم‌ها را لیست می‌کند می‌توانیم به اطلاعات پروفایل تک تک کاربران دسترسی داشته باشیم و آنها را در نمای خود بگنجانیم. در این مثال تنها یک فیلد بنام HomeTown اضافه شده است، که آن را در کنار اطلاعات ToDo نمایش می‌دهیم.
 @model IEnumerable<AspnetIdentitySample.Models.ToDo>
   
  @{
    ViewBag.Title = "Index";
  }
   
  <h2>List of ToDoes for all Users</h2>
  <p>
      Notice that we can see the User info (UserName) and profile info such as HomeTown for the user as well.
      This was possible because we associated the User object with a ToDo object and hence
      we can get this rich behavior.
  12:  </p>
   
  <table class="table">
      <tr>
          <th>
              @Html.DisplayNameFor(model => model.Description)
          </th>
          <th>
              @Html.DisplayNameFor(model => model.IsDone)
          </th>
          <th>@Html.DisplayNameFor(model => model.User.UserName)</th>
          <th>@Html.DisplayNameFor(model => model.User.HomeTown)</th>
      </tr>
  25:   
  26:      @foreach (var item in Model)
  27:      {
  28:          <tr>
  29:              <td>
  30:                  @Html.DisplayFor(modelItem => item.Description)
  31:              </td>
  32:              <td>
                  @Html.DisplayFor(modelItem => item.IsDone)
              </td>
              <td>
                  @Html.DisplayFor(modelItem => item.User.UserName)
              </td>
              <td>
                  @Html.DisplayFor(modelItem => item.User.HomeTown)
              </td>
          </tr>
      }
   
  </table>

صفحه Layout را بروز رسانی کنید تا به ToDo‌ها لینک شود

<li>@Html.ActionLink("ToDo", "Index", "ToDo")</li>
 <li>@Html.ActionLink("ToDo for User In Role Admin", "All", "ToDo")</li>

حال اپلیکیشن را اجرا کنید. همانطور که مشاهده می‌کنید دو لینک جدید به منوی سایت اضافه شده اند.


ساخت یک ToDo بعنوان کاربر عادی

روی لینک ToDo کلیک کنید، باید به صفحه ورود هدایت شوید چرا که دسترسی تنها برای کاربران احراز هویت شده تعریف وجود دارد. می‌توانید یک حساب کاربری محلی ساخته، با آن وارد سایت شوید و یک ToDo بسازید.

پس از ساختن یک ToDo می‌توانید لیست رکوردهای خود را مشاهده کنید. دقت داشته باشید که رکوردهایی که کاربران دیگر ثبت کرده اند برای شما نمایش داده نخواهند شد.


مشاهده تمام ToDo‌ها بعنوان مدیر سایت

روی لینک ToDoes for User in Role Admin کلیک کنید. در این مرحله باید مجددا به صفحه ورود هدایت شوید چرا که شما در نقش مدیر نیستید و دسترسی کافی برای مشاهده صفحه مورد نظر را ندارید. از سایت خارج شوید و توسط حساب کاربری مدیری که هنگام راه اندازی اولیه دیتابیس ساخته اید وارد سایت شوید.
User = Admin
Password = 123456
پس از ورود به سایت بعنوان یک مدیر، می‌توانید ToDo‌های ثبت شده توسط تمام کاربران را مشاهده کنید.

مطالب
مدیریت سشن‌ها در برنامه‌های وب به کمک تزریق وابستگی‌ها
سشن‌ها در برنامه‌های وب، یکی از وابستگی‌های استاتیکی هستند که می‌توان آن‌ها را از طریق تزریق وابستگی‌ها، جهت بالا بردن قابلیت آزمون پذیری برنامه، تامین کرد. همچنین اگر از سشن‌ها برای نمونه در برنامه‌های ASP.NET MVC استفاده کنید، مقدار آن‌ها در سازنده‌ی کنترلرها نال خواهند بود؛ از این جهت که در زمان نمونه سازی یک کنترلر توسط IoC Container، کار مدیریت سشن‌ها صورت نمی‌گیرد و اگر در این بین سرویسی نیاز به سشن داشته باشد، دیگر وهله سازی نخواهد شد؛ به این دلیل که صرفنظر از مقدار دهی متغیر سشن در صفحه‌ای دیگر، این مقدار در سازنده‌ی کلاس، نال است. در ادامه این مشکل را از طریق غنی سازی تزریق وابستگی‌ها با اطلاعات سشن جاری، برطرف خواهیم کرد.


طراحی یک تامین کننده‌ی عمومی سشن

public interface ISessionProvider
{
    object Get(string key);
    T Get<T>(string key) where T : class;
    void Remove(string key);
    void RemoveAll();
    void Store(string key, object value);
}
در اینجا یک اینترفیس عمومی را مشاهده می‌کنید که کار آن کپسوله سازی اعمال متداول کار با سشن‌ها است؛ برای مثال دریافت اطلاعات یک سشن، بر اساس کلیدی مشخص و یا ذخیره سازی اطلاعات اشیاء در سشن‌ها.
یک نمونه پیاده سازی عمومی آن نیز برای کار با سشن‌ها در برنامه‌های وب ASP.NET وب فرم و MVC، می‌تواند به صورت زیر باشد:
public class DefaultWebSessionProvider : ISessionProvider
{
    private readonly HttpSessionStateBase _session;
 
    public DefaultWebSessionProvider(HttpSessionStateBase session)
    {
        _session = session;
    }
 
    public object Get(string key)
    {
        return _session[key];
    }
 
    public T Get<T>(string key) where T : class
    {
        return _session[key] as T;
    }
 
    public void Remove(string key)
    {
        _session.Remove(key);
    }
 
    public void RemoveAll()
    {
        _session.RemoveAll();
    }
 
    public void Store(string key, object value)
    {
        _session[key] = value;
    }
}
در کلاس DefaultWebSessionProvider مستقیما از HttpContext.Current.Session برای دسترسی به سشن جاری استفاده نشده‌است. این مقدار را از سازنده‌ی خود که توسط کلاس پایه HttpSessionStateBase تامین می‌شود، دریافت خواهد کرد. این سازنده را توسط تنظیمات ابتدایی IoC Container خود وهله سازی و مقدار دهی می‌کنیم؛ زیرا HttpContext.Current.Session برای مقدار دهی، نیاز به راه اندازی یک وب سرور دارد و عملا استفاده و شبیه سازی از آن در بسیاری از آزمون‌های واحد، بسیار مشکل خواهد بود.
private static Container defaultContainer()
{
    return new Container(ioc =>
    {
        // session manager setup
        ioc.For<ISessionProvider>().Use<DefaultWebSessionProvider>();
        ioc.For<HttpSessionStateBase>()
           .Use(ctx => new HttpSessionStateWrapper(HttpContext.Current.Session)); 
 
        ioc.Policies.SetAllProperties(properties =>
        {
            properties.OfType<ISessionProvider>();
        });
    });
}
در مثال فوق یک نمونه از تنظیمات ابتدایی StructureMap را برای استفاده از مقدار HttpContext.Current.Session، جهت وهله سازی سازنده‌ی کلاس DefaultWebSessionProvider مشاهده می‌کنید.


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

پس از طراحی تامین کننده‌ی سفارشی سشن و همچنین معرفی آن به IoC Container خود، اکنون استفاده‌ی از آن به سادگی ذیل است:
public class HomeController : Controller
{
    private readonly ISessionProvider _sessionProvider;
    public HomeController(ISessionProvider sessionProvider)
    {
        _sessionProvider = sessionProvider;
    }
بنابراین اگر در کلاسی، کنترلری و یا سرویسی نیاز به سشن وجود داشت، بهتر است از ISessionProvider بجای مقدار دهی و یا دسترسی مستقیم به شیء استاتیک Session استفاده کرد.
نظرات مطالب
ارسال انواع بی نام (Anonymous) بازگشتی توسط Entity framework به توابع خارجی
 من میخام جداول زیر رو به روش بالا با هم join و استفاده کنم

  var query = pro.types.Select(ty => new
       {
           typID = ty.type_id,
           typ_nam = ty.type_nam,
           cat_names = ty.categories.Select(cat => new
           {
               catgID = cat.cat_id,
               catg_name = cat.cat_nam,
               items = pro.items.Select(itm => new
               {
                   item_ID = itm.item_id,
                   item_nam = itm.item_nam,
                   item_path = itm.url,
                   coments = pro.comments.Select(cmm => new
                   {
                       comm_title = cmm.comment_nam,
                       comm_mail = cmm.mail,
                       comm_text = cmm.text,
                   })

               })


           })

       }).ToList();

 bool l = myfunc(query);

  protected bool myfunc(object q)
    {
        var cquery = CreateGenericListFromAnonymous(q,
            new
            {
                typID = 0,
                typ_nam = string.Empty,
                cat_names = CreateEmptyAnonymousIEnumerable(new
                {
                    catgID = 0,
                    catg_name = string.Empty,
                    items = CreateEmptyAnonymousIEnumerable(new
                    {
                        item_ID = 0,
                        item_name = string.Empty,
                        item_path = string.Empty,
                        coments = CreateEmptyAnonymousIEnumerable(new
                        {
                            comm_title = string.Empty,
                            comm_mail = string.Empty,
                            comm_text = string.Empty,

                        })
                    })
                })
            });
        foreach (var item in cquery)
        {
            int ID = item.typID;
            string type_title = item.typ_nam;
            foreach (var Cname in item.cat_names)
            {
                int categoryID = Cname.catgID;
                string category_name = string.Empty;

                foreach (var Iname in Cname.items)
                {
                    int ItmID = Iname.item_ID;
                    string ItmName = Iname.item_name;
                    string ItmPath = Iname.item_path;
                    foreach (var cm in Iname.coments)
                    {
                        string com_title = cm.comm_title;
                        string com_mail = cm.comm_mail;
                        string com_text = cm.comm_text;
                    }
                }
            }
        }
        return true;
    }

مطالب
ساختار داده‌های خطی Linear Data Structure قسمت اول
بعضی از داده‌ها ساختارهای ساده‌ای دارند و به صورت یک صف یا یک نوار ضبط به ترتیب پشت سر هم قرار می‌گیرند؛ مثل ساختاری که صفحات یک کتاب را نگهداری می‌کند. یکی از نمونه‌های این ساختارها، List، صف، پشته و مشتقات آن‌ها می‌باشند.

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

نوع داده انتزاعی Abstraction Data Type -ADT
به زبان خیلی ساده لایه انتزاعی به ما تنها یک تعریف از ساختار مشخص شده‌ای را می‌دهد و هیچگونه پیاده سازی در آن وجود ندارد. برای مثال در لایه انتزاعی، تنها خصوصیت و عملگر‌ها و ... مشخص می‌شوند. ولی کد آن‌ها را پیاده سازی نمی‌کنیم و این باعث می‌شود که از روی این لایه بتوانیم پیاده سازی‌های متفاوت و کارآیی‌های مختلفی را ایجاد کنیم.
ساختار داده‌های مختلف در برنامه نویسی:
  • خطی یا Linear: شامل ساختارهایی چون لیست و صف و پشته است: List ,Queue,Stack
  • درختی یا Tree-Like: درخت باینری ، درخت متوازن و B-Trees
  • Dictionary : شامل یک جفت کلید و مقدار است در جدول هش
  • بقیه: گراف‌ها، صف الویت، bags, Multi bags, multi sets
در این مقاله تنها ساختارهای خطی را دنبال می‌کنیم و در آینده ساختارهای پیچیده‌تری را نیز بررسی خواهیم کرد و نیاز است بررسی کنیم کی و چگونه باید از آن‌ها استفاده کنیم.
ساختارهای لیستی از محبوبترین و پراستفاده‌ترین ساختارها هستند که با اشیاء زیادی در دنیای واقعی سازگاری دارند. مثال زیر را در نظر بگیرید:
قرار است که ما از فروشگاهی خرید کنیم و هر کدام از اجناس (المان‌ها) فروشگاه را که در سبد قرار دهیم، نام آن‌ها در یک لیست ثبت خواهد شد و اگر دیگر المان یا جنسی را از سبد بیرون بگذاریم، از لیست خط خواهد خورد.
همان که گفتیم یک ADT میتواند ساختارهای متفاوتی را پیاده سازی کند. یکی از این ساختارها اینترفیس system.collection.IList است که پیاده سازی آن منجر به ایجاد یک کلاس جدید در سیستم دات نت خواهد شد. پیاده سازی اینترفیس‌ها در سی شارپ، قوانین و قرادادهای خاص خودش را دارد و این قوانین شامل مجموعه‌ای از متد‌ها و خصوصیت‌هاست. برای پیاده سازی هر کلاسی از این اینترفیس‌ها باید این متدها و خصوصیت‌ها را هم در آن پیاده کرد.
با ارث بری از اینترفیس system.collection.IList باید رابط‌های زیر در آن پیاده سازی گردد:
(void Add(object    افزودن المان به آخر لیست 
(void Remove(object   حذف یک المان خاص از لیست  
 ()void Clear    حذف کلیه المان‌ها
( bool Contains(object   شامل این داده میشود یا خیر؟
( void RemoveAt(int  حذف یک المان بر اساس  جایگاه یا اندیسش 
(void Insert(int, object
 افزودن یک المان در جایگاهی (اندیس) خاص بر اساس مقدار position 
(int IndexOf(object اندیس یا جایگاه یک عنصر را بر می‌گرداند
 [this[int ایندکسر ، برای دستریس به عنصر در اندیس مورد نظر

لیست‌های ایستا static Lists
آرایه‌ها می‌توانند بسیاری از خصوصیات ADT را پیاده کنند ولی تفاوت بسیار مهم و بزرگی با آن‌ها دارند و آن این است که لیست به شما اجازه می‌دهد به هر تعدادی که خواستید، المان‌های جدیدی را به آن اضافه کنید؛ ولی یک آرایه دارای اندازه‌ی ثابت Fix است. البته این نکته قابل تامل است که پیاده سازی لیست با آرایه‌ها نیز ممکن است و باید به طور خودکار طول آرایه را افزایش دهید. دقیقا همان اتفاقی که برای stringbuilder در این مقاله توضیح دادیم رخ می‌دهد. به این نوع لیست‌ها، لیست‌های ایستایی که به صورت آرایه ای توسعه پذیر پیاده سازی میشوند می‌گویند. کد زیر پیاده سازی چنین لیستی است:
public class CustomArrayList<T>
{
    private T[] arr;
    private int count;
 
    public int Count
    {
        get
        {
            return this.count;
        }
    }
 
    private const int INITIAL_CAPACITY = 4;
 
    public CustomArrayList(int capacity = INITIAL_CAPACITY)
    {
        this.arr = new T[capacity];
        this.count = 0;
    }
در کد بالا یک آرایه با طول متغیر INITIAL_CAPACITY که پیش فرض آن را 4 گذاشته ایم می‌سازیم و از متغیر count برای حفظ تعداد عناصر آرایه استفاده می‌کنیم و اگر حین افزودن المان جدید باشیم و count بزرگتر از INITIAL_CAPACITY رسیده باشد، باید طول آرایه افزایش پیدا کند که کد زیر نحوه‌ی افزودن المان جدید را نشان می‌دهد. استفاده از حرف T بزرگ مربوط به مباحث Generic هست. به این معنی که المان ورودی می‌تواند هر نوع داده‌ای باشد و در آرایه ذخیره شود.
public void Add(T item)
{
    GrowIfArrIsFull();
    this.arr[this.count] = item;
    this.count++;
} 

public void Insert(int index, T item)
{
    if (index > this.count || index < 0)
    {
        throw new IndexOutOfRangeException(
            "Invalid index: " + index);
    }
    GrowIfArrIsFull();
    Array.Copy(this.arr, index,
        this.arr, index + 1, this.count - index);
    this.arr[index] = item;
    this.count++;
} 

private void GrowIfArrIsFull()
{
    if (this.count + 1 > this.arr.Length)
    {
        T[] extendedArr = new T[this.arr.Length * 2];
        Array.Copy(this.arr, extendedArr, this.count);
        this.arr = extendedArr;
    }
}
 
public void Clear()
{
    this.arr = new T[INITIAL_CAPACITY];
    this.count = 0;
}
در متد Add خط اول با تابع GrowIfArrIsFull بررسی می‌کند آیا خانه‌های آرایه کم آمده است یا خیر؟ اگر جواب مثبت باشد، طول آرایه را دو برابر طول فعلی‌اش افزایش می‌دهد و خط دوم المان جدیدی را در اولین خانه‌ی جدید اضافه شده قرار می‌دهد. همانطور که می‌دانید مقدار count همیشه یکی بیشتر از آخرین اندیس است. پس به این ترتیب مقدار count همیشه به  خانه‌ی بعدی اشاره می‌کند و سپس مقدار count به روز میشود. متد دیگری که در کد بالا وجود دارد insert است که المان جدیدی را در اندیس داده شده قرار می‌دهد. جهت این کار از سومین سازنده‌ی array.copy استفاده می‌کنیم. برای این کار آرایه مبدا و مقصد را یکی در نظر می‌گیریم و از اندیس داده شده به بعد در آرایه فعلی، یک کپی تهیه کرده و در خانه‌ی بعد اندیس داده شده به بعد قرار می‌دهیم. با این کار آرایه ما یک واحد از اندیس داده شده یک خانه، به سمت جلو حرکت می‌کند و الان خانه index و index+1 دارای یک مقدار هستند که در خط بعدی مقدار جدید را داخل آن قرار می‌دهیم و متغیر count را به روز می‌کنیم. باقی موارد را چون پردازش‌های جست و جو، پیدا کردن اندیس یک المان و گزینه‌های حذف، به خودتان واگذار می‌کنم.

لیست‌های پیوندی Linked List - پیاده سازی پویا
همانطور که دیدید لیست‌های ایستا دارای مشکل بزرگی هستند و آن هم این است که با انجام هر عملی بر روی آرایه‌ها مانند افزودن، درج در مکانی خاص و همچنین حذف (خانه ای در آرایه خالی خواهد شد و خانه‌های جلوترش باید یک گام به عقب برگردند) نیاز است که خانه‌های آرایه دوباره مرتب شوند که هر چقدر میزان داده‌ها بیشتر باشد این مشکل بزرگتر شده و ناکارآمدی برنامه را افزایش خواهد داد.
این مشکل با لیست‌های پیوندی حل می‌گردد. در این ساختار هر المان حاوی اطلاعاتی از المان بعدی است و در لیست‌های پیوندی دوطرفه حاوی المان قبلی است. شکل زیر نمایش یک لیست پیوندی در حافظه است:

برای پیاده سازی آن به دو کلاس نیاز داریم. کلاس ListNode برای نگهداری هر المان و اطلاعات المان بعدی به کار می‌رود که از این به بعد به آن Node یا گره می‌گوییم و دیگری کلاس <DynamicList<T برای نگهداری دنباله ای از گره‌ها و متدهای پردازشی آن.

public class DynamicList<T>
{
    private class ListNode
    {
        public T Element { get; set; }
        public ListNode NextNode { get; set; }
 
        public ListNode(T element)
        {
            this.Element = element;
            NextNode = null;
        }
 
        public ListNode(T element, ListNode prevNode)
        {
            this.Element = element;
            prevNode.NextNode = this;
        }
    }
 
    private ListNode head;
    private ListNode tail;
    private int count;
 
    // …
}

از آن جا که نیازی نیست کاربر با کلاس ListNode آشنایی داشته باشد و با آن سر و کله بزند، آن را داخل همان کلاس اصلی به صورت خصوصی استفاده می‌کنیم. این کلاس دو خاصیت دارد؛ یکی برای المان اصلی و دیگر گره بعدی. این کلاس دارای دو سازنده است که اولی تنها برای عنصر اول به کار می‌رود. چون اولین بار است که یک گره ایجاد می‌شود، پس باید خاصیت NextNode یعنی گره بعدی در آن Null باشد و سازنده‌ی دوم برای گره‌های شماره 2 به بعد به کار می‌رود که همراه المان داده شده، گره قبلی را هم ارسال می‌کنیم تا خاصیت NextNode آن را به گره جدیدی که می‌سازیم مرتبط سازد. سه خاصیت کلاس اصلی به نام‌های Count,Tail,Head به ترتیب برای اشاره به اولین گره، آخرین گره و تعداد گره‌ها، به کار می‌روند که در ادامه کد آن‌را در زیر می‌بینیم:

public DynamicList()
{
    this.head = null;
    this.tail = null;
    this.count = 0;
}

public void Add(T item)
{
    if (this.head == null)
    {
        this.head = new ListNode(item);
        this.tail = this.head;
    }
    else
    {
        ListNode newNode = new ListNode(item, this.tail);
        this.tail = newNode;
    }
    this.count++;
}

سازنده مقدار دهی پیش فرض را انجام می‌دهد. در متد Add المان جدیدی باید افزوده شود؛ پس چک می‌کند این المان ارسالی قرار است اولین گره باشد یا خیر؟ اگر head که به اولین گره اشاره دارد Null باشد، به این معنی است که این اولین گره است. پس اولین سازنده‌ی کلاس ListNode را صدا می‌زنیم و آن را در متغیر Head قرار می‌دهیم و چون فقط همین گره را داریم، پس آخرین گره هم شناخته می‌شود که در tail نیز قرار می‌گیرد. حال اگر فرض کنیم المان بعدی را به آن بدهیم، اینبار دیگر Head برابر Null نخواهد بود. پس دومین سازنده‌ی ListNode صدا زده می‌شود که به غیر از المان جدید، باید آخرین گره قبلی هم با آن ارسال شود و گره جدیدی که ایجاد می‌شود در خاصیت NextNode آن نیز قرار بگیرد و در نهایت گره ایجاد شده به عنوان آخرین گره لیست در متغیر Tail نیز قرار می‌گیرد. در خط پایانی هم به هر مدلی که المان جدید به لیست اضافه شده باشد متغیر Count به روز می‌شود.

public T RemoveAt(int index)
{
    if (index >= count || index < 0)
    {
        throw new ArgumentOutOfRangeException(
            "Invalid index: " + index);
    }
 
    int currentIndex = 0;
    ListNode currentNode = this.head;
    ListNode prevNode = null;
    while (currentIndex < index)
    {
        prevNode = currentNode;
        currentNode = currentNode.NextNode;
        currentIndex++;
    }
 

    RemoveListNode(currentNode, prevNode);
 
    return currentNode.Element;
}

private void RemoveListNode(ListNode node, ListNode prevNode)
{
    count--;
    if (count == 0)
    {
        this.head = null;
        this.tail = null;
    }
    else if (prevNode == null)
    {
        this.head = node.NextNode;
    }
    else
    {
        prevNode.NextNode = node.NextNode;
    }

    if (object.ReferenceEquals(this.tail, node))
    {
        this.tail = prevNode;
    }
}

برای حذف یک گره شماره اندیس آن گره را دریافت می‌کنیم و از Head، گره را بیرون کشیده و با خاصیت nextNode آنقدر به سمت جلو حرکت می‌کنیم تا متغیر currentIndex یا اندیس داده شده برابر شود و سپس گره دریافتی و گره قبلی آن را به سمت تابع RemoveListNode ارسال می‌کنیم. کاری که این تابع انجام می‌دهد این است که مقدار NextNode گره فعلی که قصد حذفش را داریم به خاصیت Next Node گره قبلی انتساب می‌دهد. پس به این ترتیب پیوند این گره از لیست از دست می‌رود و گره قبلی به جای اشاره به این گره، به گره بعد از آن اشاره می‌کند. مابقی کد از قبیل جست و برگردان اندیس یک عنصر و ... را به خودتان وگذار می‌کنم.

در روش‌های بالا ما خودمان 2 عدد ADT را پیاده سازی کردیم و متوجه شدیم برای دخیره داده‌ها در حافظه روش‌های متفاوتی وجود دارند که بیشتر تفاوت آن در مورد استفاده از حافظه و کارآیی این روش هاست.


لیست‌های پیوندی دو طرفه Doubly Linked_List

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

این مبحث را در اینجا می‌بندیم و در قسمت بعدی آن را ادامه می‌دهیم.

مطالب
تزریق وابستگی‌ها به صورت پویا در فروشگاه‌ساز Nop Commerce
این روش منحصر به Nop نیست و امکان استفاده‌ی از آن بر روی هر سورس دیگری نیز وجود دارد. همچنین اگر در رابطه با NopCommerce اطلاعاتی ندارید، میتوانید از اینجا جهت آشنا شدن با این فروشگاه ساز Asp.net core استفاده کنید.
همانطور که در جریان هستید، برای اینکه بحث DI را در پروژه داشته باشیم، باید به ازای هر سرویس مشخص کنیم که کدام اینترفیس، به کدام کلاس، map شود. به بیان دیگر باید مشخص کرد هر وقت یک شیء از Container درخواست شد، از چه کلاسی باید این شیء ساخته شود؛ در عین‌حال باید LifeTime وجود شیء در حافظه نیز مشخص شود. حال تصور کنید تعداد سرویس‌های شما در حال زیاد شدن است. در این حالت مجبور هستید دائما این سرویس‌ها را ثبت کنید؛ علاوه بر اینکه باید کدهای تکراری را جهت تعریف این سرویس‌ها بنویسید و باید به‌خاطر بسپارید که سرویس جدید را ثبت کنید. در این مقاله تلاش بر این است تا دیگر نیازی به تعریف کردن تک تک سرویس‌ها نباشد؛ به‌طوری که با رعایت دو قانون کلی بتوان سرویس‌ها را به صورت خودکار ثبت کرد.

مراحل پیاده سازی

 یک اینترفیس را به اسم ICustomService ایجاد کردم که  یک Prop به اسم InjectType دارد و مشخص میکند به چه صورتی این سرویس به ServiceCollection تزریق شود. از طرفی با استفاده از Order، الویت اضافه شدن سرویس به ServiceCollection را مشخص میکنیم و در نهایت با ImplementationType مشخص میکنیم سرویسی که اضافه شده، باید به یک اینترفیس Map شود یا خیر؟ اما مهم‌تر از اینکه ویژگی‌های تزریق وابستگی مشخص شود، مشخص میکند چه سرویس‌هایی توسط ما اضافه شده‌اند و از سرویس‌های nop تفکیک می‌شوند.
namespace Nop.Services
{
    public interface ICustomService
    {
        protected InjectType Inject { get;  }
        protected int Order { get; }
        protected ImplementationType implementationType { get; }
    }
    public enum ImplementationType
    {
        WithInterface = 0,
        WithoutInterface = 1
    }
    public enum InjectType
    {
        Scopped=0,
        Transit=1,
        SingleTon=2
    }
}

قانون اول

برای هر سرویسی که ایجاد میکنیم و میخواهیم به DI معرفی کنیم، آن سرویس باید ICustomService را پیاده سازی کرده باشد؛ دقیقا به خاطر دو دلیلی که در بالا به آن‌ها اشاره شد.

قانون دوم

هر کلاسی که Interface مرتبط به سرویس‌ها را پیاده سازی میکند، باید prop InjectType را در سازنده‌ی خودش مقدار دهی کند. بدین شکل متوجه میشویم از چه طریقی باید تزریق انجام شود. تا اینجا یک چارچوب را مشخص کردیم تا سرویس‌ها را بتوانیم تشخیص دهیم\ اما هنوز کار اصلی باقی مانده‌است. برای نمونه میتوان کد زیر را در نظر گرفت :

namespace Nop.Services
{
    public interface IMyCustomService: ICustomService
    {
        int ok();
    }
}
برای پیاده سازی سرویس ایجاد شده، کد زیر را ایجاد میکنیم :
namespace Nop.Services
{
    public class MyCustomService : IMyCustomService
    {
        public InjectType Inject { get;  }
        public int Order { get;  }
        public ImplementationType implementationType { get;  }

        public MyCustomService()
        {
            implementationType = ImplementationType.WithInterface;
            Inject = InjectType.Scopped;
            Order = 1;
        }
        public int ok()
        {
            return 10;
        }
    }
}

تعیین نقطه شروع

باید نقطه شروع به کار Nop را پیدا کنیم. از آنجایی که با معماری Nop جلو میرویم، با کمی بررسی و دیدن کد‌ها، به کلاسی میرسیم به اسم NopStartup در قسمت Nop.Web.Framework. مسیر دقیق آن: Nop.Web.Framework\Infrastructure\NopStartup.cs. حالا این کلاس چیست؟ در واقع هر کلاسی که از سرویس INopStartup ارث بری کرده باشد، اولویت پیدا میکند و قبل از کدهای دیگر اجرا می‌شود. باید کلاس جدیدی را به اسم مثلا CustomDependencyInjection ایجاد کنیم، با این تفاوت که حتما از کلاس NopStartup ارث بری کرده باشد و همچنین حتما باید متدی را به اسم ConfigureServices، بازنویسی کند. حالا داخل متدی که گفتم باید شروع کنیم به کار.

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

namespace Nop.Web.Framework.Infrastructure
{
    public class CustomDependencyInjection : NopStartup
    {
        private static bool IsSubInterface(Type t1, Type t2)
        {
            if (!t2.IsAssignableFrom(t1))
                return false;

            if (t1.BaseType == null)
                return true;

            return !t2.IsAssignableFrom(t1.BaseType);
        }
        public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
        {
            //-------------Get All Services-------------
            var asm = AppDomain.CurrentDomain
                 .GetAssemblies()
                 .Single(x => x.FullName.Contains("Nop.Services"));
            //-------------find Services that inheriance of ICustomService-------------
            var types = asm.DefinedTypes.Where(x => IsSubInterface(x, typeof(ICustomService)));
            //-----------Get All Custom Service Classess-------
            var allRelatedClassServices = types
                .Where(x => x.IsClass)
                .OrderBy(x=>(Int32)x.GetProperty("Order")
                .GetValue(Activator.CreateInstance(x), null));

            //-----------Get All Custom Service Interfaces-------
            var allRelatedInterfaceServices = types.Where(x => x.IsInterface);
            //-----------Matche Class Services To Related Interface Services-------
            TypeInfo interfaceService=null;
            foreach (var classService in allRelatedClassServices)
            {
                //-----------detect Implementation Type for service-----------
                var implementationValue = (ImplementationType)classService.GetProperty("implementationType")
                   .GetValue(Activator.CreateInstance(classService), null);

                //-----------detect inject type for service-----------
                var InjectValue = (InjectType)classService.GetProperty("Inject")
                   .GetValue(Activator.CreateInstance(classService), null);

                //-----------get related interface for service class-----------
                if (implementationValue == ImplementationType.WithInterface)
                    interfaceService = allRelatedInterfaceServices.Single(x => x.Name == $"I{classService.Name}");

               

                //----------finally Add Custom Service To Service Collection-----------
                switch (InjectValue)
                {
                    case InjectType.Scopped:
                        if(interfaceService!=null)
                            services.AddScoped(interfaceService, classService);
                        else
                            services.AddScoped(classService);
                        break;
                    case InjectType.Transit:
                        if (interfaceService != null)
                            services.AddTransient(interfaceService, classService);
                        else
                            services.AddTransient(classService);
                        break;
                    case InjectType.SingleTon:
                        if (interfaceService != null)
                            services.AddSingleton(interfaceService, classService);
                        else
                            services.AddSingleton(classService);
                        break;
                    default:
                        break;
                }
                interfaceService = null;
            }
        }
       
    }
}
نکته‌ی آخر آن که این داستان‌ها صرفا برای سرویس‌هایی هست که توسط برنامه نویس به پروژه‌ی Nop اضافه می‌شود.

لینک گیت‌هاب