مطالب
افزودن خودکار کلاس‌های تنظیمات نگاشت‌ها در EF Code first
اگر از روش Fluent-API برای تنظیم و افزودن نگاشت‌های کلاس‌ها استفاده کنیم، با زیاد شدن آن‌ها ممکن است در این بین، افزودن یکی فراموش شود یا کلا اضافه کردن دستی آن‌ها در متد OnModelCreating آنچنان جالب نیست. می‌شود این‌کار را به کمک Reflection ساده‌تر و خودکار کرد:
        void loadEntityConfigurations(Assembly asm, DbModelBuilder modelBuilder, string nameSpace)
        {
            var configurations = asm.GetTypes()
                                    .Where(type => type.BaseType != null &&
                                           type.Namespace == nameSpace &&
                                           type.BaseType.IsGenericType &&
                                           type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
                                    .ToList();

            configurations.ForEach(type =>
               {
                   dynamic instance = Activator.CreateInstance(type);
                   modelBuilder.Configurations.Add(instance);
               });
        }
در این متد، در یک اسمبلی مشخص و فضای نامی در آن، به دنبال کلاس‌هایی از نوع EntityTypeConfiguration خواهیم گشت. در ادامه این کلاس‌ها وهله سازی شده و به صورت خودکار به modelBuilder اضافه می‌شوند.

یک مثال کامل که بیانگر نحوه استفاده از متد فوق است:
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Reflection;

namespace EFGeneral
{
    public class User
    {
        public int UserNumber { get; set; }
        public string Name { get; set; }
    }

    public class UserConfig : EntityTypeConfiguration<User>
    {
        public UserConfig()
        {
            this.HasKey(x => x.UserNumber);
            this.Property(x => x.Name).HasMaxLength(450).IsRequired();
        }
    }

    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // modelBuilder.Configurations.Add(new UserConfig());

            var asm = Assembly.GetExecutingAssembly();
            loadEntityConfigurations(asm, modelBuilder, "EFGeneral");
        }

        void loadEntityConfigurations(Assembly asm, DbModelBuilder modelBuilder, string nameSpace)
        {
            var configurations = asm.GetTypes()
                                    .Where(type => type.BaseType != null &&
                                           type.Namespace == nameSpace &&
                                           type.BaseType.IsGenericType &&
                                           type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))
                                    .ToList();

            configurations.ForEach(type =>
               {
                   dynamic instance = Activator.CreateInstance(type);
                   modelBuilder.Configurations.Add(instance);
               });
        }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {
            context.Users.Add(new User { Name = "name-1" });
            context.Users.Add(new User { Name = "name-2" });
            context.Users.Add(new User { Name = "name-3" });
            base.Seed(context);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            using (var context = new MyContext())
            {
                var user1 = context.Users.Find(1);
                if (user1 != null)
                    Console.WriteLine(user1.Name);
            }
        }
    }
}
در این مثال، در متد OnModelCreating بجای اضافه کردن دستی تک تک تنظیمات تعریف شده، از متد loadEntityConfigurations جهت یافتن آن‌ها در اسمبلی جاری و فضای نام مشخصی به نام EFGeneral استفاده شده است.
مطالب
طراحی و پیاده سازی مکانیزم مدیریت Transactionها در ServiceLayer

هدف ارائه راه حلی برای مدیریت Transactionها به عنوان یک Cross Cutting Concern، توسط ApplicationServiceها می‌باشد. 

پیش نیازها:

پیش فرض ما این است که شما از EF به عنوان OR-Mapper استفاده می‌کنید و الگوی Context Per Request را پیاده سازی کرده اید یا از طریق پیاده سازی الگوی Container Per Request به داشتن Context یکتا برای هر درخواست رسیده اید.
کتابخانه StructureMap.Mvc5 پیاده سازی از الگوی Container Per Request را با استفاده از امکانات Nested Container مربوط به StructureMap ارائه می‌دهد. اشیاء موجود در Nested Container طول عمر Singleton دارند.

ایده کار به این صورت هست که توسط یک Interceptor، به هنگام ورود به متد موجود در یک ApplicationService که با TransactionalAttribute تزئین شده است، یک تراکنش ایجاد شده و بعد از اتمام کار متد مورد نظر، در صورت عدم بروز استثناء، Commit و در غیر این صورت Rollback شود. حال اگر یک متد تراکنشی، داخل خود از متد تراکنشی دیگری استفاده کند، صرفا متد بیرونی تراکنش را ایجاد می‌کند و متد داخلی از همان تراکنش استفاده خواهد کرد و اصطلاحا  این تراکنش به صورت Ambient Transaction می باشد. 


واسط ITransaction

    public interface ITransaction : IDisposable
    {
        void Commit();
        void Rollback();
    }

واسط بالا 3 متد را که برای مدیریت تراکنش لازم می‌باشد، در اختیار استفاده کننده قرار می‌دهد.


واسط IUnitOfWork

    public interface IUnitOfWork : IDisposable
    {
        ...

        ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Snapshot);
        ITransaction Transaction { get; }
        IDbConnection Connection { get; }
        bool HasTransaction { get; }
    }

اعضای جدید واسط IUnitOfWork کاملا مشخص هستند.

پیاده سازی واسط ITransaction، توسط یک Nested Type در دل کلاس DbContextBase انجام می‌گیرد.

 public abstract class DbContextBase : DbContext
    {
        ...

        #region Fields
        
        private ITransaction _currenTransaction;

        #endregion

        #region NestedTypes

        private class DbContextTransactionAdapter : ITransaction
        {
            private DbContextTransaction _transaction;

            public DbContextTransactionAdapter(DbContextTransaction transaction)
            {
                Guard.NotNull(transaction, nameof(transaction));

                _transaction = transaction;
            }

            public void Commit()
            {
                _transaction?.Commit();
            }

            public void Rollback()
            {
                if (_transaction?.UnderlyingTransaction.Connection != null)
                    _transaction.Rollback();
            }

            public void Dispose()
            {
                _transaction?.Dispose();
                _transaction = null;
            }
        }

        #endregion

        #region Public Methods
        ...

        public ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            if (_currenTransaction != null) return _currenTransaction;

            return _currenTransaction = new DbContextTransactionAdapter(Database.BeginTransaction(isolationLevel));
        }
        #endregion

        #region Properties
        ...

        public ITransaction Transaction => _currenTransaction;
        public IDbConnection Connection => Database.Connection;
        public bool HasTransaction => _currenTransaction != null;

        #endregion
}

public class ApplicationDbContext : DbContextBase, IUnitOfWork, ITransientDependency
{
     
}

کلاس DbContextTransactionAdapter همانطور که از نام آن مشخص می‌باشد، پیاده سازی از الگوی Adapter برای وفق دادن DbContextTransaction با واسط ITransaction، می‌باشد. متد BeginTransaction در صورتی که تراکنشی برای وهله جاری DbContext ایجاد نشده باشد، تراکنشی را ایجاد کرده و فیلد currentTransaction_ را نیز مقدار دهی می‌کند. 


TransactionalAttribute

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public sealed class TransactionalAttribute : Attribute
    {
        public IsolationLevel IsolationLevel { get; set; } = IsolationLevel.ReadCommitted;
        public TimeSpan? Timeout { get; set; }
    }

TransactionInterceptor

    public class TransactionInterceptor : ISyncInterceptionBehavior
    {
        private readonly IUnitOfWork _unitOfWork;

        public TransactionInterceptor(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }
        public IMethodInvocationResult Intercept(ISyncMethodInvocation methodInvocation)
        {
            var transactionAttribute = GetTransactionaAttributeOrNull(methodInvocation.InstanceMethodInfo);

            if (transactionAttribute == null || _unitOfWork.HasTransaction) return methodInvocation.InvokeNext();

            using (var transaction = _unitOfWork.BeginTransaction(transactionAttribute.IsolationLevel))
            {
                var result = methodInvocation.InvokeNext();

                if (result.Successful)
                    transaction.Commit();
                else
                {
                    transaction.Rollback();
                }

                return result;
            }
        }

        private static TransactionalAttribute GetTransactionaAttributeOrNull(MemberInfo methodInfo)
        {
            var transactionalAttribute =
                ReflectionHelper.GetAttributesOfMemberAndDeclaringType<TransactionalAttribute>(
                    methodInfo
                ).FirstOrDefault();

            return transactionalAttribute;
        }
    }

واسط ISyncInterceptionBehavior، مربوط می‌شود به کتابخانه جانبی دیگری که برای AOP توسط تیم StructureMap به نام StructureMap.DynamicInterception ارائه شده‌است. در متد Intercept، ابتدا چک می‌شود که که آیا این متد با TransactionAttribute تزئین شده و طی درخواست جاری برای Context جاری تراکنشی ایجاد نشده باشد؛ سپس تراکنش جدیدی ایجاد شده و بدنه اصلی متد اجرا می‌شود و نهایتا در صورت موفقیت آمیز بودن عملیات، تراکنش مورد نظر Commit می‌شود.

در آخر لازم است این Interceptor در تنظیمات اولیه StructureMap به شکل زیر معرفی شود:

Policies.Interceptors(new DynamicProxyInterceptorPolicy(
                type => typeof(IApplicationService).IsAssignableFrom(type), typeof(AuthorizationInterceptor), typeof(TransactionInterceptor), typeof(ValidationInterceptor)));



نکته: فرض کنید در بدنه اکشن متد یک کنترلر ASP.NET MVC یا ASP.NET Core، دو متد تراکنشی فراخوانی شود؛ در این صورت شاید لازم باشد که این دو متد طی یک تراکنش واحد به جای تراکنش‌های مجزا، اجرا شوند؛ بنابراین نیاز است از الگوی Transaction Per Request استفاده شود. برای این کار می‌توان یک ActionFilterAttribute سفارشی ایجاد کرد که ایجاد کننده تراکنش باشد و متدهای داخلی که هر کدام جدا تراکنشی بودند، نیز از تراکنش ایجاد شده استفاده کنند.

مطالب
ساخت یک Web API که از عملیات CRUD پشتیبانی می کند
در این مقاله با استفاده از ASP.NET Web API یک سرویس HTTP خواهیم ساخت که از عملیات CRUD پشتیبانی می‌کند. CRUD مخفف Create, Read, Update, Delete است که عملیات پایه دیتابیسی هستند. بسیاری از سرویس‌های HTTP این عملیات را بصورت REST API هم مدل سازی می‌کنند. در مثال جاری سرویس ساده ای خواهیم ساخت که مدیریت لیستی از محصولات (Products) را ممکن می‌سازد. هر محصول شامل فیلدهای شناسه (ID)، نام، قیمت و طبقه بندی خواهد بود.

سرویس ما متدهای زیر را در دسترس قرار می‌دهد.

 Relative URl
 HTTP method
 Action
 api/products/  GET  گرفتن لیست تمام محصولات
 api/products/id/  GET  گرفتن یک محصول بر اساس شناسه
 api/products?category=category/  GET  گرفتن یک محصول بر اساس طبقه بندی
 api/products/  POST  ایجاد یک محصول جدید
 api/products/id/  PUT  بروز رسانی یک محصول
 api/products/id/  DELETE  حذف یک محصول

