مطالب
EF Code First #8

ادامه بحث بررسی جزئیات نحوه نگاشت کلاس‌ها به جداول، توسط EF Code first


استفاده از Viewهای SQL Server در EF Code first

از Viewها عموما همانند یک جدول فقط خواندنی استفاده می‌شود. بنابراین نحوه نگاشت اطلاعات یک کلاس به یک View دقیقا همانند نحوه نگاشت اطلاعات یک کلاس به یک جدول است و تمام نکاتی که تا کنون بررسی شدند، در اینجا نیز صادق است. اما ...
الف) بر اساس تنظیمات توکار EF Code first، نام مفرد کلاس‌ها، حین نگاشت به جداول، تبدیل به اسم جمع می‌شوند. بنابراین اگر View ما در سمت بانک اطلاعاتی چنین تعریفی دارد:
Create VIEW EmployeesView
AS
SELECT id,
FirstName
FROM Employees

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

using System.ComponentModel.DataAnnotations;

namespace EF_Sample04.Models
{
[Table("EmployeesView")]
public class EmployeesView
{
public int Id { set; get; }
public string FirstName { set; get; }
}
}

در اینجا به کمک ویژگی Table، نام دقیق این View را در بانک اطلاعاتی مشخص کرده‌ایم. به این ترتیب تنظیمات توکار EF بازنویسی خواهد شد و دیگر به دنبال EmployeesViews نخواهد گشت؛ یا جدول متناظر با آن‌را به صورت خودکار ایجاد نخواهد کرد.
ب) View شما نیاز است دارای یک فیلد Primary key نیز باشد.
ج) اگر از مهاجرت خودکار توسط MigrateDatabaseToLatestVersion استفاده کنیم، پیغام خطای زیر را دریافت خواهیم کرد:

There is already an object named 'EmployeesView' in the database.

علت این است که هنوز جدول dbo.__MigrationHistory از وجود آن مطلع نشده است، زیرا یک View، خارج از برنامه و در سمت بانک اطلاعاتی اضافه می‌شود.
برای حل این مشکل می‌توان همانطور که در قسمت‌های قبل نیز عنوان شد، EF را طوری تنظیم کرد تا کاری با بانک اطلاعاتی نداشته باشد:

Database.SetInitializer<Sample04Context>(null);

به این ترتیب EmployeesView در همین لحظه قابل استفاده است.
و یا به حالت امن مهاجرت دستی سوئیچ کنید:
Add-Migration Init -IgnoreChanges
Update-Database

پارامتر IgnoreChanges سبب می‌شود تا متدهای Up و Down کلاس مهاجرت تولید شده، خالی باشد. یعنی زمانیکه دستور Update-Database انجام می‌شود، نه Viewایی دراپ خواهد شد و نه جدول اضافه‌ای ایجاد می‌گردد. فقط جدول dbo.__MigrationHistory به روز می‌شود که هدف اصلی ما نیز همین است.
همچنین در این حالت کنترل کاملی بر روی کلاس‌های Up و Down وجود دارد. می‌توان CreateTable اضافی را به سادگی از این کلاس‌ها حذف کرد.

ضمن اینکه باید دقت داشت یکی از اهداف کار با ORMs، فراهم شدن امکان استفاده از بانک‌های اطلاعاتی مختلف، بدون اعمال تغییری در کدهای برنامه می‌باشد (فقط تغییر کانکشن استرینگ، به علاوه تعیین Provider جدید، باید جهت این مهاجرت کفایت کند). بنابراین اگر از View استفاده می‌کنید، این برنامه به SQL Server گره خواهد خورد و دیگر از سایر بانک‌های اطلاعاتی که از این مفهوم پشتیبانی نمی‌کنند، نمی‌توان به سادگی استفاده کرد.



استفاده از فیلدهای XML اس کیوال سرور

در حال حاضر پشتیبانی توکاری توسط EF Code first از فیلدهای ویژه XML اس کیوال سرور وجود ندارد؛ اما استفاده از آن‌ها با رعایت چند نکته ساده، به نحو زیر است:

using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;

namespace EF_Sample04.Models
{
public class MyXMLTable
{
public int Id { get; set; }

[Column(TypeName = "xml")]
public string XmlValue { get; set; }

[NotMapped]
public XElement XmlValueWrapper
{
get { return XElement.Parse(XmlValue); }
set { XmlValue = value.ToString(); }
}
}
}


در اینجا توسط TypeName ویژگی Column، نوع توکار xml مشخص شده است. این فیلد در طرف کدهای کلاس‌های برنامه، به صورت string تعریف می‌شود. سپس اگر نیاز بود به این خاصیت توسط LINQ to XML دسترسی یافت، می‌توان یک فیلد محاسباتی را همانند خاصیت XmlValueWrapper فوق تعریف کرد. نکته‌ دیگری را که باید به آن دقت داشت، استفاده از ویژگی NotMapped می‌باشد، تا EF سعی نکند خاصیتی از نوع XElement را (یک CLR Property) به بانک اطلاعاتی نگاشت کند.

و همچنین اگر علاقمند هستید که این قابلیت به صورت توکار اضافه شود، می‌توانید اینجا رای دهید!



نحوه تعریف Composite keys در EF Code first

کلاس نوع فعالیت زیر را درنظر بگیرید:

namespace EF_Sample04.Models
{
public class ActivityType
{
public int UserId { set; get; }
public int ActivityID { get; set; }
}
}

