مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت دوم - الگوی Service Locator
در قسمت قبل برای دریافت وهله‌ای از سرویس TestService، به صورت ()<serviceProvider.GetService<ITestService عمل کردیم. این روش در اصل الگوی Service Locator نام دارد که جزئیات بیشتری از آن‌را در این قسمت بررسی خواهیم کرد.


قلب سیستم تزریق وابستگی‌های NET Core. اینترفیس IServiceProvider است

IServiceProvider که اساس IoC Container برنامه‌های مبتنی بر NET Core. را تشکیل می‌دهد، در اسمبلی System.ComponentModel و در فضای نام System تعریف شده‌است:
namespace System
{
    public interface IServiceProvider
    {
        object GetService(Type serviceType);
    }
}
زمانیکه به کمک IServiceCollection، تمام اینترفیس‌ها و کلاس‌های خود را به IoC Container معرفی کردیم، مرحله‌ی بعدی، فراهم آوردن روشی برای دریافت وهله‌ای از این سرویس‌ها توسط متد GetService است.
استفاده‌ی مستقیم از اینترفیس IServiceProvider برای دسترسی به وهله‌های سرویس‌ها، اصطلاحا الگوی Service Locator نامیده می‌شود و باید تا حد ممکن از آن پرهیز کرد؛ چون وابستگی مستقیمی از IoC Container را درون کدهای ما قرار می‌دهد و به این ترتیب یک مرحله، نوشتن آزمون‌های واحد برای آن‌را مشکل‌تر می‌کند؛ چون زمان وهله سازی از یک سرویس، دقیقا مشخص نیست به چه وابستگی‌هایی نیاز دارد. به همین جهت همیشه باید با روش تزریق وابستگی‌ها در سازنده‌ی کلاس شروع کرد و اگر به هر دلیلی این روش مهیا نبود و توسط سیستم تزریق وابستگی‌های جاری شناسایی و یا پشتیبانی نمی‌شد (مانند تزریق وابستگی در سازنده‌های Attributes)، آنگاه می‌توان به الگوی Service Locator مراجعه کرد.
برای مثال در اکثر قسمت‌های برنامه‌های ASP.NET Core امکان تزریق وابستگی‌ها در سازنده‌ی کنترلرها، میان افزارها و سایر اجزای آن وجود دارد و در این حالات نیازی به مراجعه‌ی مستقیم به IServiceProvider برای دریافت وهله‌های سرویس‌های مورد نیاز نیست. به عبارتی نگرانی در مورد IServiceProvider بهتر است مشکل IoC Container باشد و نه ما.

در مثال زیر، روش استفاده‌ی از IServiceProvider را جهت انجام تزریق وابستگی‌ها (یا به عبارتی بهتر، روش دسترسی به وهله‌های وابستگی‌ها) را مشاهده می‌کنید:
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace CoreIocServices
{
    public interface IProductService
    {
        void Delete(int id);
    }

    public class ProductService : IProductService
    {
        private readonly ITestService _testService;
        private readonly ILogger<ProductService> _logger;

        public ProductService(IServiceProvider serviceProvider)
        {
            _testService = serviceProvider.GetRequiredService<ITestService>();
            _logger = serviceProvider.GetService<ILogger<ProductService>>() ?? NullLogger<ProductService>.Instance;
        }

        public void Delete(int id)
        {
            _testService.Run();
            _logger.LogInformation($"Deleted a product with id = {id}");
        }
    }
}
این روش یا کار مستقیم با Service locator، هر چند کار می‌کند، اما روشی است که باید تا حد ممکن از آن پرهیز کنید؛ زیرا:
- با نگاه کردن به امضای سازنده‌ی این سرویس مشخص نیست که دقیقا از چه وابستگی‌هایی استفاده می‌کند. اینکار نوشتن آزمون‌های واحد آن‌را مشکل می‌کند.
- این سرویس یک وابستگی اضافه‌تر را به نام IServiceProvider، نیز پیدا کرده‌است که اگر از روش متداول تزریق وابستگی‌ها در سازنده‌ی کلاس استفاده می‌شد، نیازی به ذکر آن نبود.
- پیاده سازی Dispose Pattern در این حالت مشکل‌تر است و در قسمتی دیگر بررسی خواهد شد.