همانطور که مشاهده می‌کنید برخی از آدرس ها، شامل شناسه محصول هم می‌شوند. بعنوان مثال برای گرفتن محصولی با شناسه 28، کلاینت یک درخواست GET را به آدرس زیر ارسال می‌کند:

http://hostname/api/products/28

منابع

سرویس ما آدرس هایی برای دستیابی به دو نوع منبع (resource) را تعریف می‌کند:

URI
 Resource
 api/products/  لیست تمام محصولات
 api/products/id/  یک محصول مشخص

متد ها

چهار متد اصلی HTTP یعنی همان GET, PUT, POST, DELETE می‌توانند بصورت زیر به عملیات CRUD نگاشت شوند:

  • متد GET یک منبع (resource) را از آدرس تعریف شده دریافت می‌کند. متدهای GET هیچگونه تاثیری روی سرور نباید داشته باشند. مثلا حذف رکوردها با متد اکیدا اشتباه است.
  • متد PUT یک منبع را در آدرس تعریف شده بروز رسانی می‌کند. این متد برای ساختن منابع جدید هم می‌تواند استفاده شود، البته در صورتی که سرور به کلاینت‌ها اجازه مشخص کردن آدرس‌های جدید را بدهد. در مثال جاری پشتیبانی از ایجاد منابع توسط متد PUT را بررسی نخواهیم کرد.
  • متد POST منبع جدیدی می‌سازد. سرور آدرس آبجکت جدید را تعیین می‌کند و آن را بعنوان بخشی از پیام Response بر می‌گرداند.
  • متد DELETE منبعی را در آدرس تعریف شده حذف می‌کند.

نکته: متد PUT موجودیت محصول (product entity) را کاملا جایگزین میکند. به بیان دیگر، از کلاینت انتظار می‌رود که آبجکت کامل محصول را برای بروز رسانی ارسال کند. اگر می‌خواهید از بروز رسانی‌های جزئی/پاره ای (partial) پشتیبانی کنید متد PATCH توصیه می‌شود. مثال جاری متد PATCH را پیاده سازی نمی‌کند.

یک پروژه Web API جدید بسازید

ویژوال استودیو را باز کنید و پروژه جدیدی از نوع ASP.NET MVC Web Application بسازید. نام پروژه را به "ProductStore" تغییر دهید و OK کنید.

در دیالوگ New ASP.NET Project قالب Web API را انتخاب کرده و تایید کنید.

افزودن یک مدل

یک مدل، آبجکتی است که داده اپلیکیشن شما را نمایندگی می‌کند. در ASP.NET Web API می‌توانید از آبجکت‌های Strongly-typed بعنوان مدل هایتان استفاده کنید که بصورت خودکار برای کلاینت به فرمت‌های JSON, XML مرتب (Serialize) می‌شوند. در مثال جاری، داده‌های ما محصولات هستند. پس کلاس جدیدی بنام Product می‌سازیم.

در پوشه Models کلاس جدیدی با نام Product بسازید.

حال خواص زیر را به این کلاس اضافه کنید.

namespace ProductStore.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

افزودن یک مخزن

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

روی پوشه Models کلیک راست کنید و گزینه Add, New Item را انتخاب نمایید.

نوع آیتم جدید را Interface انتخاب کنید و نام آن را به IProductRepository تغییر دهید.

حال کد زیر را به این اینترفیس اضافه کنید.

namespace ProductStore.Models
{
    public interface IProductRepository
    {
        IEnumerable<Product> GetAll();
        Product Get(int id);
        Product Add(Product item);
        void Remove(int id);
        bool Update(Product item);
    }
}
حال کلاس دیگری با نام ProductRepository در پوشه Models ایجاد کنید. این کلاس قرارداد IProductRepository را پیاده سازی خواهد کرد. کد زیر را به این کلاس اضافه کنید.

namespace ProductStore.Models
{
    public class ProductRepository : IProductRepository
    {
        private List<Product> products = new List<Product>();
        private int _nextId = 1;

        public ProductRepository()
        {
            Add(new Product { Name = "Tomato soup", Category = "Groceries", Price = 1.39M });
            Add(new Product { Name = "Yo-yo", Category = "Toys", Price = 3.75M });
            Add(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M });
        }

        public IEnumerable<Product> GetAll()
        {
            return products;
        }

        public Product Get(int id)
        {
            return products.Find(p => p.Id == id);
        }

        public Product Add(Product item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            item.Id = _nextId++;
            products.Add(item);
            return item;
        }

        public void Remove(int id)
        {
            products.RemoveAll(p => p.Id == id);
        }

        public bool Update(Product item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            int index = products.FindIndex(p => p.Id == item.Id);
            if (index == -1)
            {
                return false;
            }
            products.RemoveAt(index);
            products.Add(item);
            return true;
        }
    }
}

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


افزودن یک کنترلر Web API

اگر قبلا با ASP.NET MVC کار کرده باشید، با مفهوم کنترلر‌ها آشنایی دارید. در ASP.NET Web API کنترلر‌ها کلاس هایی هستند که درخواست‌های HTTP دریافتی از کلاینت را به اکشن متدها نگاشت می‌کنند. ویژوال استودیو هنگام ساختن پروژه شما دو کنترلر به آن اضافه کرده است. برای مشاهد آنها پوشه Controllers را باز کنید.

  • HomeController یک کنترلر مرسوم در ASP.NET MVC است. این کنترلر مسئول بکار گرفتن صفحات وب است و مستقیما ربطی به Web API ما ندارد.
  • ValuesController یک کنترلر نمونه WebAPI است.

کنترلر ValuesController را حذف کنید، نیازی به این آیتم نخواهیم داشت. حال برای اضافه کردن کنترلری جدید مراحل زیر را دنبال کنید.

در پنجره Solution Explorer روی پوشه Controllers کلیک راست کرده و گزینه Add, Controller را انتخاب کنید.

در دیالوگ Add Controller نام کنترلر را به ProductsController تغییر داده و در قسمت Scaffolding Options گزینه Empty API Controller را انتخاب کنید.

حال فایل کنترلر جدید را باز کنید و عبارت زیر را به بالای آن اضافه نمایید.

using ProductStore.Models;
یک فیلد هم برای نگهداری وهله ای از IProductRepository اضافه کنید.
public class ProductsController : ApiController
{
    static readonly IProductRepository repository = new ProductRepository();
}

فراخوانی ()new ProductRepository طراحی جالبی نیست، چرا که کنترلر را به پیاده سازی بخصوصی از این اینترفیس گره می‌زند. بهتر است از تزریق وابستگی (Dependency Injection) استفاده کنید. برای اطلاعات بیشتر درباره تکنیک DI در Web API به این لینک مراجعه کنید.


گرفتن منابع

ProductStore API اکشن‌های متعددی در قالب متدهای HTTP GET در دسترس قرار می‌دهد. هر اکشن به متدی در کلاس ProductsController مرتبط است.

 Relative URl
 HTTP Method
 Action
 api/products/  GET  دریافت لیست تمام محصولات
 api/products/id/  GET  دریافت محصولی مشخص بر اساس شناسه
 api/products?category=category/  GET  دریافت محصولات بر اساس طبقه بندی

برای دریافت لیست تمام محصولات متد زیر را به کلاس ProductsController اضافه کنید.

public class ProductsController : ApiController
{
    public IEnumerable<Product> GetAllProducts()
    {
        return repository.GetAll();
    }
    // ....
}
نام این متد با "Get" شروع می‌شود، پس بر اساس قراردادهای توکار پیش فرض به درخواست‌های HTTP GET نگاشت خواهد شد. همچنین از آنجا که این متد پارامتری ندارد، به URl ای نگاشت می‌شود که هیچ قسمتی با نام مثلا id نداشته باشد.

برای دریافت محصولی مشخص بر اساس شناسه آن متد زیر را اضافه کنید.
public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound); 
    }
    return item;
}

نام این متد هم با "Get" شروع می‌شود اما پارامتری با نام id دارد. این پارامتر به قسمت id مسیر درخواست شده (request URl) نگاشت می‌شود. تبدیل پارامتر به نوع داده مناسب (در اینجا int) هم بصورت خودکار توسط فریم ورک ASP.NET Web API انجام می‌شود.

متد GetProduct در صورت نامعتبر بودن پارامتر id استثنایی از نوع HttpResponseException تولید می‌کند. این استثنا بصورت خودکار توسط فریم ورک Web API به خطای 404 (Not Found) ترجمه می‌شود.

در آخر متدی برای دریافت محصولات بر اساس طبقه بندی اضافه کنید.
public IEnumerable<Product> GetProductsByCategory(string category)
{
    return repository.GetAll().Where(
        p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}

اگر آدرس درخواستی پارامتر‌های query string داشته باشد، Web API سعی می‌کند پارامتر‌ها را با پارامتر‌های متد کنترلر تطبیق دهد. بنابراین درخواستی به آدرس "api/products?category=category" به این متد نگاشت می‌شود.

ایجاد منبع جدید

قدم بعدی افزودن متدی به ProductsController برای ایجاد یک محصول جدید است. لیست زیر پیاده سازی ساده ای از این متد را نشان می‌دهد.

// Not the final implementation!
public Product PostProduct(Product item)
{
    item = repository.Add(item);
    return item;
}
به دو چیز درباره این متد توجه کنید:

  • نام این متد با "Post" شروع می‌شود. برای ساختن محصولی جدید کلاینت یک درخواست HTTP POST ارسال می‌کند.
  • این متد پارامتری از نوع Product می‌پذیرد. در Web API پارامترهای پیچیده (complex types) بصورت خودکار با deserialize کردن بدنه درخواست بدست می‌آیند. بنابراین در اینجا از کلاینت انتظار داریم که آبجکتی از نوع Product را با فرمت XML یا JSON ارسال کند.

پیاده سازی فعلی این متد کار می‌کند، اما هنوز کامل نیست. در حالت ایده آل ما می‌خواهیم پیام HTTP Response موارد زیر را هم در بر گیرد:

  • Response code: بصورت پیش فرض فریم ورک Web API کد وضعیت را به 200 (OK) تنظیم می‌کند. اما طبق پروتکل HTTP/1.1 هنگامی که یک درخواست POST منجر به ساخته شدن منبعی جدید می‌شود، سرور باید با کد وضعیت 201 (Created) پاسخ دهد.
  • Location: هنگامی که سرور منبع جدیدی می‌سازد، باید آدرس منبع جدید را در قسمت Location header پاسخ درج کند.

ASP.NET Web API دستکاری پیام HTTP response را آسان می‌کند. لیست زیر پیاده سازی بهتری از این متد را نشان می‌دهد.

public HttpResponseMessage PostProduct(Product item)
{
    item = repository.Add(item);
    var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);

    string uri = Url.Link("DefaultApi", new { id = item.Id });
    response.Headers.Location = new Uri(uri);
    return response;
}
توجه کنید که حالا نوع بازگشتی این متد HttpResponseMessage است. با بازگشت دادن این نوع داده بجای Product، می‌توانیم جزئیات پیام HTTP response را کنترل کنیم. مانند تغییر کد وضعیت و مقدار دهی Location header.