در جدول متناظر با این کلاس، نباید دو رکورد تکراری حاوی شماره کاربری و شماره فعالیت یکسانی باهم وجود داشته باشند. بنابراین بهتر است بر روی این دو فیلد، یک کلید ترکیبی تعریف کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample04.Models;

namespace EF_Sample04.Mappings
{
public class ActivityTypeConfig : EntityTypeConfiguration<ActivityType>
{
public ActivityTypeConfig()
{
this.HasKey(x => new { x.ActivityID, x.UserId });
}
}
}

در اینجا نحوه معرفی بیش از یک کلید را در متد HasKey ملاحظه می‌کنید.

یک نکته:
اینبار اگر سعی کنیم مثلا از متد db.ActivityTypes.Find با یک پارامتر استفاده کنیم، پیغام خطای «The number of primary key values passed must match number of primary key values defined on the entity» را دریافت خواهیم کرد. برای رفع آن باید هر دو کلید، در این متد قید شوند:

var activity1 = db.ActivityTypes.Find(4, 1);

ترتیب آن‌ها هم بر اساس ترتیبی که در کلاس ActivityTypeConfig، ذکر شده است، مشخص می‌گردد. بنابراین در این مثال، اولین پارامتر متد Find، به ActivityID اشاره می‌کند و دومین پارامتر به UserId.


بررسی نحوه تعریف نگاشت جداول خود ارجاع دهنده (Self Referencing Entity)

سناریوهای کاربردی بسیاری را جهت جداول خود ارجاع دهنده می‌توان متصور شد و عموما تمام آن‌ها برای مدل سازی اطلاعات چند سطحی کاربرد دارند. برای مثال یک کارمند را درنظر بگیرید. مدیر این شخص هم یک کارمند است. مسئول این مدیر هم یک کارمند است و الی آخر. نمونه دیگر آن، طراحی منوهای چند سطحی هستند و یا یک مشتری را درنظر بگیرید. مشتری دیگری که توسط این مشتری معرفی شده است نیز یک مشتری است. این مشتری نیز می‌تواند یک مشتری دیگر را به شما معرفی کند و این سلسله مراتب به همین ترتیب می‌تواند ادامه پیدا کند.
در طراحی بانک‌های اطلاعاتی، برای ایجاد یک چنین جداولی، یک کلید خارجی را که به کلید اصلی همان جدول اشاره می‌کند، ایجاد خواهند کرد؛ اما در EF Code first چطور؟

using System.Collections.Generic;

namespace EF_Sample04.Models
{
public class Employee
{
public int Id { set; get; }
public string FirstName { get; set; }
public string LastName { get; set; }

//public int? ManagerID { get; set; }
public virtual Employee Manager { get; set; }
}
}

در این کلاس، خاصیت Manager دارای ارجاعی است به همان کلاس؛ یعنی یک کارمند می‌تواند مسئول کارمند دیگری باشد. برای تعریف نگاشت‌ این کلاس به بانک اطلاعاتی می‌توان از روش زیر استفاده کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample04.Models;

namespace EF_Sample04.Mappings
{
public class EmployeeConfig : EntityTypeConfiguration<Employee>
{
public EmployeeConfig()
{
this.HasOptional(x => x.Manager)
.WithMany()
//.HasForeignKey(x => x.ManagerID)
.WillCascadeOnDelete(false);
}
}
}

با توجه به اینکه یک کارمند می‌تواند مسئولی نداشته باشد (خودش مدیر ارشد است)، به کمک متد HasOptional مشخص کرده‌ایم که فیلد Manager_Id را که می‌خواهی به این کلاس اضافه کنی باید نال پذیر باشد. توسط متد WithMany طرف دیگر رابطه مشخص شده است.
اگر نیاز بود فیلد Manager_Id اضافه شده نام دیگری داشته باشد، یک خاصیت nullable مانند ManagerID را که در کلاس Employee مشاهده می‌کنید،‌ اضافه نمائید. سپس در طرف تعاریف نگاشت‌ها به کمک متد HasForeignKey، باید صریحا عنوان کرد که این خاصیت، همان کلید خارجی است. از این نکته در سایر حالات تعاریف نگاشت‌ها نیز می‌توان استفاده کرد، خصوصا اگر از یک بانک اطلاعاتی موجود قرار است استفاده شود و از نام‌های دیگری بجز نام‌های پیش فرض EF استفاده کرده است.




مثال‌های این سری رو از این آدرس هم می‌تونید دریافت کنید: (^)

نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
متد doCallApi نحوه‌ی استفاده‌ی از jwtToken دریافتی پس از لاگین را جهت دسترسی به اکشن متدهای کنترلر محافظت شده‌ی MyProtectedApiController و یا MyProtectedAdminApiController ، نمایش می‌دهد.
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
با سپاس. اگر بخواهیم در متد HandleRequirementAsync  علاوه بر کاربر جاری و نام کنترلر و نام اکشن، مقدار پارامترهای ارسال شده از کلاینت به اکشن را دریافت کنیم تا بتوانیم بر اساس مقدار آن تصمیم بگیریم، چه باید کرد؟
نظرات مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت سوم
من برای جستجو این تغییراتو به سورس شما اضافه کردم؛ اما در کنترلر Search برای دسترسی به متد SearchNews  یا SearchArticle (اضافه کردم)، چطوری باید انجام بدم! 
اصلا این روش درستیه؟
نظرات مطالب
ASP.NET MVC #10
جهت تکمیل: می‌توان دو یا چند متد همنام در یک کنترلر داشت اگر از Verbهای مختلفی استفاده کنند. Post و Get و امثال این، Http Verb هستند.
نظرات مطالب
ASP.NET MVC #13
باتشکر.زمانی که ViewModel ما در یک DLL مجزا قرار میگیره ، چطور می‌تونیم از ویژگی RemoteValidation استفاده کنیم.
[System.Web.Mvc.Remote(action: "CheckUserNameAndEmail",
                               controller: "Customer",
                               AdditionalFields = "Email",
                               HttpMethod = "POST",
                               ErrorMessage = "Username is not available.")]