تفاوت‌های بین متدهای ()<GetService<T  و  ()<GetRequiredService<T

از آنجائیکه دیگر از NET 1.0. استفاده نمی‌کنیم، استفاده‌ی از متد GetService با امضایی که در اینترفیس IServiceProvider تعریف شده و strongly typed نیست، بیشتر برای کارهای پویا مناسب است. به همین جهت دو نگارش جنریک از آن در اسمبلی Microsoft.Extensions.DependencyInjection.Abstractions با امضای زیر تعریف شده‌اند که نمونه‌ای از آن‌را در قسمت قبل نیز استفاده کردیم و برای استفاده‌ی از آن‌ها ذکر فضای نام Microsoft.Extensions.DependencyInjection ضروری است:
namespace Microsoft.Extensions.DependencyInjection
{
    public static class ServiceProviderServiceExtensions
    {
        public static T GetRequiredService<T>(this IServiceProvider provider);
        public static T GetService<T>(this IServiceProvider provider);
    }
}
اکنون این سؤال مطرح می‌شود که تفاوت‌های بین این دو متد چیست؟
- متد GetService یک شیء سرویس از نوع T را بازگشت می‌دهد و یا نال؛ اگر سرویسی از نوع T، پیشتر به سیستم معرفی نشده باشد.
- متد GetRequiredService یک شیء سرویس از نوع T را بازگشت می‌دهد و یا اگر سرویسی از نوع T پیشتر به سیستم معرفی نشده باشد، استثنای InvalidOperationException را صادر می‌کند.

بنابراین تنها تفاوت این دو متد، در نحوه‌ی رفتار آن‌ها با درخواست وهله‌ای از یک سرویس پیشتر ثبت نشده‌است؛ یکی نال را باز می‌گرداند و دیگری یک استثناء را صادر می‌کند.


با توجه به این تفاوت‌ها کدامیک از متدهای GetService و یا GetRequiredService را باید استفاده کرد؟

همانطور که پیشتر نیز در توضیحات الگوی Service locator عنوان شد، هیچکدام! ابتدا با تزریق وابستگی‌های در سازنده‌ی کلاس شروع کنید و اگر تامین این وابستگی، توسط IoC Container جاری پشتیبانی نمی‌شد، آنگاه نیاز به استفاده‌ی از یکی از نگارش‌های متد GetService خواهد بود و متد توصیه شده نیز GetRequiredService است و نه GetService؛ به این دلایل:
- حذف کدهای تکراری: اگر از GetService استفاده کنید، نیاز خواهید داشت پس از تمام فراخوانی‌های آن، بررسی نال بودن آن‌را نیز انجام دهید. برای حذف این نوع کدهای تکراری، بهتر است از همان متد GetRequiredService استفاده کنید که به صورت توکار این بررسی را نیز انجام می‌دهد.
- پشتیبانی از روش Fail Fast و یا همان Defensive programming: اگر بررسی نال بودن GetService را فراموش کنید، در سطرهای بعدی، یافتن علت NullReferenceException صادر شده مشکل‌تر از رسیدگی به InvalidOperationException صادر شده‌ی توسط GetRequiredService خواهد بود که توضیحات دقیقی را در مورد سرویس ثبت نشده ارائه می‌دهد.
- اگر بر روی IoC Container پیش‌فرض NET Core. یک IoC Container دیگر را مانند AutoFac قرار داده‌اید، استفاده‌ی از GetRequiredService، سبب می‌شود تا اینگونه IoC Containerهای ثالث بتوانند اطلاعات مفیدتری را از سرویس‌های ثبت نشده ارائه دهند.

