آی تی نرد

اشتراک اطلاعات و تجربیات در زمینه ی توسعه ی دات نت و البته شیرپوینت

استفاده از ماژول Ninject.Extensions.Factory و الگوی Factory در زمان اعتبارسنجی Model در MVC

استفاده از الگوی Repository و پیاده سازی Dependency Injection با استفاده از کتابخانه ی Ninject برروی یک پروژه ی نسبتا بزرگ MVC بسیار کارامد خواهد بود.
به هرحال هر کدوم از این الگوها مزایای مربوط به خودش رو خواهد داشت. استفاده از الگوی اشاره شده به مراتب بیشتر از الگوهای دیگر در برنامه هایی که محوریت بیشتر آن برروی استفاده و تحلیل داده ها در بانک های اطلاعاتی هست می باشد، و استفاده از Ninject هم که مزایای خودش رو به این پروژه اضافه خواهد کرد.

بخش مهم اعتبارسنجی داده ها قبل از افزوده شدن به منبع ذخیره سازی نهایی مورد توجه است، در کلاس های Metadata که از طریق Attribute ها یا DataAnnotations اعتبار سنجی های اولیه صورت میگیرد زمانی نیاز به پیاده سازی یک اعتبارسنجی سفارشی و استفاده از Repository و استخراج داده های مورد نیاز به عنوان بخشی از این اعتبارسنجی می باشد. که متاسفانه عملیات ایجاد نمونه ی جدید کلاس Repository از طریق Field Injection یا هر نوع دیگری از Injection صورت نمی گیرد و با خطای null reference exception مواجه خواهید شد.

برای روشن کردن توضیحاتم یه مثال بزنم:
سناریویی رو در نظر بگیرید که در اون مشتری میخواد یک محصول رو خریداری کنه، اما باید بررسی بشه که اگر در لحظه ی خرید، تعداد محصول برابر با 0 هست پیغام خطای مناسب نمایش داده شود.
بنابراین ما یک کلاس Customer و یک کلاس Product که مربوط به اعتبارسنجی می باشد به شکل زیر خواهیم داشت،

کلاس مشتری:

   [MetadataType(typeof(CustomerMetadata))]
   public partial class Customer { }

   public partial class CustomerMetadata
   {
      [ScaffoldColumn(false)]
      public int ID { get; set; }

      [Required(ErrorMessage = MetadataErrorMessages.Required)]
      [StringLength(50, ErrorMessage = MetadataErrorMessages.ExceedMaxLength)]
      public string FirstName { get; set; }

      [Required(ErrorMessage = MetadataErrorMessages.Required)]
      [StringLength(50, ErrorMessage = MetadataErrorMessages.ExceedMaxLength)]
      public string LastName { get; set; }	  
	  
      [ScaffoldColumn(false)]
      public int ProductId { get; set; }	
	  
      public virtual Product Product { get; set; }
   }

کلاس محصول:

   [MetadataType(typeof(ProductMetadata))]
   public partial class Product { }

   public partial class ProductMetadata
   {
      [ScaffoldColumn(false)]
      public int ID { get; set; }

      [Required(ErrorMessage = MetadataErrorMessages.Required)]
      [StringLength(100, ErrorMessage = MetadataErrorMessages.ExceedMaxLength)]
      public string Name { get; set; }
	  
      [Required(ErrorMessage = MetadataErrorMessages.Required)]
      public int Count { get; set; }
   }

در این کلاس ها اعتبارسنجی های اولیه از طریق خصوصیات Required و StringLenght صورت گرفته است. اما اگر بخواهیم در لحظه ی ثبت اطلاعات مشتری تعداد محصول را بررسی کنیم(البته صحیح تر این بود که یک کلاس واسط فروش هم در نظر گرفته میشد اما برای هرچه ساده تر شدن به همین صورت هم کفایت میکنه) و از اونجایی که این داده(Count) رو در اختیار نداریم می بایست با استفاده از اینترفیس IValidatableObject یک اعتبارسنجی جدید برروی کلاس مشتری تعریف کنیم به صورت زیر:

   [MetadataType(typeof(CustomerMetadata))]
   public partial class Customer : IValidatableObject 
   { 
      public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
      {

      }   
   }

اما برای دسترسی به اطلاعات کامل محصول نیاز به استفاده از Repository محصول می باشد که به معنی مقداردهی اینترفیس IProductRepository با Instance جدید از کلاس ProductRepository هست(که البته این کار با توجه به Dependency Injection درست نیست). خب نکته ای که در اینجا وجود دارد این هست که شما بوسیله ی Ninject قاعدتا می بایست از طریق Field Injection بدون ایجاد Instance جدید، از Repository محصول بتوانید استفاده کنید(البته در نهایت Instance جدید توسط Ninject ایجاد خواهد شد)،

 [Inject]
 private IProductRepository _productRepo;

اما بنا به دلایلی این عمل انجام نمی شود.

در این مواقع میتوان از ماژول یا اکستنژن Ninject.Extensions.Factory در کلاسی با الگوی Factory استفاده کرد، به صورت زیر:

   public class Factories
   {
      public static readonly Factories Instance = new Factories();

      [Inject]
      public Func<IProductRepository> ProductRepo { get; set; }
   }   

خب بعد هم به راحتی میتوان از این کلاس در زمان اعتبارسنجی به صورت زیر استفاده کرد:

   [MetadataType(typeof(CustomerMetadata))]
   public partial class Customer : IValidatableObject 
   { 
      public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
      {
         var product = Factories.Instance.ProductRepo().Find(ProductId);
         if (product != null || product.Count==0)
         {
            yield return new ValidationResult("متاسفانه محصول: " + product.Name + " به اتمام رسیده است.", new string[] { "ProductId" });
         }
      }   
   }

خب همانطور که مشاهده کردید مشکل این سناریوی فرضی بدون نقض Dependency Injection با استفاده از ماژول اشاره شده به راحتی حل شد.

نظرات (1) -

Loading