آیا  ایجاد ارتباط (رفرنس) بین لایه ویوومدل (مستقل) و  برنامه اصلی روش درستی است ؟
بازخوردهای دوره
حذف یک ردیف از اطلاعات به همراه پویانمایی محو شدن اطلاعات آن توسط jQuery در ASP.NET MVC
البته راه دیگری هم پیدا کردم.
به نظرم جالب اومد.
ابتدا یک اکشن فیلتر تعریف شده و در ان هدر درخواست خوانده شده و با محتوی کوکی چک می‌شود در صورتی که برابر باشند عمل انجام خواهد شد با این تفاوت باید توکن در زمان ارسال در هدر درخواست قرار گیرد مانند زیر:
کد اکشن فیلتر:
public class ValidateJsonAntiForgeryTokenAttribute : ActionFilterAttribute
    {
#region Methods (1) 

// Public Methods (1) 

        /// <summary>
        /// Called when [action executing].
        /// </summary>
        /// <param name="actionContext">The action context.</param>
        public void OnActionExecuting(HttpActionContext actionContext)
        {
            try
            {
                var cookieName = AntiForgeryConfig.CookieName;
                var headers = actionContext.Request.Headers;
                var cookie = headers
                    .GetCookies()
                    .Select(c => c[AntiForgeryConfig.CookieName])
                    .FirstOrDefault();
                var rvt = headers.GetValues("__RequestVerificationToken").FirstOrDefault();
                AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt);
            }
            catch
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized request.");
            }
        }

#endregion Methods 
    }
سپس در اکشن دلخواه باید این اکشن فیلتر را بکار برد (در درخواست‌های json)
[HttpPost]
        //[ValidateAntiForgeryToken]
        [ValidateJsonAntiForgeryToken]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public virtual ActionResult DeletePhoto(int? id)
        {
            string ret="error";
            if (id != null && id > 0)
            {
                bool result = _imageGalleryService.Value.Photos.Value.Delete(id.ToInt32());
                ret = result ? "ok" : "nok";
            }
            return Content(ret);
            //return Json(new { ret });
        }
سپس در طرف ویو به صورت زیر عمل شود
$.ajax({
                       ....
                    type: "POST",
                    url: '@Url',
                    data: JSON.stringify({ id: Id }),
                    contentType: "application/json; charset=utf-8",
                    // این قسمت اضافه شود
                    headers: { __RequestVerificationToken: $("input[name=__RequestVerificationToken]").val() },
                    dataType: "json",
                     ..........
امیدوارم برای دوستان هم کاربرد داشته باشد




مطالب
آموزش LightInject IoC Container - قسمت 1
LightInject در حال حاضر یکی از قدرتمند‌ترین IoC Container‌‌ها است که از لحاظ سرعت و کارآیی در بالاترین جایگاه در میان IoC Container‌‌های موجود قرار دارد. جهت بررسی کارایی IoC Container‌ها می‌توانید به این لینک مراجعه کنید . LightInject یک IoC Container فوق العاده سبک وزن می‌باشد که تمامی قابلیت‌های متداولی که از یک Service Container انتظار می‌رود را شامل می‌شود. تنها شامل یک فایل .cs می‌باشد که تمامی کدهای آن در همین یک فایل نوشته شده‌اند. در پروژه‌های کوچک تا بزرگ بدون از دست دادن کارآیی، با بالاترین سرعت ممکن عمل تزریق وابستگی را انجام می‌دهد. در این مجموعه مقالات به بررسی کامل این IoC Container می‌پردازیم و تمامی قابلیت‌های آن را آموزش می‌دهیم.

نحوه نصب و راه اندازی LightInject
در پنجره Package Manager Console می‌توانید با نوشتن دستور ذیل، نسخه باینری آن را نصب کنید که به فایل .dll آن Reference میدهد.

PM> Install-Package LightInject
 همچنین می‌توانید توسط دستور ذیل فایل .cs آن را به پروژه اضافه نمایید. 

PM> Install-Package LightInject.Source

 آماده سازی پروژه نمونه 
قبل از شروع کار با LightInject، یک پروژه Windows Forms Application را با ساختار کلاس‌های ذیل ایجاد نمایید. (در مقالات بعدی و پس از آموزش کامل LightInject نحوه استفاده از آن را در ASP.NET MVC نیز آموزش می‌دهیم)
    public class PersonModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Family { get; set; }
        public DateTime Birth { get; set; }
    }

    public interface IRepository<T> where T:class
    {
        void Insert(T entity);
        IEnumerable<T> FindAll();
    }

    public interface IPersonRepository:IRepository<PersonModel>
    {
    }

    public class PersonRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public interface IPersonService
    {
        void Insert(PersonModel entity);
        IEnumerable<PersonModel> FindAll();
    }

    public class PersonService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public PersonService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            _personRepository.Insert(entity);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
PersonModel: ساختار داده ای جدول Person در سمت Application، که در لایه Domain Model ایجاد می‌گردد.
توجه: جهت سهولت تست و تسریع کدنویسی از لایه بندی و از کلاس‌های ViewModel استفاده نکردیم.
IRepository: یک Interface عمومی برای تمامی Interface‌های مربوط به Repository که عملیات مربوط به پایگاه داده مثل بروزرسانی و واکشی اطلاعات را انجام می‌دهند.
IPersonRepository: واسط بین لایه Service و لایه Repository می‌باشد.
PersonRepository: پیاده سازی واقعی عملیات مربوط به پایگاه داده برای PersonModel می‌باشد. به کلاسهایی که حاوی پیاده سازی واقعی کد می‌باشند Concrete Class می‌گویند.
IPersonService: واسط بین رابط کاربری و لایه سرویس می‌باشد. رابط کاربری به جای دسترسی مستقیم به PersonService از IPersonService استفاده می‌کند.
PersonService: دریافت درخواست‌های رابط کاربری و بررسی قوانین تجاری، سپس ارسال درخواست به لایه Repository در صورت صحت درخواست، و در نهایت ارسال پاسخ دریافتی به رابط کاربری. در واقع واسطی بین Repository و UI می‌باشد.
پس از ایجاد ساختار فوق کد مربوط به Form1 را بصورت زیر تغییر دهید.
public partial class Form1 : Form
    {
        private readonly IPersonService _personService;
        public Form1(IPersonService personService)
        {
            _personService = personService;
            InitializeComponent();
        }
    }
توضیحات
در کد فوق به منظور ارتباط با سرویس از IPersonService استفاده نمودیم که به عنوان پارامتر ورودی برای سازنده Form1 تعریف شده است. حتما با Dependency Inversion و انواع Dependency Injection آشنا هستید که به سراغ مطالعه این مقاله آمدید و علت این نوع کدنویسی را هم می‌دانید. بنابراین توضیح بیشتری در این مورد نمی‌دهم.
حال اگر برنامه را اجرا کنید در Program.cs با خطای عدم وجود سازنده بدون پارامتر برای Form1 مواجه می‌شوید که کد آن را باید به صورت زیر تغییر می‌دهیم.
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonRepository, PersonRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
        }