تنها حالتی که استفاده‌ی از روش GetService را نیاز دارد، شرطی کردن ثبت و معرفی کردن سرویس‌ها به IoC Container است؛ اگر سرویسی ثبت شده بود، آنگاه قطعه کدی اجرا شود.
مطالب
ارسال خودکار مطلب به بلاگر

اکثر خدمات گوگل دارای API هم هستند و به این ترتیب با استفاده از برنامه نویسی نیز می‌توان به آن‌ها دسترسی پیدا کرد. برای نمونه API دسترسی به Blogger در اینجا توضیح داده شده است. برای کار با این امکانات یا می‌توان چرخ را از نو اختراع کرد یا از کتابخانه‌های مرتبطی همانند Gdata API for .NET استفاده نمود. برای دات نت فریم ورک، از آدرس http://code.google.com/p/google-gdata/ می‌توان آخرین کتابخانه‌های کار با GData یا Google Data API را دریافت کرد. برای نمونه فایل Google_Data_API_Setup_1.9.0.0.msi فعلی آن حدود 28 مگ حجم دارد و به درد کسانی می‌خورد که علاقمند هستند تا تمام امکانات موجود آن‌را بررسی کنند. راه ساده‌تری هم برای دسترسی به این کتابخانه‌ها وجود دارد؛ می‌توان از NuGet استفاده کرد.


به این ترتیب به سادگی و سرعت هرچه تمامتر فایل 200 کیلوبایتی Google.GData.Client.dll دریافت شده و ارجاعی نیز به آن اضافه خواهد شد. همین حد جهت کار با بلاگر کافی است.
برای نمونه قطعه کد زیر کار ارسال یک مطلب جدید به وبلاگ بلاگری شما را انجام خواهد داد:

using System;
using System.Collections.Generic;
using Google.GData.Client;

namespace BloggerAutoPoster
{
public class BloggerAutoPoster
{
public string UserName { set; get; }

public string Password { set; get; }

public string PostTitle { set; get; }

public IList<string> PostTags { set; get; }

public string PostBody { set; get; }

public string BlogUrl { set; get; }

public bool PostAsDraft { set; get; }

public bool PostNewEntry()
{
var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(UserName, Password)
};
var newPost = constructNewEntry();
var result = service.Insert(new Uri(BlogUrl), newPost);
return result != null;
}

private AtomEntry constructNewEntry()
{
var newPost = new AtomEntry
{
Title = { Text = PostTitle },
Content = new AtomContent
{
Content = string.Format(@"<div xmlns=""http://www.w3.org/1999/xhtml"">{0}</div>", PostBody),
Type = "xhtml"
},
IsDraft = PostAsDraft
};

foreach (var tag in PostTags)
{
newPost.Categories.Add(
new AtomCategory
{
Term = tag,
Scheme = "http://www.blogger.com/atom/ns#"
});
}

return newPost;
}
}
}

مثالی از استفاده آن هم به صورت زیر می‌باشد:

new BloggerAutoPoster
{
BlogUrl = "https://www.blogger.com/feeds/number/posts/default",
UserName = "name@gmail.com",
Password = "pass",
PostTitle = "بررسی ارسل خودکار-3",
PostTags = new List<string> { "بررسی ارسال خودکار" },
PostBody = "تست می‌شود123",
PostAsDraft = false
}.PostNewEntry();

نام کاربری و کلمه عبور آن، همان مشخصات وارد شدن به اکانت جی‌میل شما است. اگر می‌خواهید مطلب ارسالی بلافاصله در سایت ظاهر نشود PostAsDraft را true کنید. همچنین BlogUrl آن، همانطور که ملاحظه می‌کنید فرمت خاصی دارد. جهت یافتن آن می‌توان از قطعه کد زیر کمک گرفت:

using System;
using System.Collections.Generic;
using System.Linq;
using Google.GData.Client;