متد CreateResponse آبجکتی از نوع HttpResponseMessage می‌سازد و بصورت خودکار آبجکت Product را مرتب (serialize) کرده و در بدنه پاسخ می‌نویسد. نکته دیگر آنکه مثال جاری، مدل را اعتبارسنجی نمی‌کند. برای اطلاعات بیشتر درباره اعتبارسنجی مدل‌ها در Web API به این لینک مراجعه کنید.


بروز رسانی یک منبع

بروز رسانی یک محصول با PUT ساده است.

public void PutProduct(int id, Product product)
{
    product.Id = id;
    if (!repository.Update(product))
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}
نام این متد با "Put" شروع می‌شود، پس Web API آن را به درخواست‌های HTTP PUT نگاشت خواهد کرد. این متد دو پارامتر می‌پذیرد، یکی شناسه محصول مورد نظر و دیگری آبجکت محصول آپدیت شده. مقدار پارامتر id از مسیر (route) دریافت می‌شود و پارامتر محصول با deserialize کردن بدنه درخواست.


حذف یک منبع

برای حذف یک محصول متد زیر را به کلاس ProductsController اضافه کنید.

public void DeleteProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    repository.Remove(id);
}
اگر یک درخواست DELETE با موفقیت انجام شود، می‌تواند کد وضعیت 200 (OK) را بهمراه بدنه موجودیتی که وضعیت فعلی را نمایش می‌دهد برگرداند. اگر عملیات حذف هنوز در حال اجرا است (Pending) می‌توانید کد 202 (Accepted) یا 204 (No Content) را برگردانید.

در مثال جاری متد DeleteProduct نوع void را بر می‌گرداند، که فریم ورک Web API آن را بصورت خودکار به کد وضعیت 204 (No Content) ترجمه می‌کند.
نظرات مطالب
آشنایی با مفهوم Indexer در C#.NET
ممنون. Indexer مطلب جالبی است؛ ولی یک مورد کلا از زبان سی شارپ از قلم افتاده به نام indexed property
در حالت عادی می‌شود بر روی یک وهله از کلاس، Index اعمال کرد. اگر نخواهیم روی کل وهله اعمال شود چطور؟ برای مثال فقط روی یک خاصیت خاص.
پیاده سازی آن یک نکته کوچک دارد که به شرح زیر است:

using System;

namespace IndexedProperties
{
    public class Data
    {
        private int[] _localArray;
        private ArrayIndexer _arrayIndexer;

        public Data()
        {
            _localArray = new int[10];
            for (int i = 0; i < 10; i++)
                _localArray[i] = i + 1;
            _arrayIndexer = new ArrayIndexer(this);
        }
        
        public ArrayIndexer Number
        {
            get { return _arrayIndexer; }
        }       

        public class ArrayIndexer
        {
            private Data _arrayOwner;

            public ArrayIndexer(Data arrayOwner)
            {
                _arrayOwner = arrayOwner;
            }

            public int this[int index]
            {
                get { return _arrayOwner._localArray[index]; }
            }

            public int Length
            {
                get { return _arrayOwner._localArray.Length; }
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            var data = new Data();
            for (int i = 0; i < 10; i++)
                Console.WriteLine(data.Number[i]);
        }
    }
}
در اینجا از کلاس‌های تودرتو برای پیاده سازی Indexed property استفاده شده است.
به این ترتیب تعاریف آرایه مورد استفاده نیازی نیست مستقیما در معرض دید قرار گیرد و همچنین read-only هم تعریف شده است. به علاوه اینکه indexerها محدود به int نیستند و برای مثال می‌توان از string و غیره نیز استفاده کرد.
 
نظرات مطالب
بیرون نگاه داشتن پکیج های NuGet از سورس کنترل Git
در ابتدا بهتر است با فایل‌های packages.config و repositories.config آشنا شویم.
فایل packages.config در ازای هر پروژه ایجاد می‌شود و در این فایل اطلاعات package هایی که به پروژه اضافه شده اند نگهداری می‌شوند. 
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.Owin" version="3.0.0" targetFramework="net45" />
  <package id="RavenDB.Client" version="2.0.2375" targetFramework="net45" />
</packages>
فایل repositories.config در فولدر  packages در مسیر اصلی solution وجود دارد، در این فایل مسیر  فایل‌های packages.config هر پروژه نگداری می‌شود.
<?xml version="1.0" encoding="utf-8"?>
<repositories>
  <repository path="..\Application\Test\packages.config" />
  <repository path="..\ViewModel\Test\packages.config" />
</repositories>
1- ابتدا فولدر packages  را از سورس کنترل حذف کنید، checkin کنید.
حال هر شخصی که پروژه را get میکند نیاز است با توجه به مطالب و روش‌های گفته شده در بالا -به ویژوال استودیو اجازه دهید بسته‌های NuGet را در صورت لزوم احیا کند- یا -فعال سازی NuGet Package Restore برای پروژه‌ها- package‌ها را دریافت کند. بعد از انجام این کار فولدر packages و فایل repositories.config در local هر کاربر ایجاد می‌شوند.
توجه شود این فولدر و محتویات آن از طریق Add Item to folder دوباره به سورس کنترل اضافه نشود.
2- یا می‌توانید ابتدا محتویات فولدر packages به غیر از فایل repositories.config  را از سورس کنترل  پاک کنید (فولدر‌ها و فایل‌های package  ها ). به فایل  repositories.config برای مسیر فایل‌های  packages.config هر پروژه  نیاز داریم .
شما فولدر  packages و  فایل repositories.config را checkin کنید (در  فولدر   packages جز فایل  repositories.config فایل یا فولدر دیگری وجود نداشته باشد ).
و در هر بار Build کردن موجود بودن فایل‌های package دوباره چک می‌شوند و اگر موجود نباشند، دریافت می‌شوند.
نکته مهم این است که اگر Build definition تعریف کرده باشید نیاز به تنظیمات در سرور build برای دریافت package‌ها دارید. 
نظرات مطالب
EF Code First #12
بحث ASP.NET متفاوت است با Windows Forms یا WPF. در برنامه‌های وب یک زیر ساخت آماده برای آغاز و پایان درخواست‌ها و مدیریت خودکار این مسایل وجود دارد. معادل آن مثلا در یک برنامه مبتنی بر MVVM، مدیریت طول عمر یک context در طول عمر ViewModel برنامه است. علت این مساله هم به stateless بودن برنامه‌های وب و state-full بودن برنامه‌های ویندوزی بر می‌گردد. در یک برنامه وب در پایان درخواست، تمام اشیاء یک فرم در سمت سرور تخریب می‌شوند. اما در یک برنامه ویندوزی تا زمانیکه یک فرم باز است، اشیاء آن تخریب نخواهند شد. بنابراین مدیریت context در برنامه‌های ویندوزی «دستی» است. در زمان شروع فرم context شروع خواهد شد، زمان تخریب/بستن آن، با بستن یا dispose یک context، خودبخود اتصالات هم قطع خواهند شد.
در متد Program.Main هم می‌تونید تنظیمات اولیه ObjectFactory رو قرار بدید (شبیه به Application_Start برنامه‌های وب).
بنابراین در برنامه‌های وب «context/session per http request» داریم؛ در برنامه‌های ویندوزی «context per operation or per form». یعنی می‌تونید بسته به معماری برنامه ویندوزی خود، context را در سطح یک فرم تعریف کنید و مدیریت؛ و یا در سطح یک عملیات کوتاه مانند یک کلیک. تمام مباحث ObjectFactory.GetInstance، uow.SaveChanges و یا Dispose آن هم دستی است و زیر ساختی برای مدیریت خودکار آن‌ها همانند برنامه‌های مثلا ASP.NET MVC وجود ندارد. حداکثر اینکه یک سری base class را شبیه به مثال Web forms زده شده تهیه کنید، تا میزان کدهای تکراری را کاهش بدید. 
مطالب
مقدمه‌ای بر تزریق وابستگی‌ها درASP.NET Core
ASP.NET Core با ذهنیت پشتیبانی و استفاده از تزریق وابستگی‌ها ایجاد شده‌است. اپلیکیشن‌های ASP.NET Core از سرویس‌های ذاتی فریم ورک که داخل متدهای کلاس Startup پروژه تزریق شده‌اند و همچنین سرویس‌های اپلیکیشن که تنظیمات خاص آنها در پروژه انجام گرفته است، استفاده می‌کنند. سرویس کانتینر پیش فرض ارائه شده توسط ASP.NET Core، مجموعه‌ای حداقلی از ویژگی‌ها را ارائه می‌کند و هدف آن جایگزینی با دیگر فریم ورک‌های تزریق وابستگی نمی‌باشد.

مشاهده یا دانلود کدهای مقاله


تزریق وابستگی چیست؟

تزریق وابستگی (DI) تکنیکی برای دستیابی به اتصال شل بین اشیاء و همکاران اشیاء و وابستگی‌های بین آنها می‌باشد. یک شیء برای انجام وظایف خود، بجای اینکه اشیاء همکار خود را به صورت مستقیم نمونه سازی کند، یا از ارجاعات استاتیک استفاده نماید، می‌تواند از اشیائی که برایش تامین شده‌است، استفاده کند. در اغلب موارد کلاس‌ها، وابستگی‌های خود را از طریق سازنده‌ی خود درخواست می‌کنند، که به آنها اجازه می‌دهد اصل وابستگی صریح را رعایت کنند (Explicit Dependencies Principle). این روش را «تزریق در سازنده» می‌نامند.
از آنجا که در طراحی کلاس‌ها با استفاده از DI، نمونه سازی مستقیم، توسط کلاس‌ها و به صورت Hard-coded انجام نمی‌گیرد، وابستگی بین اشیاء کم شده و پروژه‌ای با اتصالات شل به دست می‌آید. با این کار اصل وابستگی معکوس (Dependency Inversion Principle) رعایت می‌شود. بر اساس این اصل، ماژول‌های سطح بالا نباید به ماژول‌های سطح پایین خود وابسته باشند؛ بلکه هر دو باید به کلاس‌هایی انتزاعی وابسته باشند. اشیاء بجای ارجاع به پیاده سازی‌های خاص کلاس‌های همکار خود، کلاس‌های انتزاعی، معمولاٌ اینترفیس آنها را درخواست می‌کنند و هنگام نمونه سازی از آنها (داخل متد سازنده) کلاس پیاده سازی شده برایشان تامین می‌شود. خارج کردن وابستگی‌‎های مستقیم از کلاس‌ها و تامین پیاده سازی‌های این اینترفیس‌ها به صورت پارامتر‌هایی برای کلاس‌ها، یک مثال از الگوی طراحی استراتژی (Strategy design pattern) می‌باشد.

در حالتیکه کلاس‌ها به تعداد زیادی کلاس وابستگی داشته باشند و برای اجرا شدن، نیاز به تامین وابستگی‌هایشان داشته باشند، بهتر است یک کلاس اختصاصی، برای نمونه سازی این کلاس‌ها با وابستگی‌های مورد نیاز آنها، در سیستم وجود داشته باشد. این کلاس نمونه ساز را کانتینرIoC، یا کانتینر DI یا به طور خلاصه کانتینر می‌نامند ( Inversion of Control (IoC) ). کانتینر در اصل یک کارخانه می‌باشد که وظیفه‌ی تامین نمونه‌هایی از کلاس‌هایی را که از آن درخواست می‌شود، انجام می‌دهد. اگر یک کلاس تعریف شده، وابستگی به کلاس‌های دیگر داشته باشد و کانتینر برای ارائه وابستگی‌های کلاس تعریف شده تنظیم شده باشد، هر موقع نیاز به یک نمونه از این کلاس وجود داشته باشد، به عنوان بخشی از کار نمونه سازی از کلاس مورد نظر، کلاس‌های وابسته‌ی آن نیز ایجاد می‌شوند (همه‌ی کارهای مربوط به نمونه سازی کلاس خاص و کلاس‌های وابسته به آن توسط کانتینر انجام می‌گیرد). به این ترتیب، می‌توان وابستگی‌های بسیار پیچیده و تو در توی موجود در سیستم را بدون نیاز به هیچگونه نمونه سازی hard-code شده، برای کلاس‌ها فراهم کرد. کانتینرها علاوه بر ایجاد اشیاء و وابستگی‌های موجود در آنها، معمولا طول عمر اشیاء در اپلیکیشن را نیز مدیریت می‌کنند.
ASP.NET Core یک کانتینر بسیار ساده را به نام اینترفیس IServiceProvider  ارائه داده است که به صورت پیش فرض از تزریق وابستگی در سازنده‌ی کلاس‌ها پشتیبانی می‌کند و همچنین ASP.NET برخی از سرویس‌های خود را از طریق DI در دسترس قرار داده است. کانتینرASP.NET، یک اشاره‌گر به کلاس‌هایی است که به عنوان سرویس عمل می‌کنند. در ادامه‌ی این مقاله، سرویس‌ها به کلاس‌هایی گفته می‌شود که به وسیله‌ی کانتینر ASP.NET Core مدیریت می‌شوند. شما می‌توانید سرویس ConfigureServices کانتینر را در داخل کلاس Startup پروژه خود پیکربندی کنید.


تزریق وابستگی از طریق متد سازنده‌ی کلاس

تزریق وابستگی از طریق متد سازنده، مستلزم آن است که سازنده‌ی کلاس مورد نظر عمومی باشد. در غیر این صورت، اپلیکیشن شما استثنای InvalidOperationException  را با پیام زیر نشان می‌دهد:
 A suitable constructor for type 'YourType' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.

تزریق از طریق متد سازنده مستلزم آن است که تنها یک سازنده‌ی مناسب وجود داشته باشد. البته Overload سازنده امکان پذیر است؛ ولی باید تنها یک متد سازنده وجود داشته باشد که آرگومان‌های آن توسط DI قابل ارائه باشند. اگر بیش از یکی وجود داشته باشد، سیستم استثنای InvalidOperationException را با پیام زیر نشان می‌دهد:
 Multiple constructors accepting all given argument types have been found in type 'YourType'. There should only be one applicable constructor.

سازندگان می‌توانند آرگومان‌هایی را از طریق DI دریافت کنند. برای این منظور آرگومان‌های این سازنده‌ها باید مقدار پیش فرضی را داشته باشند. به مثال زیر توجه نمایید:
// throws InvalidOperationException: Unable to resolve service for type 'System.String'...
public CharactersController(ICharacterRepository characterRepository, string title)
{
    _characterRepository = characterRepository;
    _title = title;
}

// runs without error
public CharactersController(ICharacterRepository characterRepository, string title = "Characters")
{
    _characterRepository = characterRepository;
    _title = title;
}


استفاده از سرویس ارائه شده توسط فریم ورک

متد ConfigureServices در کلاس Startup، مسئول تعریف سرویس‌هایی است که سیستم از آن استفاده می‌کند. از جمله‌ی این سرویس‌ها می‌توان به ویژگی‌های پلتفرم مانند EF Core و ASP.NET Core MVC اشاره کرد. IServiceCollection که به ConfigureServices ارائه می‌شود، سرویس‌های زیر را تعریف می‌کند (که البته بستگی به نوع پیکربندی هاست دارد):