توضیحات
کلاس ServiceContainer وظیفه‌ی Register کردن یک کلاس را برای یک Interface دارد. زمانی که می‌خواهیم Form1 را نمونه سازی نماییم و Application را راه اندازی کنیم، باید نمونه ای را از جنس IPersonService ایجاد نموده و به سازنده‌ی Form1 ارسال نماییم. با رعایت اصل DIP، نمونه سازی واقعی یک کلاس لایه دیگر، نباید در داخل کلاس‌های لایه جاری انجام شود. برای این منظور از شیء container استفاده نمودیم و توسط متد GetInstance، نمونه‌ای از جنس IPersonService را ایجاد نموده و به Form1 پاس دادیم. حال container از کجا متوجه می‌شود که چه کلاسی را برای IPersonService نمونه سازی نماید؟
در خطوط قبلی توسط متد Register، کلاس PersonService را برای IPersonService ثبت نمودیم. container نیز برای نمونه سازی به کلاس هایی که برایش Register نمودیم مراجعه می‌نماید و نمونه سازی را انجام می‌دهد. جهت استفاده از PersonService به پارامتر ورودی IPersonRepository برای سازنده‌ی آن نیاز داریم که کلاس PersonRepository را برای IPersonRepository ثبت کردیم.
حال اگر برنامه را اجرا کنید، به درستی اجرا خواهد شد. برنامه را متوقف کنید و به کد موجود در Program.cs مراجعه نموده و دو خط مربوط به Register را Comment نمایید. سپس برنامه را اجرا کنید و خطای تولید شده را ببینید. این خطا بیان می‌کند که امکان نمونه سازی برای IPersonService را ندارد. چون قبلا هیچ کلاسی را برای آن Register نکرده ایم.
Named Services
در برخی مواقع، بیش از یک کلاس وجود دارند که ممکن است از یک Interface ارث بری نمایند. در این حالت و در زمان Register، باید به ServiceContainer بگوییم که کدام کلاس را باید نمونه سازی نماید. برای بررسی این موضوع، کلاسهای زیر را به ساختار پروژه اضافه نمایید.
    public class WorkerModel:PersonModel
    {
        public ManagerModel Manager { get; set; }
    }

    public class ManagerModel:PersonModel
    {
        public IEnumerable<WorkerModel> Workers { get; set; }
    }

    public class WorkerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class ManagerRepository:IPersonRepository
    {
        public void Insert(PersonModel entity)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<PersonModel> FindAll()
        {
            throw new NotImplementedException();
        }
    }

    public class WorkerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public WorkerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var worker = entity as WorkerModel;
            _personRepository.Insert(worker);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }

    public class ManagerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

        public ManagerService(IPersonRepository personRepository)
        {
            _personRepository = personRepository;
        }

        public void Insert(PersonModel entity)
        {
            var manager = entity as ManagerModel;
            _personRepository.Insert(manager);
        }

        public IEnumerable<PersonModel> FindAll()
        {
            return _personRepository.FindAll();
        }
    }