namespace BloggerAutoPoster
{
public class BlogInfo
{
public string Title { set; get; }
public string Url { set; get; }
}

public class BloggerInfo
{
public static IList<BlogInfo> FindMyBlogsUrls(string username, string password)
{
var result = new List<BlogInfo>();

var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(username, password)
};

var query = new FeedQuery { Uri = new Uri("https://www.blogger.com/feeds/default/blogs") };
var feed = service.Query(query);

if (feed == null)
throw new NotSupportedException("You don't have any blogs!");

foreach (var entry in feed.Entries)
{
result.AddRange(entry.Links.Where(t => t.Rel.Equals("http://schemas.google.com/g/2005#post"))
.Select(t => new BlogInfo
{
Url = new Uri(t.HRef.ToString()).AbsoluteUri,
Title = entry.Title.Text
}));
}

return result;
}
}
}

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


بازخوردهای دوره
تبدیلگر تاریخ شمسی برای AutoMapper
ممنون. آقای نصیری من کدهای شمارو استفاده کردم و خطای زیر برای من رخ داد :

Type 'System.String' does not have a default constructor

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.ArgumentException: Type 'System.String' does not have a default constructor


من به این صورت نوشتم :

            CreateMap<DateTime, string>().ConvertUsing(new DateTimeToPersianDateTimeConverter());

            CreateMap<User, UserViewModel>();

این هم model و viewmodel :

 public class User : IdentityUser<long, UserLogin, UserRole, UserClaim>
    {
        public virtual DateTime RegisterDate { get; set; }
        // bla bla
    }


  public class UserViewModel
    {
        public string RegisterDate { get; set; }
    }

public List<UserViewModel> GetUsersByPage(int pageNumber, string pagerSortBy, string pagerSortOrder, string term, out int records,
            int recordsPerPage)
{
    //bla bla bla
    return users.Skip(skipRecords) /*این خط خطا داره*/
                .Take(recordsPerPage)
                .Project(_mappingEngine).To<UserViewModel>().Future().ToList();
}

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

CreateMap<User, UserViewModel>()
.ForMember(d => d.RegisterDate, m => m.MapFrom(s => s.RegisterDate.ToString()));

ممنون میشم راهنمایی کنید.
نظرات مطالب
ASP.NET MVC #4
هنگامی که روی Action راست کلیک میکنم و درخواست ساختن View متناظر را می‌دهم، یک Dialoge باز می‌شود که می‌توان نام View  را تعیین کرد. اگر نام View را متفاوت با نام Action قرار بدهم و View  را بسازم و سپس پروژه را اجرا کنم، با درخواست آن Action، فریمورک به دنبال View ای با نام خود Action  میگردد. 
سوال بنده این است که اگر لزوما نام Action  و  View باید یکسان باشد پس چرا MVC امکان تغییر نام View را به ما می‌دهد؟ آیا برای لینک کردن Action و View  با نام متفاوت باید تنظیمات خاصی انجام داد؟
مطالب
روشی برای مقایسه‌ی مقادیر تمام خواص دو شیء در آزمون‌های واحد
در زمان نوشتن تست‌های مختلف (Unit - Integration - UI) گاهی اوقات پیش می‌آید که بخواهید تمامی خصوصیت‌های یک شیء را تایید کنید. معمولا نوشتن اعتبارسنجی برای همه خصوصیت‌ها و همین طور پیام‌های استثناء برای هر یک در زمان عدم تایید اعتبار، کار بسیار زمانبری است. در این مقاله به شما نشان خواهم داد که چگونه با نوشتن یک اعتبارسنج عمومی از اتلاف زمان زیادی جلوگیری کنید.

با استفاده از کلاس زیر می‌توان کار اعتبارسنجی را با استفاده از Reflection به راحتی انجام داد. در اینجا برای اعتبارسنجی DateTime از کلاس DateTimeAssert استفاده کرده‌ایم.
public class PropertiesValidator<TK, T> where T : new() where TK : new()
{
    static TK _instance;