  نوع سرویس    طول زندگی 
    Microsoft.AspNetCore.Hosting.IHostingEnvironment  
 Singleton 
    Microsoft.AspNetCore.Hosting.IApplicationLifetime     Singleton 
    Microsoft.AspNetCore.Hosting.IStartup     Singleton 
    Microsoft.AspNetCore.Hosting.Server.IServer     Singleton 
    Microsoft.Extensions.Options.IConfigureOptions     Transient 
    Microsoft.Extensions.ObjectPool.ObjectPoolProvider     Singleton 
    Microsoft.AspNetCore.Hosting.IStartupFilter     Transient 
    System.Diagnostics.DiagnosticListener     Singleton 
    System.Diagnostics.DiagnosticSource     Singleton 
    Microsoft.Extensions.Options.IOptions     Singleton 
    Microsoft.AspNetCore.Http.IHttpContextFactory     Transient 
    Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory     Transient 
    Microsoft.Extensions.Logging.ILogger     Singleton 
    Microsoft.Extensions.Logging.ILoggerFactory  
 Singleton 

در زیر نمونه ای از نحوه‌ی اضافه کردن سرویس‌های مختلف را به کانتینر، با استفاده از متدهای الحاقی مانند AddDbContext، AddIdentity و AddMvc، مشاهده می‌کنید:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}
ویژگی‌ها و میان افزار‌های ارائه شده توسط ASP.NET، مانند MVC، از یک قرارداد، با استفاده از متد الحاقی AddServiceName برای ثبت تمام سرویس‌های مورد نیاز این ویژگی پیروی می‌کنند.


ثبت سرویس‌های اختصاصی

شما می‌توانید سرویس‌های اپلیکیشن خودتان را به ترتیبی که در تکه کد زیر مشاهده می‌کنید، ثبت نمایید. اولین نوع جنریک، نوعی است که از کانتینر درخواست خواهد شد و معمولا به شکل اینترفیس می‌باشد. نوع دوم، نوع پیاده سازی شده‌ای است که به وسیله‌ی کانتینر، نمونه سازی خواهد شد و کانتینر برای درخواست‌های از نوع اول، این نمونه از  تایپ را ارائه خواهد کرد:
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();

نکته:
هر متد الحاقی <services.Add<ServiceName، سرویس‌هایی را اضافه و پیکربندی می‌کند. به عنوان مثال services.AddMvc نیازمندی‌های سرویس MVC را اضافه می‌کند. توصیه می‌شود شما هم با افزودن متدهای الحاقی در فضای نام Microsoft.Extensions.DependencyInjection این قرارداد را رعایت نمائید. این کار باعث کپسوله شدن ثبت گروهی سرویس‌ها می‌شود.
متد AddTransient، برای نگاشت نوع‌های انتزاعی به سرویس‌های واقعی که نیاز به نمونه سازی به ازای هر درخواست دارند، استفاده می‌شود. در اصطلاح، طول عمر سرویس‌ها در اینجا مشخص می‌شوند. در ادامه گزینه‌های دیگری هم برای طول عمر سرویس‌ها تعریف خواهند شد. خیلی مهم است که برای هر یک از سرویس‌های ثبت شده، طول عمر مناسبی را انتخاب نمایید. آیا برای هر کلاس که سرویسی را درخواست می‌کند، باید یک نمونه‌ی جدید ساخته شود؟ آیا فقط یک نمونه در طول یک درخواست وب مورد استفاده قرار می‌گیرد؟ یا باید از یک نمونه‌ی واحد برای طول عمر کل اپلیکیشن استفاده شود؟
در مثال ارائه شده‌ی در این مقاله، یک کنترلر ساده به نام CharactersController وجود دارد که نام کاراکتری را نشان می‌دهد. متد Index، لیست کنونی کاراکترهایی را که در اپلیکیشن ذخیره شده‌اند، نشان می‌دهد. در صورتیکه این لیست خالی باشد، تعدادی به آن اضافه می‌کند. توجه داشته باشید، اگرچه این اپلیکیشن از Entity Framework Core و ClassDataContext برای داده‌های مانا استفاده می‌کند، هیچیکدام از آنها در کنترلر ظاهر نمی‌شوند. در عوض، مکانیزم دسترسی به داده‌های خاص، در پشت یک اینترفیس (ICharacterRepository) مخفی شده است (طبق الگوی طراحی ریپازیتوری). یک نمونه از ICharacterRepository از طریق سازنده درخواست می‌شود و به یک فیلد خصوصی اختصاص داده می‌شود، سپس برای دسترسی به کاراکتر‌ها در صورت لزوم استفاده می‌شود:
public class CharactersController : Controller
{
    private readonly ICharacterRepository _characterRepository;

    public CharactersController(ICharacterRepository characterRepository)
    {
        _characterRepository = characterRepository;
    }

    // GET: /characters/
    public IActionResult Index()
    {
        PopulateCharactersIfNoneExist();
        var characters = _characterRepository.ListAll();

        return View(characters);
    }

    private void PopulateCharactersIfNoneExist()
    {
        if (!_characterRepository.ListAll().Any())
        {
            _characterRepository.Add(new Character("Darth Maul"));
            _characterRepository.Add(new Character("Darth Vader"));
            _characterRepository.Add(new Character("Yoda"));
            _characterRepository.Add(new Character("Mace Windu"));
        }
    }
}

ICharacterRepository دو متد مورد نیاز کنترلر برای کار با نمونه‌های Character را تعریف می‌کند:
using System.Collections.Generic;
using DependencyInjectionSample.Models;

namespace DependencyInjectionSample.Interfaces
{
    public interface ICharacterRepository
    {
        IEnumerable<Character> ListAll();
        void Add(Character character);
    }
}
این اینترفیس با نوع واقعی CharacterRepository پیاده سازی شده است که در زمان اجرا استفاده می‌شود:

using System.Collections.Generic;
using System.Linq;
using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Models
{
    public class CharacterRepository : ICharacterRepository
    {
        private readonly ApplicationDbContext _dbContext;

        public CharacterRepository(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public IEnumerable<Character> ListAll()
        {
            return _dbContext.Characters.AsEnumerable();
        }

        public void Add(Character character)
        {
            _dbContext.Characters.Add(character);
            _dbContext.SaveChanges();
        }
    }
}
توجه داشته باشید که CharacterRepository یک ApplicationDbContext را در سازنده‌ی خود درخواست می‌کند. همانطور که مشاهده می‌شود هر وابستگی درخواست شده، به نوبه خود وابستگی‌های دیگری را درخواست می‌کند. تزریق وابستگی‌هایی به شکل زنجیره‌ای، همانند این مثال غیر معمول نیست. کانتینر مسئول resolve (نمونه سازی) همه‌ی وابستگی‌های موجود در گراف وابستگی و بازگرداندن سرویس کاملا resolve شده می‌باشد.

نکته
ایجاد شیء درخواست شده و تمامی اشیاء مورد نیاز شیء درخواست شده را گراف شیء می‌نامند. به همین ترتیب مجموعه‌ای از وابستگی‌هایی را که باید resolve شوند، به طور معمول، درخت وابستگی یا گراف وابستگی می‌نامند.

در مورد مثال مطرح شده، ICharacterRepository و به نوبه خود ApplicationDbContext باید با سرویس‌های خود در کانتینر ConfigureServices و کلاس Startup ثبت شوند. ApplicationDbContext با فراخوانی متد <AddDbContext<T پیکربندی می‌شود. کد زیر ثبت کردن نوع CharacterRepository را نشان می‌دهد:
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseInMemoryDatabase()
    );

    // Add framework services.
    services.AddMvc();

    // Register application services.
    services.AddScoped<ICharacterRepository, CharacterRepository>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();
    services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
    services.AddTransient<OperationService, OperationService>();
}
کانتکست انتیتی فریم ورک، با استفاده از متدهای کمکی که در تکه کد بالا نشان داده شده است، باید با طول عمر Scoped به کانتینر سرویس‌ها افزوده شود. این کار می‌تواند به صورت اتوماتیک انجام گیرد. همه‌ی ریپازیتوری‌هایی که از Entity Framework استفاده می‌کنند، باید از یک طول عمر مشابه استفاده کنند.

هشدار
خطر بزرگی را که باید در نظر گرفت، resolve کردن سرویس Scoped از طول عمر singleton می‌باشد. در صورت انجام این کار، احتمال دارد که سرویس‌ها وارد حالت نادرستی شوند.

سرویس‌هایی که وابستگی‌های دیگری هم دارند، باید آنها را در کانتینر ثبت کنند. اگر سازنده‌ی سرویس نیاز به یک primitive به عنوان ورودی داشته باشد، می‌توان با استفاده از الگوی گزینه‌ها و پیکربندی (options pattern and configuration)، ورودی‌های مناسبی را به سازنده‌ها منتقل کرد.


طول عمر سرویس‌ها و گزینه‌های ثبت

سرویس‌های ASP.NET را می‌توان با طول عمرهای زیر پیکربندی کرد:
Transient: سرویس‌هایی با طول عمر Transient، در هر زمان که درخواست می‌شوند، مجددا ایجاد می‌شوند. این طول عمر برای سرویس‌های سبک و بدون حالت مناسب می‌باشند.
Scoped: سرویس‌هایی با طول عمر Scoped، تنها یکبار در طی هر درخواست ایجاد می‌شوند.
Singleton: سرویس‌هایی با طول عمر Singleton، برای اولین باری که درخواست می‌شوند (یا اگر در ConfigureServices نمونه‌ای را مشخص کرده باشید) ایجاد می‌شوند و درخواست‌های آتی برای این سرویس‌ها از همان نمونه‌ی ایجاد شده استفاده می‌کنند. اگر اپلیکیشن شما درخواست رفتار singleton را داشته باشد، پیشنهاد می‌شود که سرویس کانتینر را برای مدیریت طول عمر سرویس مورد نیاز پیکربندی کنید و خودتان الگوی طراحی singleton را پیاده سازی نکنید.

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

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

using System;

namespace DependencyInjectionSample.Interfaces
{
    public interface IOperation
    {
        Guid OperationId { get; }
    }

    public interface IOperationTransient : IOperation
    {
    }
    public interface IOperationScoped : IOperation
    {
    }
    public interface IOperationSingleton : IOperation
    {
    }
    public interface IOperationSingletonInstance : IOperation
    {
    }
}
ما این اینترفیس‌ها را با استفاده از یک کلاس واحد به نام Operation پیاده سازی کرده‌ایم. سازنده‌ی این کلاس، یک Guid به عنوان ورودی می‌گیرد؛ یا اگر Guid برایش تامین نشد، خودش یک Guid جدید را می‌سازد.
سپس در ConfigureServices، هر نوع با توجه به طول عمر مورد نظر، به کانتینر افزوده می‌شود:
services.AddScoped<ICharacterRepository, CharacterRepository>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();
توجه داشته باشید که سرویس IOperationSingletonInstance، از یک نمونه‌ی خاص، با شناسه‌ی شناخته شده‌ی Guid.Empty استفاده می‌کند (این Guid فقط شامل اعداد صفر می‌باشد). بنابراین زمانیکه این تایپ مورد استفاده قرار می‌گیرد، کاملا واضح است. تمام این سرویس‌ها وابستگی‌های خود را به صورت پراپرتی نمایش می‌دهند. بنابراین می‌توان آنها را در View نمایش داد.

using DependencyInjectionSample.Interfaces;

namespace DependencyInjectionSample.Services
{
    public class OperationService
    {
        public IOperationTransient TransientOperation { get; }
        public IOperationScoped ScopedOperation { get; }
        public IOperationSingleton SingletonOperation { get; }
        public IOperationSingletonInstance SingletonInstanceOperation { get; }

        public OperationService(IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance instanceOperation)
        {
            TransientOperation = transientOperation;
            ScopedOperation = scopedOperation;
            SingletonOperation = singletonOperation;
            SingletonInstanceOperation = instanceOperation;
        }
    }
}
برای نشان دادن طول عمر اشیاء، در بین درخواست‌های جداگانه‌ی یک اپلیکیشن، مثال ذکر شده شامل کنترلر OperationsController می‌باشد که هر کدام از انواع IOperation و همچنین OperationService را درخواست می‌کند. سپس اکشن Index تمام مقادیر OperationId کنترل کننده و سرویس‌ها را نمایش می‌دهد:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
using Microsoft.AspNetCore.Mvc;

namespace DependencyInjectionSample.Controllers
{
    public class OperationsController : Controller
    {
        private readonly OperationService _operationService;
        private readonly IOperationTransient _transientOperation;
        private readonly IOperationScoped _scopedOperation;
        private readonly IOperationSingleton _singletonOperation;
        private readonly IOperationSingletonInstance _singletonInstanceOperation;

        public OperationsController(OperationService operationService,
            IOperationTransient transientOperation,
            IOperationScoped scopedOperation,
            IOperationSingleton singletonOperation,
            IOperationSingletonInstance singletonInstanceOperation)
        {
            _operationService = operationService;
            _transientOperation = transientOperation;
            _scopedOperation = scopedOperation;
            _singletonOperation = singletonOperation;
            _singletonInstanceOperation = singletonInstanceOperation;
        }

        public IActionResult Index()
        {
            // viewbag contains controller-requested services
            ViewBag.Transient = _transientOperation;
            ViewBag.Scoped = _scopedOperation;
            ViewBag.Singleton = _singletonOperation;
            ViewBag.SingletonInstance = _singletonInstanceOperation;

            // operation service has its own requested services
            ViewBag.Service = _operationService;
            return View();
        }
    }
}

حالا دو درخواست جداگانه برای این کنترلر ساخته شده است:



به تفاوت‌های موجود در مقادیر OperationId در یک درخواست و بین درخواستها توجه کنید:
-  OperationId اشیاء Transient همیشه متفاوت می‌باشند. چون یک نمونه جدید برای هر کنترلر و هر سرویس ایجاد شده‌است.
- اشیاء Scoped در یک درخواست، یکسان هستند؛ اما در درخواست‌های مختلف متفاوت می‌باشند.
- اشیاء Singleton برای هر شی‌ء و هر درخواست (صرف نظر از اینکه یک نمونه در ConfigureServices ارائه شده است) یکسان می‌باشند.


درخواست سرویس

در ASP.NET سرویس‌های موجود در یک درخواست HttpContext از طریق مجموعه RequestServices قابل مشاهده می‌باشد.


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


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


طراحی سرویس‌ها برای تزریق وابستگی‌ها

شما باید سرویس‌های خود را طوری طراحی کنید که از تزریق وابستگی‌ها برای ارتباطات خود استفاده نمایند. این کار باعث کاهش استفاده از فراخوانی‌های متدهای استاتیک (متدهای استاتیک، حالت دار می‌باشند و استفاده‌ی زیاد از آنها باعث به وجود آمدن بوی بد کدی به نام static cling، می‌شود) و همچنین از بین رفتن نیاز به نمونه سازی مستقیم کلاس‌های وابسته داخل سرویس‌ها، می‌شود. هر موقع بخواهید بین new کردن یک کلاس، یا درخواست دادن آن از طریق تزریق وابستگی، یکی را انتخاب کنید، این اصطلاح را به یاد بیاورید،  New is Glue. با پیروی از اصول SOLID طراحی شیء گرا، به طور طبیعی کلاس‌های شما تمایل به کوچک بودن، کارا و قابل تست بودن را دارند.
اگر متوجه شدید که کلاس‌های شما تمایل دارند تا تعداد وابستگی‌های زیادی به آنها تزریق شود، چه باید بکنید؟ به طور کلی این مشکل نشانه‌ای است از نقض  Single Responsibility Principle یا SRP است و احتمالا کلاس‌های شما وظایف بیش از اندازه‌ای را دارند. در این گونه موارد تلاش کنید مقداری از وظایف کلاس را به یک کلاس جدید منتقل کنید. در نظر داشته باشید که کلاس‌های کنترلر باید به مسائل UI تمرکز کنند و قوانین کسب و کار و جزئیات دسترسی به داده‌ها باید در کلاس‌هایی جداگانه و مرتبط با خود قرار داشته باشند.
به طور خاص برای دسترسی به داده ، شما می‌توانید DbContext را به کنترلر‌های خود تزریق کنید (با فرض اینکه شما EF را به کانتینر سرویس ConfigureServices اضافه کرده‌اید). بعضی از توسعه دهندگان به جای تزریق مستقیم DbContext از یک اینترفیس ریپازیتوری استفاده می‌نمایند. می‌توانید با استفاده از یک اینترفیس برای کپسوله کردن منطق دسترسی به داده‌ها در یک مکان، تعداد تغییرات مورد نیاز را در صورت تغییر دیتابیس، به حداقل برسانید.


تخریب سرویس ها

سرویس کانتینر برای نوع‌های IDisposable که خودش ایجاد کرده‌است، متد Dispose را فراخوانی خواهد کرد. با این حال، اگر شما خودتان نمونه‌ای را به صورت دستی نمونه سازی و به کانتینر اضافه کرده باشید، سرویس کانتینر آنرا dispose نخواهد کرد.

مثال:
// Services implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}

public void ConfigureServices(IServiceCollection services)
{
    // container will create the instance(s) of these types and will dispose them
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();

    // container did not create instance so it will NOT dispose it
    services.AddSingleton<Service3>(new Service3());
    services.AddSingleton(new Service3());
}

نکته:
در نسخه 1.0، کانتینر برای تمام اشیاء از نوع IDisposable از جمله اشیائی که خودش ایجاد نکرده بود، متد dispose را فراخوانی می‌کرد.


سرویس‌های کانتینر جانشین