توضیحات
دو کلاس Manager و Worker به همراه سرویس‌ها و Repository هایشان اضافه شده اند که از IPersonService و IPersonRepository مشتق شده اند.
حال کد کلاس Program را به صورت زیر تغییر می‌دهیم
...
 var container = new ServiceContainer();
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>()));
توضیحات
در کد فوق، چون WorkerService بعد از PersonService ثبت یا Register شده است، LightInject در زمان ارسال پارامتر به Form1، نمونه ای از کلاس WorkerService را ایجاد میکند. اما اگر بخواهیم از کلاس PersonService نمونه سازی نماید باید کد را به صورت زیر تغییر دهیم.
...
            container.Register<IPersonService, PersonService>("PersonService");
            container.Register<IPersonService, WorkerService>();
            container.Register<IPersonRepository, PersonRepository>();
            container.Register<IPersonRepository, WorkerRepository>();
            Application.Run(new Form1(container.GetInstance<IPersonService>("PersonService")));
همانطور که مشاهده می‌نمایید، در زمان Register نامی را به آن اختصاص دادیم که در زمان نمونه سازی از این نام استفاده شده است.
اگر در زمان ثبت، نامی را به نمونه‌ی مورد نظر اختصاص داده باشیم، و فقط یک Register برای آن Interface معرفی نموده باشیم، در زمان نمونه سازی، LightInject آن نمونه را به عنوان سرویس پیش فرض در نظر می‌گیرد.
  container.Register<IPersonService, PersonService>("PersonService");
  Application.Run(new Form1(container.GetInstance<IPersonService>()));
در کد فوق، چون برای IPersonService فقط یک کلاس برای نمونه سازی معرفی شده است، با فراخوانی متد GetInstance، حتی بدون ذکر نام، نمونه ای را از کلاس PersonService ایجاد می‌کند.
IEnumerable<T>
زمانی که چند کلاس را که از یک Interface مشتق شده اند، با هم Register می‌نمایید، LightInject این قابلیت را دارد که این کلاس‌های Register شده را در قالب یک لیست شمارشی برگردانید.
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetInstance<IEnumerable<IPersonService>>();
در کد فوق لیستی با دو آیتم ایجاد می‌شود که یک آیتم از نوع PersonService و دیگری از نوع WorkerService می‌باشد. همچنین از کد زیر نیز می‌توانید استفاده کنید:
            container.Register<IPersonService, PersonService>();
            container.Register<IPersonService, WorkerService>("WorkerService");
            var personList = container.GetAllInstances<IPersonService>();
به جای متد GetInstance از متد GetAllInstances استفاده شده است.
LightInject از Collection‌های زیر نیز پشتیبانی می‌نماید:
  • Array
  • ICollection<T>
  • IList<T>
  • IReadOnlyCollection<T>
  • IReadOnlyList<T>
Values
توسط LightInject می‌توانید مقادیر ثابت را نیز تعریف کنید
            container.RegisterInstance<string>("SomeValue");
            var value = container.GetInstance<string>();
متغیر value با رشته "SomeValue" مقداردهی می‌گردد. اگر چندین ثابت رشته ای داشته باشید می‌توانید نام جداگانه ای را به هر کدام اختصاص دهید و در زمان فراخوانی مقدار به آن نام اشاره کنید.
            container.RegisterInstance<string>("SomeValue","String1");
            container.RegisterInstance<string>("OtherValue","String2");
            var value = container.GetInstance<string>("String2");
متغیر value با رشته "OtherValue" مقداردهی می‌گردد.
مطالب
آشنایی با الگوی MVP

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

یکی از الگوهایی که شیوه‌ی صحیح این جدا سازی را ترویج می‌کند، الگوی MVP یا Model-View-Presenter می‌باشد. خلاصه‌ی این الگو به صورت زیر است:


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

View :
من می‌دانم که چگونه باید اطلاعاتی را به کاربر به شکلی بصری ارائه داد.
من می‌دانم که چگونه باید اعمالی مانند data binding و امثال آن را انجام داد.
من نمی‌دانم که چگونه باید منطق پردازشی موارد ذکر شده را فراهم آورم.

Presenter :
من می‌دانم که چگونه باید درخواست‌های رسیده کاربر به View را دریافت کرده و آن‌ها را به Model‌ انتقال دهم.
من می‌دانم که چگونه باید اطلاعات را به Model ارسال کرده و سپس نتیجه‌ی پردازش آن‌ها را جهت نمایش در اختیار View قرار دهم.
من نمی‌دانم که چگونه باید اطلاعاتی را ترسیم کرد (مشکل View است نه من) و نمی‌دانم که چگونه باید پردازشی را بر روی اطلاعات انجام دهم. (مشکل Model است و اصلا ربطی به اینجانب ندارد!)


یک مثال ساده از پیاده سازی این روش
برنامه‌ای وبی را بنویسید که پس از دریافت شعاع یک دایره از کاربر، مساحت ‌آن‌را محاسبه کرده و نمایش دهد.
یک تکست باکس در صفحه قرار خواهیم داد (txtRadius) و یک دکمه جهت دریافت درخواست کاربر برای نمایش نتیجه حاصل در یک برچسب به نام lblResult

الف) پیاده سازی به روش متداول (اسپاگتی کد)