    public static TK Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new TK();
            }
            return _instance;
        }
    }

    public void Validate(T expectedObject, T realObject, params string[] propertiesNotToCompare)
    {
        var properties = realObject.GetType().GetProperties();
        foreach (var currentRealProperty in properties)
        {
            if (!propertiesNotToCompare.Contains(currentRealProperty.Name))
            {
                var currentExpectedProperty = expectedObject.GetType().GetProperty(currentRealProperty.Name);
                var exceptionMessage = $"The property {currentRealProperty.Name} of class {currentRealProperty.DeclaringType?.Name} was not as expected.";

                if (currentRealProperty.PropertyType != typeof(DateTime) && currentRealProperty.PropertyType != typeof(DateTime?))
                {
                    Assert.AreEqual( currentExpectedProperty.GetValue( expectedObject,
                                                                        null ),
                                        currentRealProperty.GetValue( realObject,
                                                                    null ),
                                        exceptionMessage );
                }
                else
                {
                    DateTimeAssert.Validate( currentExpectedProperty.GetValue( expectedObject,
                                                                                null ) as DateTime?,
                                                currentRealProperty.GetValue( realObject,
                                                                            null ) as DateTime?,
                                                TimeSpan.FromMinutes( 5 ) );
                }
            }
        }
    }
}


طرز استفاده

فرض کنید مدلی داریم با این مشخصات:
public class ObjectToAssert
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime LastVisit { get; set; }
}
و دو نمونه از آن را ایجاد کرده ایم:
var expectedObject = new ObjectToAssert
                        {
                            FirstName = "Vahid",
                            LastName = "Mohammad Taheri",
                            LastVisit = new DateTime( 2016, 11, 14, 0, 10, 50 )
                        };
var actualObject = new ObjectToAssert
                        {
                            FirstName = "Vahid",
                            LastName = "Mohammad Taheri",
                            LastVisit = new DateTime( 2016, 11, 14, 0, 13, 50 )
                        };
کلاسی را با ارث بری از PropertiesValidator ایجاد می‌کنیم:
public class ObjectToAssertValidator : PropertiesValidator<ObjectToAssertValidator, ObjectToAssert>
{
    public void Validate(ObjectToAssert expected, ObjectToAssert actual)
    {
        this.Validate(expected, actual, "FirstName");
    }
}

نکته
: در صورتی که می‌خواهید خصوصیتی را استثناء کنید از اعتبارسنجی، می‌توانید آن‌را به عنوان پارامتر سوم به بعد به تابع Validate ارسال کنید. طبق کد بالا FirstName به صورت استثناء تعریف شده است.


اکنون دو نمونه ساخته شده از ObjectToAssert بالا را با فراخوانی دستور زیر اعتبارسنجی می‌کنیم:
ObjectToAssertValidator.Instance.Validate(expectedObject, actualObject);
نظرات مطالب
طراحی گردش کاری با استفاده از State machines - قسمت دوم
- بله. برای نمونه تریگر Save بر روی حالت InProgress، در مثال جاری وجود دارد.
- آیا برنامه‌ی وب یا چند ریسمانی هست؟ اگر بله از روش‌های قفل گذاری روی قسمتی از کد، برای جلوگیری از دسترسی همزمان به آن (حتی چند کلیک پشت سر هم کاربر هم در عمل همین معنا را پیدا می‌کند) استفاده کنید:
// فقط یک ترد امکان دسترسی به کد را داشته باشد
private static readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1,1);

public async Task RunAsync()
{
   // شروع تمام تردها معلق خواهند شد تا این سمافور به پایان برسد
   // پس از پایان کار ترد جاری، فقط یک ترد در حال انتظار، مجوز دسترسی به قطعه کد بعدی را خواهد یافت و به همین ترتیب برای سایر تردها
   await _semaphoreSlim.WaitAsync(); 
   try
   {
       await Task.Delay(20000); // Your code here
   }
   finally
   {
      _semaphoreSlim.Release();
   }
}
- خطاها هم می‌توانند یک state جدید مانند Rejected مثال این مطلب باشند.
نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
// فقط یک ترد امکان دسترسی به کد را داشته باشد
private static readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1,1);