کانتینر موجود در net core. به منظور تامین نیازهای اساسی فریم ورک ایجاد شده‌است و تعداد زیادی از اپلیکیشن‌ها از آن استفاده می‌کنند. با این حال، توسعه دهندگان می‌توانند کانتینرهای مورد نظر خود را جایگزین آن کنند. متد ConfigureServices به طور معمول مقدار void را بر می‌گرداند. اما با تغییر امضای آن به نوع بازگشتیIServiceProvider، می‌توان سرویس کانتینر متفاوتی را در اپلیکیشن پیکربندی کرد. سرویس‌های کانتینر IOC مختلفی برای NET. وجود دارند؛ در مثال زیر، Autofac استفاده شده است.
در ابتدا بسته‌های زیر را نصب کنید:
Autofac
Autofac.Extensions.DependencyInjection
سپس کانتینر را در ConfigureServices پیکربندی کنید و  IServiceProvider را به عنوان خروجی بازگردانید:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    // Add other framework services

    // Add Autofac
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterModule<DefaultModule>();
    containerBuilder.Populate(services);
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}


توصیه ها

هنگام کار با تزریق وابستگی‌ها، توصیه‌های ذیر را در نظر داشته باشید:
- DI برای اشیایی که دارای وابستگی پیچیده هستند، مناسب می‌باشد. کنترلرها، سرویس‌ها، آداپتورها و ریپازیتوری‌ها، نمونه‌هایی از این اشیاء هستند که می‌توانند به DI اضافه شوند.
- از ذخیره‌ی داده‌ها و پیکربندی مستقیم در DI اجتناب کنید. به عنوان مثال، معمولا سبد خرید کاربر نباید به سرویس کانتینر اضافه شود. پیکربندی باید از مدل گزینه‌ها استفاده کند. همچنین از اشیاء "data holder"، که فقط برای دسترسی دادن به اشیاء دیگر ایجاد شده‌اند، نیز اجتناب کنید. در صورت امکان بهتر است شیء واقعی مورد نیاز DI درخواست شود.
- از دسترسی استاتیک به سرویس‌ها اجتناب شود.
- از نمونه سازی مستقیم سرویس‌ها در کد برنامه خود اجتناب کنید.
- از دسترسی استاتیک به HttpContext اجتناب کنید.

توجه
مانند هر توصیه‌ی دیگری، ممکن است شما با شرایطی مواجه شوید که مجبور به نقض هر یک از این توصیه‌ها شوید. اما این موارد استثناء بسیار نادر می‌باشند و رعایت این نکات یک عادت برنامه نویسی خوب محسوب می‌شود.

مرجع: Introduction to Dependency Injection in ASP.NET Core
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر
ایجاد نام جداول به صورت جمع (Pluralized) و داینامیک :
اگه از روش خودکار کردن تعاریف DbSet ها استفاده کرده باشیم چون دیگر در داخل کلاس Context برنامه، Dbset تعریف نمی‌شود معمولا برای نام گذاری جداول که به صورت جمع باشند از اتریبیوت Table استفاده می‌کنیم.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Table("Users")]
public class User : BaseEntity
{
    public long Id { get; set; }

    [Required]
    public string FullName { get; set; } = null!;
}
برای اینکه نام جداول به صورت خودکار به صورت جمع ایجاد شوند میتوان از این متد استفاده کرد:
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;

namespace ProjectName.Common.EfHelpers;

public static class EfToolkit
{
    /// <summary>
    /// ایجاد نام موجودیت‌ها
    /// نام موجودیت اگر اتریبیوت تیبل داشته باشد
    /// از همان نام استفاده میشود و اگر نداشته باشد
    /// نامش جمع بسته میشود
    /// </summary>
    /// <param name="builder"></param>
    public static void MakeTableNamesPluralized(this ModelBuilder builder)
    {
        var entityTypes = builder.Model.GetEntityTypes();

        foreach (var entityType in entityTypes)
        {
            // Get the CLR type of the entity
            var entityClrType = entityType.ClrType;
            var hasTableAttribute = entityClrType.GetCustomAttribute<TableAttribute>();

            // Apply the pluralized table name for the entity
            if (hasTableAttribute is null)
            {
                // Get the pluralized table name
                var pluralizedTableName = GetPluralizedTableName(entityClrType);
                builder.Entity(entityClrType).ToTable(pluralizedTableName);
            }
        }
    }