protected void btnGetData_Click(object sender, EventArgs e)
{
lblResult.Text = (Math.PI * double.Parse(txtRadius.Text) * double.Parse(txtRadius.Text)).ToString();
}
بله! کار می‌کنه!
اما این مشکلات را هم دارد:
- منطق برنامه (روش محاسبه مساحت دایره) با رابط کاربر گره خورده.
- کدهای برنامه در پروژه‌ی دیگری قابل استفاده نیست. (شما متد یا کلاسی را این‌جا با قابلیت استفاده مجدد می‌توانید پیدا می‌کنید؟ آیا یکی از اهداف برنامه نویسی شیءگرا تولید کدهایی با قابلیت استفاده مجدد نبود؟)
- چگونه باید برای آن آزمون واحد نوشت؟

ب) بهبود کد و جدا سازی لایه‌ها از یکدیگر

در روش MVP متداول است که به ازای هر یک از اجزاء ابتدا یک interface نوشته شود و سپس این اینترفیس‌ها پیاده سازی گردد.

پیاده سازی منطق برنامه:

1- ایجاد Model :
یک فایل جدید را به نام CModel.cs به پروژه اضافه کرده و کد زیر را به آن خواهیم افزود:

using System;

namespace MVPTest
{
public interface ICircleModel
{
double GetArea(double radius);
}

public class CModel : ICircleModel
{
public double GetArea(double radius)
{
return Math.PI * radius * radius;
}
}
}
همانطور که ملاحظه می‌کنید اکنون منطق برنامه از موارد زیر اطلاعی ندارد:
- خبری از textbox و برچسب و غیره نیست. اصلا نمی‌داند که رابط کاربری وجود دارد یا نه.
- خبری از رخ‌دادهای برنامه و پاسخ دادن به آن‌ها نیست.
- از این کد می‌توان مستقیما و بدون هیچ تغییری در برنامه‌های دیگر هم استفاده کرد.
- اگر باگی در این قسمت وجود دارد، تنها این کلاس است که باید تغییر کند و بلافاصله کل برنامه از این بهبود حاصل شده می‌تواند بدون هیچگونه تغییری و یا به هم ریختگی استفاده کند.
- نوشتن آزمون واحد برای این کلاس که هیچگونه وابستگی به UI ندارد ساده است.


2- ایجاد View :
فایل دیگری را به نام CView.cs را به همراه اینترفیس زیر به پروژه اضافه می‌کنیم:

namespace MVPTest
{
public interface IView
{
string RadiusText { get; set; }
string ResultText { get; set; }
}
}

کار View دریافت ابتدایی مقادیر از کاربر توسط RadiusText و نمایش نهایی نتیجه توسط ResultText است البته با یک اما.
View نمی‌داند که چگونه باید این پردازش صورت گیرد. حتی نمی‌داند که چگونه باید این مقادیر را به Model جهت پردازش برساند یا چگونه آن‌ها را دریافت کند (به همین جهت از اینترفیس برای تعریف آن استفاده شده).

3- ایجاد Presenter :
در ادامه فایل جدیدی را به نام CPresenter.cs‌ با محتویات زیر به پروژه خواهیم افزود:

namespace MVPTest
{
public class CPresenter
{
IView _view;

public CPresenter(IView view)
{
_view = view;
}

public void CalculateCircleArea()
{
CModel model = new CModel();
_view.ResultText = model.GetArea(double.Parse(_view.RadiusText)).ToString();
}
}
}

کار این کلاس برقراری ارتباط با Model است.
می‌داند که چگونه اطلاعات را به Model ارسال کند (از طریق _view.RadiusText) و می‌داند که چگونه نتیجه‌ی پردازش را در اختیار View قرار دهد. (با انتساب آن به _view.ResultText)
نمی‌داند که چگونه باید این پردازش صورت گیرد (کار مدل است نه او). نمی‌داند که نتیجه‌ی نهایی را چگونه نمایش دهد (کار View است نه او).
روش معرفی View به این کلاس به constructor dependency injection معروف است.

اکنون کد وب فرم ما که در قسمت (الف) معرفی شده به صورت زیر تغییر می‌کند:

using System;

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

public string RadiusText
{
get { return txtRadius.Text; }
set { txtRadius.Text = value; }
}
public string ResultText
{
get { return lblResult.Text; }
set { lblResult.Text = value; }
}

protected void btnGetData_Click(object sender, EventArgs e)
{
CPresenter presenter = new CPresenter(this);
presenter.CalculateCircleArea();
}
}
}

در این‌جا یک وهله از Presenter برای برقراری ارتباط با Model ایجاد می‌شود. همچنین کلاس وب فرم ما اینترفیس View را نیز پیاده سازی خواهد کرد.

مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت اول - معرفی، نصب و تعریف قواعد اعتبارسنجی
روش مرسوم اعتبارسنجی اطلاعات مدل‌های ASP.NET Core، با استفاده از data annotations توکار آن است که در بسیاری از موارد هم به خوبی کار می‌کند. اما اگر به دنبال ویژگی‌های دیگری مانند نوشتن آزمون‌های واحد برای اعتبارسنجی اطلاعات، جداسازی شرط‌های اعتبارسنجی از تعاریف مدل‌ها، نوشتن اعتبارسنجی‌های پیچیده به همراه تزریق وابستگی‌ها هستید، کتابخانه‌ی FluentValidation می‌تواند جایگزین بهتر و بسیار کاملتری باشد.