public override async Task RunAsync()
{
   // شروع تمام تردها معلق خواهند شد تا این سمافور به پایان برسد
   // پس از پایان کار ترد جاری، فقط یک ترد در حال انتظار، مجوز دسترسی به قطعه کد بعدی را خواهد یافت و به همین ترتیب برای سایر تردها
   await _semaphoreSlim.WaitAsync(); 
   try
   {
       await Task.Delay(20000); // Your code here
   }
   finally
   {
      _semaphoreSlim.Release();
   }
}
مطالب
استفاده از الگوی Adapter در تزریق وابستگی‌ها

در بعضی از مواقع ممکن است که در هنگام استفاده از اصل تزریق وابستگی‌ها، با یک مشکل روبرو شویم و آن این است که اگر از کلاسی استفاده می‌کنیم که به سورس آن دسترسی نداریم، نمی‌توانیم برای آن یک Interface تهیه کنیم و اصل (Depend on abstractions, not on concretions) از بین می‌رود، حال چه باید کرد.
برای اینکه موضوع تزریق وابستگی‌ها (DI) به صورت کامل در قسمتهای دیگر سایت توضیح داده شده است، دوباره آن را برای شما بازگو نمی‌کنیم .
لطفا به کد‌های ذیل توجه کنید:

کد بدون تزریق وابستگیها

به سازنده کلاس ProductService و تهیه یک نمونه جدید از وابستگی مورد نیاز آن دقت نمائید:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

namespace ASPPatterns.Chap2.Service
{
    public class Product
    {
    }

    public class ProductRepository
    {
        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products = new List<Product>();
            // Database operation to populate products …
            return products;
        }
    }

    public class ProductService
    {
        private ProductRepository _productRepository;
        public ProductService()
        {
            _productRepository = new ProductRepository();
        }

        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products;
            string storageKey = string.Format("products_in_category_id_{0}", categoryId);
            products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
            if (products == null)
            {
                products = _productRepository.GetAllProductsIn(categoryId);
                HttpContext.Current.Cache.Insert(storageKey, products);
            }
            return products;
        }
    }
}

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

using System;
using System.Collections.Generic;

namespace ASPPatterns.Chap2.Service
{
    public interface IProductRepository
    {
        IList<Product> GetAllProductsIn(int categoryId);
    }

    public class ProductRepository : IProductRepository
    {
        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products = new List<Product>();
            // Database operation to populate products …
            return products;
        }
    }

    public class ProductService
    {
        private IProductRepository _productRepository;
        public ProductService(IProductRepository  productRepository)
        {
            _productRepository = productRepository;
        }

        public IList<Product> GetAllProductsIn(int categoryId)
        {
            //…
        }
    }
}
همانطور که ملاحظه می‌کنید به علت دسترسی به سورس، به راحتی برای استفاده از کلاس ProductRepository در کلاس ProductService، از تزریق وابستگی‌ها استفاده کرده‌ایم.
اما از این جهت که شما دسترسی به سورس Http context class را ندارید، نمی‌توانید به سادگی یک Interface را برای آن ایجاد کنید و سپس یک تزریق وابستگی را مانند کلاس ProductRepository برای آن تهیه نمائید.
خوشبختانه این مشکل قبلا حل شده است و الگویی که به ما جهت پیاده سازی آن کمک کند، وجود دارد و آن الگوی آداپتر (Adapter Pattern)  می‌باشد.
این الگو عمدتا برای  ایجاد یک Interface از یک کلاس به صورت یک Interface سازگار و قابل استفاده می‌باشد. بنابراین می‌توانیم این الگو را برای تبدیل HTTP Context caching API به یک API سازگار و قابل استفاده به کار ببریم.
در ادامه می‌توان Interface سازگار جدید را در داخل productservice که از اصل تزریق وابستگی‌ها (DI ) استفاده می‌کند تزریق کنیم.

یک اینترفیس جدید را با نام ICacheStorage به صورت ذیل ایجاد می‌کنیم:

public interface ICacheStorage
{
    void Remove(string key);
    void Store(string key, object data);
    T Retrieve<T>(string key);
}
حالا که شما یک اینترفیس جدید دارید، می‌توانید کلاس produceservic را به شکل ذیل به روز رسانی کنید تا از این اینترفیس، به جای HTTP Context استفاده کند.
public class ProductService
{
    private IProductRepository _productRepository;
    private ICacheStorage _cacheStorage;
    public ProductService(IProductRepository  productRepository,
    ICacheStorage cacheStorage)
    {
        _productRepository = productRepository;
        _cacheStorage = cacheStorage;
    }

    public IList<Product> GetAllProductsIn(int categoryId)
    {
        IList<Product> products;
        string storageKey = string.Format("products_in_category_id_{0}", categoryId);
        products = _cacheStorage.Retrieve<List<Product>>(storageKey);
        if (products == null)
        {
            products = _productRepository.GetAllProductsIn(categoryId);
            _cacheStorage.Store(storageKey, products);
        }
        return products;
    }
}
مسئله ای که در اینجا وجود دارد این است که HTTP Context Cache API صریحا نمی‌تواند Interface ایی که ما ایجاد کرده‌ایم را اجرا کند.
پس چگونه الگوی Adapter می‌تواند به ما کمک کند تا از این مشکل خارج شویم؟
هدف این الگو به صورت ذیل در GOF مشخص شده است .«تبدیل  Interface از یک کلاس به یک Interface مورد انتظار Client»
تصویر ذیل، مدل این الگو را به کمک UML نشان می‌دهد:
 

همانطور که در این تصویر ملاحظه می‌کنید، یک Client ارجاعی به یک Abstraction در تصویر (Target) دارد (ICacheStorage در کد نوشته شده). کلاس Adapter اجرای Target را بر عهده دارد و به سادگی متدهای Interface را نمایندگی می‌کند. در اینجا کلاس Adapter، یک نمونه از کلاس Adaptee را استفاده می‌کند و در هنگام اجرای قراردادهای Target، از این نمونه استفاده خواهد کرد.

اکنون کلاس‌های خود را در نمودار UML قرار می‌دهیم که به شکل ذیل آنها را ملاحظه می‌کنید.
 


در شکل ملاحظه می‌نمایید که یک کلاس جدید با نام HttpContextCacheAdapter مورد نیاز است. این کلاس یک کلاس روکش (محصور کننده یا Wrapper) برای متدهای HTTP Context cache است. برای اجرای الگوی Adapter کلاس HttpContextCacheAdapter را به شکل ذیل ایجاد می‌کنیم:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
namespace ASPPatterns.Chap2.Service
{
    public class HttpContextCacheAdapter : ICacheStorage
    {
        public void Remove(string key)
        {
            HttpContext.Current.Cache.Remove(key);
        }

        public void Store(string key, object data)
        {
            HttpContext.Current.Cache.Insert(key, data);
        }

        public T Retrieve<T>(string key)
        {
            T itemStored = (T)HttpContext.Current.Cache.Get(key);
            if (itemStored == null)
                itemStored = default(T);
            return itemStored;
        }
    }
}
حال به سادگی می‌توان یک caching solution دیگر را پیاده سازی کرد بدون اینکه در کلاس ProductService  اثر یا تغییری ایجاد کند .

منبع : Professional Asp.Net Design Pattern
 
نظرات مطالب
ASP.NET MVC #9
routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional, area = "" } // Parameter defaults
            );