    /// <summary>
    /// گرفتن نام تایپ و عوض کردن نام آن از مفرد به جمع
    /// Singular to plural
    /// Category => Categories
    /// Box => Boxes
    /// Bus => Buses
    /// Computer => Computers
    /// </summary>
    /// <param name="entityClrType"></param>
    /// <returns></returns>
    private static string GetPluralizedTableName(Type entityClrType)
    {
        // Example implementation (Note: This is a simple pluralization logic and might not cover all cases)
        var typeName = entityClrType.Name;
        if (typeName.EndsWith("y"))
        {
            // Substring(0, typeName.Length - 1)
            // Range indexer
            var typeNameWithoutY = typeName[..^1];
            return typeNameWithoutY + "ies";
        }

        if (typeName.EndsWith("s") || typeName.EndsWith("x"))
        {
            return typeName + "es";
        }
        return typeName + "s";
    }
}
نحوه فراخوانی در متد OnModelCreating:
protected override void OnModelCreating(ModelBuilder builder)
{
    // it should be placed here, otherwise it will rewrite the following settings!
    base.OnModelCreating(builder);
    builder.RegisterAllEntities(typeof(BaseEntity));
    builder.MakeTableNamesPluralized();
    builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
هر موجودیتی که اتریبیوت Table داشته باشد از همان نام برای جدول استفاده می‌شود در غیر اینصورت نام انتیتی به صورت جمع ایجاد می‌گردد.
قبل از اینکه نام جداول نیز جمع بسته شود تمامی موجودیت‌ها به صورت خودکار اضافه می‌شوند.
public static class EfToolkit
{
    /// <summary>
    /// ثبت تمامی انتیتی‌ها
    /// <param name="builder"></param>
    /// <param name="type"></param>
    /// </summary>
    public static void RegisterAllEntities(this ModelBuilder builder, Type type)
    {
        var entities = type.Assembly.GetTypes()
            .Where(x => x.BaseType == type);
        foreach (var entity in entities)
            builder.Entity(entity);
    }
}  
هر کلاسی در لایه موجودیت‌ها از BaseEntity ارث بری کرده باشد به عنوان یک Entity شناخته می‌شود.
مطالب
C# 8.0 - Nullable Reference Types
نوع‌های ارجاعی (Reference Types) در #C، همیشه نال‌پذیر بوده‌اند؛ در مقابل نوع‌های مقداری (value types) مانند DateTime که برای نال‌پذیر کردن آن‌ها باید یک علامت سؤال را در حین تعریف نوع آن‌ها ذکر کرد تا تبدیل به یک نوع نال‌پذیر شود (DateTime? Created). بنابراین عنوانی مانند «نوع‌های ارجاعی نال‌نپذیر» شاید آنچنان مفهوم نباشد.
خالق Null در زبان‌های برنامه نویسی، آن‌را یک اشتباه چند میلیارد دلاری می‌داند! و به عنوان یک توسعه دهنده‌ی دات نت، غیرممکن است که در حین اجرای برنامه‌های خود تابحال به null reference exception برخورد نکرده باشید. هدف از ارائه‌ی قابلیت جدید «نوع‌های ارجاعی نال‌نپذیر» در C# 8.0، مقابله‌ی با یک چنین مشکلاتی است و خصوصا غنی سازی IDEها برای ارائه‌ی اخطارهایی پیش از کامپایل برنامه، در مورد قسمت‌هایی از کد که ممکن است سبب بروز null reference exception شوند.


فعالسازی «نوع‌های ارجاعی نال‌نپذیر»

قابلیت «نوع‌های ارجاعی نال‌نپذیر» به صورت پیش‌فرض غیرفعال است. برای فعالسازی آن می‌توان فایل csproj را به صورت زیر، با افزودن خاصیت NullableContextOptions، ویرایش کرد:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <NullableContextOptions>enable</NullableContextOptions>
  </PropertyGroup>
</Project>
یک نکته: در نگارش‌های بعدی NET Core SDK. و همچنین ویژوال استودیو (از نگارش 16.2.0 به بعد)، خاصیت NullableContextOptions به صرفا Nullable تغییر نام یافته و ساده شده‌است. بنابراین اگر در این نگارش‌ها به خطاهای ذیل برخوردید:
CS8632: The annotation for nullable reference types should only be used in code within a ‘#nullable’ context.
CS8627: A nullable type parameter must be known to be a value-type or non-nullable reference type. Consider adding a ‘class’, ‘struct’ or type constraint.
صرفا به معنای استفاده‌ی از نام قدیمی این ویژگی است که باید به Nullable تغییر پیدا کند:
<PropertyGroup>
  <LangVersion>preview</LangVersion>
  <Nullable>enable</Nullable>
</PropertyGroup>
اما در زمان نگارش این مطلب که 3.0.100-preview5-011568 در دسترس است، فعلا همان نام قدیمی NullableContextOptions کار می‌کند.


تغییر ماهیت نوع‌های ارجاعی #C با فعالسازی NullableContextOptions


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


یک مثال: بررسی تاثیر فعالسازی NullableContextOptions بر روی یک پروژه

کلاس زیر را در نظر بگیرید:
    public class Person
    {
        public string FirstName { get; set; }

        public string MiddleName { get; set; }

        public string LastName { get; set; }

        public Person(string first, string last) =>
            (FirstName, LastName) = (first, last);

        public Person(string first, string middle, string last) =>
            (FirstName, MiddleName, LastName) = (first, middle, last);

        public override string ToString() => $"{FirstName} {MiddleName} {LastName}";
    }
با فعالسازی خاصیت NullableContextOptions، بلافاصله اخطار زیر در IDE ظاهر می‌شود (اگر ظاهر نشد، یکبار پروژه را بسته و مجددا بارگذاری کنید):


در این کلاس، دو سازنده وجود دارند که یکی MiddleName را دریافت می‌کند و دیگری خیر. در اینجا کامپایلر تشخیص داده‌است که چون در سازنده‌ی اولی که MiddleName را دریافت نمی‌کند، مقدار پیش‌فرض خاصیت MiddleName، نال خواهد بود و همچنین ما NullableContextOptions را نیز فعال کرده‌ایم، بنابراین این خاصیت دیگر به صورت معمول و متداول یک نوع ارجاعی نال‌پذیر عمل نمی‌کند و دیگر نمی‌توان نال را به عنوان مقدار پیش‌فرض آن، به آن نسبت داد. به همین جهت اخطار فوق ظاهر شده‌است.
برای رفع این مشکل:
به کامپایلر اعلام می‌کنیم: «می‌دانیم که MiddleName می‌تواند نال هم باشد» و آن‌را در این زمینه راهنمایی می‌کنیم:
public string? MiddleName { get; set; }
پس از این تغییر، اخطار فوق که ذیل سازنده‌ی اول کلاس Person ظاهر شده بود، محو می‌شود. اما اکنون مجددا کامپایلر، در جائیکه می‌خواهیم از آن استفاده کنیم:
    public static class NullableReferenceTypes
    {
        //#nullable enable // Toggle to enable

        public static string Exemplify()
        {
            var vahid = new Person("Vahid", "N");
            var length = GetLengthOfMiddleName(vahid);

            return $"{vahid.FirstName}'s middle name has {length} characters in it.";

            static int GetLengthOfMiddleName(Person person)
            {
                string middleName = person.MiddleName;
                return middleName.Length;
            }
        }
    }
اخطارهایی را صادر می‌کند:


در اینجا در متد محلی (local function) تعریف شده، سعی در دسترسی به خاصیت MiddleName وجود دارد و اکنون با تغییر جدیدی که اعمال کردیم، به صورت نال‌پذیر تعریف شده‌است.
همچنین در سطر بعدی آن نیز نتیجه‌ی نهایی middleName، مورد استفاده قرار گرفته‌است که آن نیز مشکل‌دار تشخیص داده شده‌است.
مشکل اولین سطر را به این صورت می‌توانیم برطرف کنیم:
var middleName = person.MiddleName;
در اینجا بجای ذکر صریح نوع string، از var استفاده شده‌است. پیشتر با ذکر صریح نوع string، آن‌را یک رشته‌ی نال‌نپذیر تعریف کرده بودیم. اما اکنون چون person.MiddleName نال‌پذیر تعریف شده‌است، var نیز به صورت خودکار به این رشته‌ی نال‌پذیر اشاره می‌کند.
اما مشکل سطر دوم هنوز باقی است:


علت اینجا است که متغیر middleName نیز اکنون ممکن است مقدار نال را داشته باشد. برای رفع این مشکل می‌توان از اپراتور .? استفاده کرد و سپس اگر مقدار نهایی این عبارت نال بود، مقدار صفر را بازگشت می‌دهیم:
static int GetLengthOfMiddleName(Person person)
{
   var middleName = person.MiddleName;
   return middleName?.Length ?? 0;
}
هدف از این قابلیت و ویژگی کامپایلر، کمک کردن به توسعه دهنده‌ها جهت نوشتن کدهایی امن‌تر و مقاوم‌تر به null reference exception‌ها است.


امکان خاموش و روشن کردن ویژگی نوع‌های ارجاعی نال‌نپذیر به صورت موضعی

زمانیکه خاصیت NullableContextOptions را فعال می‌کنیم، بر روی کل پروژه تاثیر می‌گذارد. برای مثال اگر یک چنین قابلیتی را بر روی پروژه‌های قدیمی خود فعال کنید، با صدها اخطار مواجه خواهید شد. به همین جهت است که این ویژگی حتی با فعالسازی C# 8.0 و انتخاب آن، به صورت پیش‌فرض غیرفعال است. بنابراین برای اینکه بتوان پروژه‌های قدیمی را قدم به قدم و سر فرصت، «مقاوم‌تر» کرد، می‌توان تعیین کرد که کدام قسمت، تحت تاثیر این ویژگی قرار بگیرد و کدام قسمت‌ها خیر:
public static class NullableReferenceTypes
{
#nullable disable // Toggle to enable
در اینجا می‌توان با استفاده از compiler directive جدید nullable# به کامپایلر اعلام کرد که از این قسمت صرفنظر کن. مقدار آن می‌تواند disable و یا enable باشد.


مجبور ساختن خود به «مقاوم سازی» برنامه

اگر NullableContextOptions را فعال کنید، کامپایلر صرفا یکسری اخطار را در مورد مشکلات احتمالی صادر می‌کند؛ اما برنامه هنوز کامپایل می‌شود. برای اینکه خود را مقید به «مقاوم سازی» برنامه کنیم، می‌توانیم با فعالسازی ویژگی TreatWarningsAsErrors در فایل csprj، این اخطارها را تبدیل به خطای کامپایلر کرده و از کامپایل برنامه جلوگیری کنیم:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <NullableContextOptions>enable</NullableContextOptions>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
</Project>
البته TreatWarningsAsErrors تمام اخطارهای برنامه را تبدیل به خطا می‌کند. اگر می‌خواهید انتخابی‌تر عمل کنید، می‌توان از خاصیت WarningsAsErrors استفاده کرد:
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>


آیا اگر برنامه‌ای با C# 7.0 کامپایل شود، کتابخانه‌های تهیه شده‌ی با C# 8.0 را می‌تواند استفاده کند؟

پاسخ: بله. از دیدگاه برنامه‌های قدیمی، کتابخانه‌های تهیه شده‌ی با C# 8.0، تفاوتی با سایر کتابخانه ندارند. آن‌ها نوع‌های نال‌پذیر جدید را مانند ?string مشاهده نمی‌کنند؛ آن‌ها فقط string را مشاهده می‌کنند و روش کار کردن با آن‌ها نیز همانند قبل است. بدیهی است در این حالت از مزایای کامپایلر C# 8.0 در تشخیص زود هنگام مشکلات برنامه محروم خواهند بود؛ اما عملکرد برنامه تفاوتی نمی‌کند.


وضعیت برنامه‌ی C# 8.0 ای که از کتابخانه‌های C# 7.0 و یا قبل از آن استفاده می‌کند، چگونه خواهد بود؟

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


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

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

1- هرجائیکه قرار است متغیر ارجاعی نال‌پذیر باشد، آن‌را صراحتا اعلام کنید.
string name = null; // ERROR
string? name = null; // OK!
این مثال را پیشتر بررسی کردیم. با فعالسازی ویژگی نوع‌های ارجاعی نال‌نپذیر، ماهیت آن‌ها نیز تغییر می‌کند و دیگر نمی‌توان به آن‌ها null را انتساب داد. اگر نیاز است حتما اینکار صورت گیرد، آن‌ها را توسط ? به صورت nullable تعریف کنید.
نمونه‌ی دیگر آن مثال زیر است:
public class Person
{
    public Address? Address { get; set; };
    public string Country => Address?.Country;   // ERROR! 
}
در اینجا Address یک نوع ارجاعی نال‌پذیر است. بنابراین حاصل Address?.Country می‌تواند نال باشد و به Country نال‌نپذیر قابل انتساب نیست. برای رفع این مشکل کافی است دقیقا مشخص کنیم که این رشته نیز نال‌پذیر است:
public class Person
{
    public Address? Address { get; set; };
    public string? Country => Address?.Country;  // OK!
}

البته در این حالت باید به مثال زیر دقت داشت:
var node = this; // Initialize non-nullable variable
while (node != null)
{
   node = null; // ERROR!
}
چون node در اینجا توسط var تعریف شده‌است، دقیقا نوع this را که non-nullable است، پیدا می‌کند. بنابراین بعدها نمی‌توان به آن null را انتساب داد. اگر چنین موردی نیاز بود، باید صریحا نوع آن‌را بدو امر، nullable تعریف کرد؛ چون هنوز امکان تعریف ?var میسر نیست:
Node? node = this;   // Initialize nullable variable
while (node != null) {
   node = null; // OK!
}


2- نوع‌های خود را مقدار دهی اولیه کنید.
در مثال زیر:
public class Person
{
   public string Name { get; set; } // ERROR!
}
در این حالت چون خاصیت Name، در سازنده‌ی کلاس مقدار دهی اولیه نشده‌است، یک اخطار صادر می‌شود که بیانگر احتمال نال بودن آن است. یک روش مواجه شدن با این مشکل، تعریف آن به صورت یک خاصیت نال‌پذیر است:
public class Person
{
   public string? Name { get; set; }
}

یا یک استثناء را صادر کنید:
public class Person
{
    public string Name { get; set; }
    public Person(string name) {
        Name = name ?? throw new ArgumentNullException(nameof(name));
    }
}
به این ترتیب کامپایلر می‌داند که اگر نام دریافتی نال بود، دقیقا باید چگونه رفتار کند.
البته در این حالت برای مقدار دهی اولیه‌ی Name، حتما نیاز به تعریف یک سازنده‌است و در این حالت کدهایی را که از سازنده‌ی پیش‌فرض استفاده کرده بودند (مانند new Person { Name = "Vahid" })، باید تغییر دهید.

راه‌حل دیگر، مقدار دهی اولیه‌ی این خواص بدون تعریف یک سازنده‌ی اضافی است:
public class Person
{
   public string Name { get; set; } = string.Empty;
   // -or-
   public string Name { get; set; } = "";
}
برای مثال می‌توان از مقادیر خالی زیر برای مقدار دهی اولیه‌ی رشته‌ها، آرایه‌ها و مجموعه‌ها استفاده کرد:
String.Empty
Array.Empty<T>()
Enumerable.Empty<T>()
یا حتی می‌توان اشیاء دیگر را نیز به صورت زیر مقدار دهی اولیه کرد:
public class Person
{
   public Address Address { get; set; } = new Address();
}
البته در این حالت باید مفهوم فلسفی «خالی بودن» را پیش خودتان تفسیر و تعریف کنید که دقیقا مقصود از یک آدرس خالی چیست؟ به همین جهت شاید تعریف این شیء به صورت nullable بهتر باشد.
مطالب
ذخیره تنظیمات متغیر مربوط به یک وب اپلیکیشن ASP.NET MVC با استفاده از EF
طی این  مقاله، نحوه‌ی ذخیره سازی تنظیمات متغیر و پویای یک برنامه را به صورت Strongly Typed ارائه خواهم داد. برای این منظور، یک API را که از Lazy Loading ، Cache ، Reflection و Entity Framework بهره میگیرد، خواهیم ساخت.
برنامه‌ی هدف ما که از این API استفاده می‌کند، یک اپلیکیشن Asp.net MVC است. قبل از شروع به ساخت API مورد نظر، یک دید کلی در مورد آنچه که قرار است در نهایت توسعه یابد، در زیر مشاهده میکنید:
public SettingsController(ISettings settings)
{
  // example of saving 
  _settings.General.SiteName = "دات نت تیپس";
  _settings.Seo.HomeMetaTitle = ".Net Tips";
  _settings.Seo.HomeMetaKeywords = "َAsp.net MVC,Entity Framework,Reflection";
  _settings.Seo.HomeMetaDescription = "ذخیره تنظیمات برنامه";
  _settings.Save();
}

همانطور که در کدهای بالا مشاهده میکنید، شی setting_ ما دارای دو پراپرتی فقط خواندنی بنام‌های General و Seo است که شامل  تنظیمات مورد نظر ما هستند و این دو کلاس از کلاس پایه‌ی SettingBase ارث بری کرده‌اند. دو دلیل برای انجام این کار وجود دارد:
  1. تنظیمات به صورت گروه بندی شده در کنار  هم قرار گرفته‌اند و یافتن تنظیمات برای زمانی که نیاز به دسترسی  به آنها داریم، راحت‌تر و ساده‌تر خواهد بود. 
  2. به این شکل تنظیمات قابل دسترس در یک گروه، از دیتابیس بازیابی خواهند شد.

اصلا چرا باید این تنظیمات را در دیتابیس ذخیره کنیم؟ 

شاید فکر کنید چرا باید تنظیمات را در دیتابیس ذخیره کنیم در حالی که فایل web.config در درسترس است و می‌توان توسط کلاس ConfigurationManager به اطلاعات آن دسترسی داشت.
جواب: دلیل این است که با تغییر فایل web.config، برنامه‌ی وب شما ری استارت خواهد شد (چه زمان‌هایی یک برنامه Asp.net ری استارت میشود).
برای جلوگیری از این مساله، راه حل مناسب برای ذخیره سازی اطلاعاتی که نیاز به تغییر در زمان اجرا دارند، استفاده از از دیتابیس می‌باشد. در این مقاله از Entity Framework و پایگاه داده Sql Sever استفاده می‌کنم.

مراحل ساخت Setting API مورد نظر به شرح زیر است:
  1. ساخت یک Asp.net Web Application 
  2. ساخت مدل Setting و افزودن آن به کانتکست Entity Framework 
  3. ساخت کلاس SettingBase برای بازیابی و ذخیره سازی تنظیمات با رفلکشن
  4. ساخت کلاس GenralSettins و SeoSettings که از کلاس SettingBase ارث بری کرده‌اند.
  5. ساخت کلاس Settings به منظور مدیریت تمام انواع تنظیمات 

یک برنامه‌ی Asp.Net Web Application را از نوع MVC ایجاد کنید. تا اینجا مرحله‌ی اول ما به پایان رسید؛ چرا که ویژوال استودیو کار‌های مورد نیاز ما را انجام خواهد داد.
 لازم است مدل خود را به ApplicationDbContext موجود در فایل IdentityModels.cs معرفی کنیم. به شکل زیر:
namespace DynamicSettingAPI.Models
{
    public interface IUnitOfWork
    {
        DbSet<Setting> Settings { get; set; }
        int SaveChanges();
    }
} 

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>,IUnitOfWork
    {
        public DbSet<Setting> Settings { get; set; }
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }


namespace DynamicSettingAPI.Models
{
    public class Setting
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string Value { get; set; }
    }
}
مدل تنظیمات ما خیلی ساده است و دارای سه پراپرتی به نام‌های Name ، Type ، Value هست که به ترتیب برای دریافت مقدار تنظیمات، نام کلاسی که از کلاس SettingBase ارث برده و نام تنظیمی که لازم داریم ذخیره کنیم، در نظر گرفته شده‌اند. 
لازم است تا متد OnModelCreating مربوط به ApplicationDbContext را نیز تحریف کنیم تا کانفیگ مربوط به مدل خود را نیز اعمال نمائیم.
 protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Setting>()
                    .HasKey(x => new { x.Name, x.Type });

            modelBuilder.Entity<Setting>()
                        .Property(x => x.Value)
                        .IsOptional();

            base.OnModelCreating(modelBuilder);
        }