نصب کتابخانه‌ی FluentValidation در پروژه

فرض کنید پروژه‌ی ما از سه پوشه‌ی FluentValidationSample.Web، FluentValidationSample.Models و FluentValidationSample.Services تشکیل شده‌است که اولی یک پروژه‌ی MVC است و دو مورد دیگر classlib هستند.
در پروژه‌ی FluentValidationSample.Models، بسته‌ی نیوگت کتابخانه‌ی FluentValidation را به صورت زیر نصب می‌کنیم:
dotnet add package FluentValidation.AspNetCore


جایگزین کردن سیستم اعتبارسنجی مبتنی بر DataAnnotations با FluentValidation

اکنون فرض کنید در پروژه‌ی Models، مدل ثبت‌نام زیر را اضافه کرده‌ایم که از همان data annotations توکار و استاندارد ASP.NET Core برای اعتبارسنجی اطلاعات استفاده می‌کند:
using System.ComponentModel.DataAnnotations;

namespace FluentValidationSample.Models
{
    public class RegisterModel
    {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email")]
        [EmailAddress]
        public string Email { get; set; }

        [Range(18, 60)]
        [Display(Name = "Age")]
        public int Age { get; set; }
    }
}
برای جایگزین کردن data annotations اعتبارسنجی اطلاعات با روش FluentValidation، می‌توان به صورت زیر عمل کرد:
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.UserName).NotNull();
            RuleFor(x => x.Password).NotNull().Length(6, 100);
            RuleFor(x => x.ConfirmPassword).Equal(x => x.Password);
            RuleFor(x => x.Email).EmailAddress();
            RuleFor(x => x.Age).InclusiveBetween(18, 60);
        }
    }
}
برای این منظور ابتدا یک کلاس Validator را با ارث بری از AbstractValidator از نوع مدلی که می‌خواهیم قواعد اعتبارسنجی آن‌را مشخص کنیم، ایجاد می‌کنیم. سپس در سازنده‌ی آن، می‌توان به متدهای تعریف شده‌ی در این کلاس پایه دسترسی یافت.
در اینجا در ابتدا به ازای هر خاصیت کلاس مدل مدنظر، یک RuleFor تعریف می‌شود که با استفاده از static reflection، امکان تعریف strongly typed آن‌ها وجود دارد. سپس ویژگی Required به متد NotNull تبدیل می‌شود و ویژگی StringLength توسط متد Length قابل تعریف خواهد بود و یا ویژگی Compare توسط متد Equal به صورت strongly typed به خاصیت دیگری متصل می‌شود.

پس از این تعاریف، می‌توان ویژگی‌های اعتبارسنجی اطلاعات را از مدل ثبت نام حذف کرد و تنها ویژگی‌های خاص Viewهای MVC را در صورت نیاز باقی گذاشت:
using System.ComponentModel.DataAnnotations;

namespace FluentValidationSample.Models
{
    public class RegisterModel
    {
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        public string ConfirmPassword { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email")]
        public string Email { get; set; }

        [Display(Name = "Age")]
        public int Age { get; set; }
    }
}


تعریف پیام‌های سفارشی اعتبارسنجی

روش تعریف پیام‌های سفارشی شکست اعتبارسنجی اطلاعات را توسط متد WithMessage در ادامه مشاهده می‌کنید:
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.UserName)
                .NotNull()
                    .WithMessage("Your first name is required.")
                .MaximumLength(20)
                    .WithMessage("Your first name is too long!")
                .MinimumLength(3)
                    .WithMessage(registerModel => $"Your first name `{registerModel.UserName}` is too short!");

            RuleFor(x => x.Password)
                .NotNull()
                    .WithMessage("Your password is required.")
                .Length(6, 100);

            RuleFor(x => x.ConfirmPassword)
                .NotNull()
                    .WithMessage("Your confirmation password is required.")
                .Equal(x => x.Password)
                    .WithMessage("The password and confirmation password do not match.");

            RuleFor(x => x.Email).EmailAddress();
            RuleFor(x => x.Age).InclusiveBetween(18, 60);
        }
    }
}
به ازای هر متد تعریف یک قاعده‌ی اعتبارسنجی جدید، بلافاصله می‌توان از متد WithMessage نیز استفاده کرد. همچنین این متد می‌تواند به اطلاعات اصل model دریافتی نیز همانند پیام سفارشی مرتبط با MinimumLength نام کاربری، دسترسی پیدا کند.


روش تعریف اعتبارسنجی‌های سفارشی خواص مدل

فرض کنید می‌خواهیم یک کلمه‌ی عبور وارد شده‌ی معتبر، حتما از جمع حروف کوچک، بزرگ، اعداد و symbols تشکیل شده باشد. برای این منظور می‌توان از متد Must استفاده کرد:
using System.Text.RegularExpressions;
using FluentValidation;

namespace FluentValidationSample.Models
{
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            RuleFor(x => x.Password)
                .NotNull()
                    .WithMessage("Your password is required.")
                .Length(6, 100)
                .Must(password => hasValidPassword(password));
            //...

        }

        private static bool hasValidPassword(string password)
        {
            var lowercase = new Regex("[a-z]+");
            var uppercase = new Regex("[A-Z]+");
            var digit = new Regex("(\\d)+");
            var symbol = new Regex("(\\W)+");
            return lowercase.IsMatch(password) &&
                    uppercase.IsMatch(password) &&
                    digit.IsMatch(password) &&
                    symbol.IsMatch(password);
        }
    }
}
متد Must، می‌تواند مقدار خاصیت متناظر را نیز در اختیار ما قرار دهد و بر اساس آن مقدار می‌توان خروجی true/false ای را بازگشت داد تا نشان شکست و یا موفقیت آمیز بودن اعتبارسنجی اطلاعات باشد.