بر اساس تعریف مسیریابی فوق (Parameter defaults آن) که در قسمت چهارم بحث شد، اگر نام کنترلری وارد نشود، از نام پیش فرض Home استفاده می‌شود (مثلا برای مدیریت آدرس‌های ریشه سایت http://localhost و یا http://site.com). اگر جایی در آدرسی، نام اکشن متدی ذکر نشود، از نام متد پیش فرضی به نام Index استفاده می‌شود و الی آخر.
مطالب
C# 7 - Throw Expressions
در طراحی زبان #C، واژه‌ی کلیدی throw همیشه یک statement بوده‌است و نه یک expression. برای مثال از آن نمی‌توان در قطعه کدهای شرطی و عبارات lambda استفاده کرد. برای رفع این محدودیت، در C# 7 کار معرفی «throw expressions» صورت گرفته‌است.


throw expressions در C# 7

روش تعریف throw expressions همانند روش متداول تعریف آن‌ها است و از این لحاظ تغییری صورت نگرفته‌است. تنها تغییر انجام شده، امکان استفاده‌ی از آن در محل‌هایی است که پیشتر مجاز نبوده‌است.
برای نمونه قطعه کد متداول ذیل را درنظر بگیرید:
public class MyApiType
{
    private object _loadedResource;
    private object _someProperty;
 
    public MyApiType()
    {
        _loadedResource = LoadResource();
        if (_loadedResource == null) throw new InvalidOperationException();
    }
 
    public object SomeProperty
    {
        get
        {
            return _someProperty;
        }
 
        set
        {
            if (value == null) throw new ArgumentNullException();
            _someProperty = value;
        }
    }
}
در اینجا با روش fail fast، کار بررسی null بودن مقادیر دریافتی در سازنده و همچنین یک خاصیت، صورت گرفته‌اند و در صورت نال بودن، یک استثناء صادر می‌شود.
اکنون در C# 7 می‌توان قطعه کد فوق را به صورت ذیل خلاصه کرد:
public class MyApiType
{
    private object _loadedResource = LoadResource() ?? throw new InvalidOperationException();
    private object _someProperty;
 
    public object SomeProperty
    {
        get
        {
            return _someProperty;
        } 
        set
        {
            _someProperty = value ?? throw new ArgumentNullException();
        }
    }
}
از این جهت که واژه‌ی کلیدی throw در C#7 هم statement است و هم expression، اکنون می‌توان از آن در عبارات شرطی و همچنین null-coalescing expressions نیز استفاده کرد. به علاوه امکان استفاده‌ی از آن‌را در یک «initialization expression» مانند loadedResource_ نیز مشاهده می‌کنید.

به علاوه اگر نکات «Expression-bodied Members» را نیز اعمال کنیم، اینبار به قطعه کد خلاصه‌تر ذیل خواهیم رسید:
public class MyApiType
{
    private object _loadedResource = LoadResource() ?? throw new InvalidOperationException();
    private object _someProperty;
 
    public object SomeProperty
    {
        get => _someProperty;
        set => _someProperty = value ?? throw new ArgumentNullException();
    }
}


یک مثال ترکیبی دیگر

قطعا تا پیش از C# 7 یک چنین بررسی‌های شرطی را در حین تزریق وابستگی‌ها، در سازنده‌ی کلاس‌های سرویس خود انجام داده‌اید:
public partial class ShippingAddressPage
{
    private readonly IWebDriver driver;
    public ShippingAddressPage(IWebDriver driver)
    {
        if (driver == null)
        {
            throw new ArgumentNullException(nameof(driver));
        }
        this.driver = driver;
    }
}
اکنون در C# 7 می‌توان قطعه کد فوق را به صورت ذیل خلاصه کرد:
public partial class ShippingAddressPage
{
    private readonly IWebDriver driver;
    public ShippingAddressPage(IWebDriver driver) =>
      this.driver = driver ?? throw new ArgumentNullException(nameof(driver));
}
که ترکیبی است از throw expressions و همچنین Expression-bodied Members.


مثالی از کاربرد throw expressions در یک conditional ternary operator

private static int ThrowUsageInAnExpression(int value = 40)
{
    return value < 20 ? value : throw new ArgumentOutOfRangeException("Argument value must be less than 20");
}
مثال‌هایی را که تا اینجا بررسی کردیم بیشتر به null-coalescing expressions مرتبط بودند. در اینجا یک نمونه کاربرد throw expression را در یک conditional ternary operator مشاهده می‌کنید.