ساختاری به شکل زیر مد نظر ماست:

  کلاس SettingBase ما همچین ساختاری را خواهد داشت:
namespace DynamicSettingAPI.Service
{
    public abstract class SettingsBase
    {
        //1
        private readonly string _name;
        private readonly PropertyInfo[] _properties;

        protected SettingsBase()
        {
            //2
            var type = GetType();
            _name = type.Name;
            _properties = type.GetProperties();
        }

        public virtual void Load(IUnitOfWork unitOfWork)
        {
            //3 get setting for this type name
            var settings = unitOfWork.Settings.Where(w => w.Type == _name).ToList();

            foreach (var propertyInfo in _properties)
            {
                //get the setting from setting list
                var setting = settings.SingleOrDefault(s => s.Name == propertyInfo.Name);
                if (setting != null)
                {
                    //4 set 
                    propertyInfo.SetValue(this, Convert.ChangeType(setting.Value, propertyInfo.PropertyType));
                }
            }
        }
        public virtual void Save(IUnitOfWork unitOfWork)
        {
            //5 get all setting for this type name
            var settings = unitOfWork.Settings.Where(w => w.Type == _name).ToList();

            foreach (var propertyInfo in _properties)
            {
                var propertyValue = propertyInfo.GetValue(this, null);
                var value = (propertyValue == null) ? null : propertyValue.ToString();

                var setting = settings.SingleOrDefault(s => s.Name == propertyInfo.Name);
                if (setting != null)
                {
                    // 6 update existing value
                    setting.Value = value;
                }
                else
                {
                    // 7 create new setting
                    var newSetting = new Setting()
                    {
                        Name = propertyInfo.Name,
                        Type = _name,
                        Value = value,
                    };
                    unitOfWork.Settings.Add(newSetting);
                }
            }
        }
    }
}
این کلاس قرار است توسط کلاس‌های تنظیمات ما به ارث برده شود و در واقع کارهای مربوط به رفلکشن را در این کلاس کپسوله کرده‌ایم. همانطور که مشخص است ما دو فیلد را به نام‌های name_ و properties_ به صورت فقط خواندنی در نظر گرفته ایم که نام کلاس مورد نظر ما که از این کلاس به ارث خواهد برد، به همراه پراپرتی‌های آن، در این ظرف‌ها قرار خواهند گرفت.
متد Load وظیفه‌ی واکشی تمام تنظیمات مربوط به Type و ست کردن مقادیر به دست آمده را به خصوصیات کلاس ما، برعهده دارد. کد زیر مقدار دریافتی از دیتابیس را به نوع داده پراپرتی مورد نظر تبدیل کرده و نتیجه را به عنوان Value پراپرتی ست میکند. 
propertyInfo.SetValue(this, Convert.ChangeType(setting.Value, propertyInfo.PropertyType));
متد Save نیز وظیفه‌ی ذخیره سازی مقادیر موجود در خصوصیات کلاس تنظیماتی را که از کلاس SettingBase ما به ارث برده است، به عهده دارد. 
این متد دیتا‌های موجود دردیتابیس را که متعلق به کلاس ارث برده مورد نظر ما هستند، واکشی میکند و در یک حلقه، اگر خصوصیتی در دیتابیس موجود بود، آن را ویرایش کرده وگرنه یک رکورد جدید را ثبت میکند.

  کلاس‌های تنظیمات شخصی سازی شده خود را به شکل زیر تعریف میکنیم :
  public class GeneralSettings : SettingsBase
    {
        public string SiteName { get; set; }
        public string AdminEmail { get; set; }
        public bool RegisterUsersEnabled { get; set; }
    }

 public class GeneralSettings : SettingsBase
    {
        public string SiteName { get; set; }
        public string AdminEmail { get; set; }
    }
نیازی به توضیح ندارد.
برای اینکه تنظیمات را به صورت یکجا داشته باشیم و Abstraction ای را برای استفاده از این API ارائه دهیم، یک اینترفیس و یک کلاس که اینترفیس مذکور را پیاده کرده است در نظر میگیریم: 
public interface ISettings
{
    GeneralSettings General { get; }
    SeoSettings Seo { get; }
    void Save();
}

public class Settings : ISettings
{
    // 1
    private readonly Lazy<GeneralSettings> _generalSettings;
    // 2
    public GeneralSettings General { get { return _generalSettings.Value; } }

    private readonly Lazy<SeoSettings> _seoSettings;
    public SeoSettings Seo { get { return _seoSettings.Value; } }

    private readonly IUnitOfWork _unitOfWork;
    public Settings(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
        // 3
        _generalSettings = new Lazy<GeneralSettings>(CreateSettings<GeneralSettings>);
        _seoSettings = new Lazy<SeoSettings>(CreateSettings<SeoSettings>);
    }

    public void Save()
    {
        // only save changes to settings that have been loaded
        if (_generalSettings.IsValueCreated)
            _generalSettings.Value.Save(_unitOfWork);

        if (_seoSettings.IsValueCreated)
            _seoSettings.Value.Save(_unitOfWork);

        _unitOfWork.SaveChanges();
    }
    // 4
    private T CreateSettings<T>() where T : SettingsBase, new()
    {
        var settings = new T();
        settings.Load(_unitOfWork);
        return settings;
    }
}
این اینترفیس مشخص می‌کند که ما به چه نوع تنظیماتی، دسترسی داریم و متد Save آن برای آپدیت کردن تنظیمات، در نظر گرفته شده است. هر کلاسی که از کلاس SettingBase ارث بری کرده را به صورت فیلد فقط خواندنی و با استفاده از کلاس Lazy درون آن ذکر میکنیم و به این صورت کلاس تنظیمات ما زمانی ساخته خواهد شد که برای اولین بار به آن دسترسی داشته باشیم.
متد CreateSetting وظیفه‌ی لود دیتا را از دیتابیس، بر عهده دارد که برای این منظور، متد لود Type مورد نظر را فراخوانی میکند. این متد وقتی به کلاس تنظیمات مورد نظر برای اولین بار دسترسی پیدا کنیم، فراخوانی خواهد شد.

 حتما امکان این وجود دارد که شما از امکان Caching هم بهره ببرید برای مثال همچین متد و سازنده‌ای را در کلاس Settings در نظر بگیرید:
private readonly ICache _cache;
public Settings(IUnitOfWork unitOfWork, ICache cache)
{
    // ARGUMENT CHECKING SKIPPED FOR BREVITY
    _unitOfWork = unitOfWork;
    _cache = cache;
    _generalSettings = new Lazy<GeneralSettings>(CreateSettingsWithCache<GeneralSettings>);
    _seoSettings = new Lazy<SeoSettings>(CreateSettingsWithCache<SeoSettings>);
}

private T CreateSettingsWithCache<T>() where T : SettingsBase, new()
{
    // this is where you would implement loading from ICache
    throw new NotImplementedException();
}
در آخر هم به شکل زیر میتوان (به عنوان دمو فقط ) از این API استفاده کرد.
   public ActionResult Index()
        {
            using (var uow = new ApplicationDbContext())
            {
                var _settings = new Settings(uow);
                _settings.General.SiteName = "دات نت تیپس";
                _settings.General.AdminEmail = "admin@gmail.com";
                _settings.General.RegisterUsersEnabled = true;
                _settings.Seo.HomeMetaTitle = ".Net Tips";
                _settings.Seo.MetaKeywords = "Asp.net MVC,Entity Framework,Reflection";
                _settings.Seo.HomeMetaDescription = "ذخیره تنظیمات برنامه";

                var settings2 = new Settings(uow);
                var output = string.Format("SiteName: {0} HomeMetaDescription: {1}  MetaKeywords:  {2}  MetaTitle:  {3}  RegisterEnable:  {4}",
                    settings2.General.SiteName,
                    settings2.Seo.HomeMetaDescription,
                    settings2.Seo.MetaKeywords,
                    settings2.Seo.HomeMetaTitle,
                    settings2.General.RegisterUsersEnabled.ToString()
                    );
                return Content(output);
            }

        }

خروجی :

نکته: در پروژه ای که جدیدا در سایت ارائه داده‌ام و در حال تکمیل آن هستم، از بهبود یافته‌ی این مقاله استفاده می‌شود. حتی برای اسلاید شو‌های سایت هم میشود از این روش استفاده کرد و از فرمت json بهره برد برای این منظور. حتما در پروژه‌ی مذکور همچین امکانی را هم در نظر خواهم گرفتم.
پیشنها میکنم سورس SmartStore را بررسی کنید. آن هم به شکل مشابهی ولی پیشرفته‌تر از این مقاله، همچین امکانی را دارد.