البته lambda expression نوشته شده را می‌توان توسط method groups، به صورت زیر نیز خلاصه نوشت:
RuleFor(x => x.Password)
    .NotNull()
        .WithMessage("Your password is required.")
    .Length(6, 100)
    .Must(hasValidPassword);


انتقال تعاریف اعتبارسنج‌های سفارشی خواص به کلاس‌های مجزا

اگر نیاز به استفاده‌ی از متد hasValidPassword در کلاس‌های دیگری نیز وجود دارد، می‌توان اینگونه اعتبارسنجی‌های سفارشی را به کلاس‌های مجزایی نیز تبدیل کرد. برای مثال فرض کنید که می‌خواهیم ایمیل دریافت شده، فقط از یک دومین خاص قابل قبول باشد.
using System;
using FluentValidation;
using FluentValidation.Validators;

namespace FluentValidationSample.Models
{
    public class EmailFromDomainValidator : PropertyValidator
    {
        private readonly string _domain;

        public EmailFromDomainValidator(string domain)
            : base("Email address {PropertyValue} is not from domain {domain}")
        {
            _domain = domain;
        }

        protected override bool IsValid(PropertyValidatorContext context)
        {
            if (context.PropertyValue == null) return false;
            var split = context.PropertyValue.ToString().Split('@');
            return split.Length == 2 && split[1].Equals(_domain, StringComparison.OrdinalIgnoreCase);
        }
    }
}
برای این منظور یک کلاس جدید را با ارث‌بری از PropertyValidator تعریف شده‌ی در فضای نام FluentValidation.Validators، ایجاد می‌کنیم. سپس متد IsValid آن‌را بازنویسی می‌کنیم تا برای مثال ایمیل‌ها را صرفا از دومین خاصی بپذیرد.
PropertyValidatorContext امکان دسترسی به نام و مقدار خاصیت در حال اعتبارسنجی را میسر می‌کند. همچنین مقدار کل model جاری را نیز به صورت یک object در اختیار ما قرار می‌دهد.

اکنون برای استفاده‌ی از آن می‌توان از متد SetValidator استفاده کرد:
RuleFor(x => x.Email)
    .SetValidator(new EmailFromDomainValidator("gmail.com"));
و یا حتی می‌توان یک متد الحاقی fluent را نیز برای آن طراحی کرد تا SetValidator را به صورت خودکار فراخوانی کند:
    public static class CustomValidatorExtensions
    {
        public static IRuleBuilderOptions<T, string> EmailAddressFromDomain<T>(
            this IRuleBuilder<T, string> ruleBuilder, string domain)
        {
            return ruleBuilder.SetValidator(new EmailFromDomainValidator(domain));
        }
    }
سپس تعریف قاعده‌ی اعتبارسنجی ایمیل‌ها به صورت زیر تغییر می‌کند:
RuleFor(x => x.Email).EmailAddressFromDomain("gmail.com");


تعریف قواعد اعتبارسنجی خواص تو در تو و لیستی

فرض کنید به RegisterModel این قسمت، دو خاصیت آدرس و شماره تلفن‌ها نیز اضافه شده‌است که یکی به شیء آدرس و دیگری به مجموعه‌ای از آدرس‌ها اشاره می‌کند:
    public class RegisterModel
    {
        // ...

        public Address Address { get; set; }

        public ICollection<Phone> Phones { get; set; }
    }

    public class Phone
    {
        public string Number { get; set; }
        public string Description { get; set; }
    }

    public class Address
    {
        public string Location { get; set; }
        public string PostalCode { get; set; }
    }
در یک چنین حالتی، ابتدا به صورت متداول، قواعد اعتبارسنجی Phone و Address را جداگانه تعریف می‌کنیم:
    public class PhoneValidator : AbstractValidator<Phone>
    {
        public PhoneValidator()
        {
            RuleFor(x => x.Number).NotNull();
        }
    }

    public class AddressValidator : AbstractValidator<Address>
    {
        public AddressValidator()
        {
            RuleFor(x => x.PostalCode).NotNull();
            RuleFor(x => x.Location).NotNull();
        }
    }
سپس برای تعریف اعتبارسنجی دو خاصیت پیچیده‌ی اضافه شده، می‌توان از همان متد SetValidator استفاده کرد که اینبار پارامتر ورودی آن، نمونه‌ای از AbstractValidator‌های هرکدام است. البته برای خاصیت مجموعه‌ای اینبار باید با متد RuleForEach شروع کرد:
    public class RegisterModelValidator : AbstractValidator<RegisterModel>
    {
        public RegisterModelValidator()
        {
            // ...

            RuleFor(x => x.Address).SetValidator(new AddressValidator());

            RuleForEach(x => x.Phones).SetValidator(new PhoneValidator());
        }


در قسمت بعد، روش‌های مختلف استفاده‌ی از قواعد اعتبارسنجی تعریف شده را در یک برنامه‌ی ASP.NET Core بررسی می‌کنیم.



برای مطالعه‌ی بیشتر
- «FluentValidation #1»