نظرات اشتراک‌ها
معرفی کتابخانه‌ی DNTScanner.Core
ممنون از زحمات.در مورد این کتابخونه امکانش هست راهنمایی بفرمایید.
اگر اشتباه نوشتم اصلاح بفرمایید.
اگر بخوام این کتابخونه رو در پروژه ای استفاده کنم به نظرم باید علاوه بر ناگت ، خروجی پروژه ویندوز سرویس رو هم به طریقی از سمت قسمت وب اجرا کنم . و سیستمی که اسکنر در اون نصب شده باید برنامه ویندوز سرویس رو اجرا کنه . پس در واقع اگر کلاینت دارای اسکنر بود و سرور دارای پروژه من ، به طریقی باید ارتباط signalr رو بین این دو بخش برقرار کنم ؟
اشتراک‌ها
یک معماری برای پروژه های Asp.net core web api

ساخت یک API عالی بستگی به یک معماری عالی دارد. در اینجا گذری در جنبه‌های طراحی api و ویژگیهای توسعه در asp.net core و فلسفه معماری و الگوهای طراحی انجام گرفته شده.

یک معماری برای پروژه های Asp.net core web api
بازخوردهای پروژه‌ها
t4 برای Context Per Request
با سلام و احترام
میخواستم ببینم شما برای معماری که در قسمت 12 مربوط به EFCodeFirst معرفی کردید  فایل t4   میشه معرفی کنید که نزدیک به این معماری یا اینکه خود این باشه؟


مطالب
Functional Programming - قسمت چهارم - برخورد با Exception ها
چنانچه قسمت‌های قبلی سری آموزش برنامه نویسی تابعی Functional Programming را مطالعه نکرده‌اید، پیشنهاد میکنم قبلا آن‌ها را  (+  و  +  و  +) قبل از شروع بخوانید. در این قسمت قرار است تاثیر استثناءها (exception) را بر روی کدها بررسی کرده و راهکاری را از جنس functional برایش ارائه کنیم. 



Exception و خوانایی کد

تکه کد زیر را در نظر بگیرید: یک Action معمولی در Asp.Net MVC که یک نام را دریافت کرده و یک کارمندرا ایجاد میکند:

public ActionResult CreateEmployee(string name) { 
    try { 
        ValidateName(name);
        // ادامه کد‌ها return View("با موفقیت ثبت شد");
        }
    catch (ValidationException ex) 
    { 
        return View("خطا", ex.Message);
    }
}

private void ValidateName(string name) { 
    if (string.IsNullOrWhiteSpace(name)) 
        throw new ValidationException("نام نمی‌تواند خالی باشد");

    if (name.Length > 100) 
        throw new ValidationException("نام نمی‌تواند طولانی باشد");
}

در این قطعه کد، در متد ValidateName، در صورت معتبر نبودن ورودی، یک Exception رخ میدهد و بلاک کد try/catch، این exception را دریافت کرده و خطای مناسبی را به کاربر نشان خواهد داد. تا اینجا ظاهرا همه چیز مرتب است و مشکلی ندارد! احتمالا کد‌های مشابه به این کد را زیاد دیده‌اید. در اینجا متد ValidateName، صادق نیست. در قسمت اول، در مورد Honesty صحبت کردیم. به عبارت ساده‌تر شما از امضای این متد نمی‌توانید به نوع خروجی و کاری که قرار است انجام دهد، پی ببرید. در واقع شما همیشه باید پیاده سازی متد را گوشه‌ای، در ذهن خود داشته باشید و برای اطمینان از کاری که متد انجام میدهد، همیشه باید به بدنه‌ی متد برگردیم. اگر به‌خاطر داشته باشید، توابع برنامه نویسی را به توابع ریاضی تشبیه کردیم. پس میتوانیم بگوییم: 

به عبارت دیگر وقتی از exception‌ها برای کنترل flow برنامه استفاده میکنید، مشابه کاری را انجام می‌دهید که دستور GOTO انجام می‌داد. این دستور در روش‌های قبل از برنامه نویسی ساخت یافته وجود داشت و توسط یک دانشمند هلندی به نام آقای دیکسترا حذف شد. وقتی از دستور GOTO یا JUMP استفاده میکنیم، فهمیدن flow برنامه پیچیدگی‌های زیادی را خواهد داشت. چراکه فراخوانی قطعه‌های کد و متد‌ها، وابستگی شدیدی خواهند داشت و البته میتوان گفت استفاده از exception‌ها برای کنترل جریان برنامه، می‌توانند از GOTO هم بد‌تر باشند؛ چرا که exception میتواند از لایه‌های مختلف کد نیز عبور کند.

امیدوارم تا اینجا به یک عقیده‌ی مشترک رسیده باشیم. خوب راهکار چیست؟ تصور کنید که تکه کد بالا را به صورت زیر تبدیل کنیم: 

public ActionResult CreateEmployee(string name) { 
    string error = ValidateName(name);

 if (error != string.Empty) 
        return View("خطا", error);
    // ادامه کد‌ها return View("با موفقیت ثبت شد");
}

private string ValidateName(string name) { 

    if (string.IsNullOrWhiteSpace(name)) 
        return "نام نمی‌تواند خالی باشد";

    if (name.Length > 100) 
        return "طول نام نمی‌تواند بیشتر از 100 کاراکتر باشد";

    return string.Empty;
}

با refactor ای که انجام دادیم، متد ValidateName را به یک تابع ریاضی تبدیل کردیم. به این معنا که هر آنچه را که از امضای متد، مشخص است، انجام می‌دهد و در این حالت چیزی مخفی نیست. توجه داشته باشید که این راهکار نهایی ما نیست و لطفا مقاله را تا انتها بخوانید!  



موارد استفاده Exception

با همه‌ی بدی‌هایی که از Exception‌ها گفتیم، با این حساب پس چه زمانی از آن استفاده کنیم؟

  1. Exception‌ها واقعا برای موارد استثنائی هستند.
  2. Exception‌ها برای شرایطی هستند که به معنای واقعی یک باگ باشند.
  3. منتظر رخ دادن Exception نباشیم! 

در توضیح مورد سوم، در اعتبار سنجی داده‌های کاربر (Validation) انتظار داده‌ی نادرستی را می‌توان داشت، پس نمی‌توانیم آن را یک حالت استثنایی بدانیم. معماری زیر را در نظر بگیرید


دیتایی که به API ما ارسال خواهد شد، همیشه شامل عملیات Filter یا به عبارتی Validation است و از آنجایی که می‌توان انتظار استفاده‌ی نادرست یا دیتای نادرست را داشت، نمیتوانیم این را حالتی از استثنائات در نظر بگیریم؛ ولی بر خلاف آن، وقتی در دامین پروژه و ارتباط بین دامین‌های مختلف، دیتایی رد و بدل می‌شود که معتبر نیست، میتوانیم آن را جزء استثناء‌ها در نظر بگیریم. به مثال زیر دقت کنید:

public ActionResult UpdateEmployee(int employeeId, string name) { 
    string error = ValidateName(name);
    
    if (error != string.Empty) 
        return View("Error", error);
    
    Employee employee = GetEmployee(employeeId); 
    employee.UpdateName(name);
}

public class Employee { 

    public void UpdateName(string name){

        if (name == null) 
            throw new ArgumentNullException();
        
        // ادامه کد‌ها }
}

در قطعه کد بالا تصور این است که کلاس Employee و متد UpdateName خارج از دامین می‌باشند. همانطورکه مشاهده میکنید، ما در action controller، از خالی نبودن نام اطمینان حاصل کردیم و سپس آن را به متد UpdateName ارجاع دادیم. ولی اگه به بدنه‌ی متد UpdateName دقت کنید، می‌بینید که مجددا از خالی نبودن نام اطمینان حاصل کرده‌ایم و در صورت خالی بودن، یک Exception را صادر میکنیم! به این مدل چک کردن‌ها در دامین‌های مختلف، معمولا guard clause گفته می‌شود و یک نوع قرارداد بین برنامه نویس هاست. اگر طبق تعریفی که بالاتر ارائه کردیم هم چک کنیم، میتوانیم حدس بزنیم که خالی بودن نام، نشان یک باگ در نرم افزار است! 



مفهوم fail fast

تا اینجا متوجه شدیم که از exception‌ها باید در شرایط استثنائی استفاده کنیم. خوب با توجه به این مساله، چه طور میتوانیم آن‌ها را Handle کنیم؟ این سؤال ما را به مفهومی به نام fail fast می‌رساند. این مفهوم به ما میگوید:

  • کار جاری را به محض یک اتفاق استثنائی باید متوقف کنیم.
  • رعایت این نکته در نهایت ما را به یک نرم افزار پایدار خواهد رساند.


برای درک هر چه بهتر این موضوع، بیایید به عکس این حالت نگاه کنیم؛ اصطلاحا Fail Silently.

متد زیر را ببینید: 

public void ProcessItems(List<Item> items) { 

    foreach (Item item in items) { 
        try { 
            Process(item);
 } 
        catch (Exception ex) 
        { 
            Logger.Log(ex);
 }
 }
 }

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

به صور خلاصه مهم‌ترین مزیت Fail Fast را میتوانیم به صورت زیر خلاصه کنیم:

  • مسیر رسیدن به خطا‌ها سر راست‌تر می‌شود.
  • نرم افزار به پایداری مناسبی خواهد رسید.
  • از اعتبار دیتای ذخیره شده اطمینان خواهیم داشت.


کجا exception‌ها را به دام بیندازیم؟

در یکی از حالت‌های زیر:

  • لاگ کردن
  • متوقف کردن عملیات
  • هیچ گاه در بلاک catch هیچ منطقی را پیاده نکنید.


حالت دیگر در استفاه از کتابخانه‌های دیگران (3rd parties) است. به طور مثال در استفاده از EF ممکن است به دلیل عدم برقراری ارتباط با دیتابیس، خطایی را دریافت کنید. در این حالت با توجه به نکات فوق، با این استثنائات برخورد کنید:

  • جلوی این نوع استثنائات را در پایین‌ترین حد ممکن در کد خود بگیرید.
  • Exception هایی را catch کنید که میدانید در حالت استثناء، چه کاری را می‌توانید انجام دهید.


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

قطعه کد زیر را در نظر بگیرید:

public void CreateCustomer(string name) { 
    Customer customer = new Customer(name); 
    bool result = SaveCustomer(customer);
    if (!result) { 
        MessageBox.Show("Error connecting to the database. Please try again later.");
    }
}

private bool SaveCustomer(Customer customer) { 
    try { 
        using (MyContext context = new MyContext()) { 
            context.Customers.Add(customer);
         context.SaveChanges();
        } 
        return true;
    }
    catch (DbUpdateException ex) { 
        if (ex.Message == "Unable to open the DB connection") 
            return false; 
        else 
            throw;
    }
}

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

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

  • Honest بودن متد را نگه دارد.
  • خروجی متد را به همراه وضعیت اجرا شدن برگرداند.
  • شکل یکسانی را برای خطا‌ها داشته باشد.
  • فقط جلوی خطا‌های غیر منتظره را بگیرد.


برای مثال کد بالا را به شکل زیر refactor می‌کنیم:

private Result SaveCustomer(Customer customer) { 
    try { 

        using (var context = new MyContext()) { 

            context.Customers.Add(customer); 
            context.SaveChanges();
 } 

        return Result.Ok();
    } 
    catch (DbUpdateException ex) { 
        if (ex.Message == "Unable to open the DB connection") 
            Result.Fail(ErrorType.DatabaseIsOffline);

        if (ex.Message.Contains("IX_Customer_Name")) 
            return Result.Fail(ErrorType.CustomerAlreadyExists);

        throw;
    }
}

به عبارتی با این روش میتوانیم از انجام شدن/نشدن عملیات اطمینان حاصل کنیم و خروجی/دلیل انجام نشدن را نیز میتوانیم برگردانیم.

اگر به امضای متد‌های زیر نگاه کنیم، می‌توانیم آن‌ها را طبق الگوی CQS دسته‌بندی کنیم: 

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

مطالب
امکان بررسی سلامت برنامه در ASP.NET Core 2.2
ASP.NET Core 2.2 به همراه تعدادی قابلیت جدید است که یکی از آن‌ها بررسی سلامت برنامه یا Health Check نام دارد. در بسیاری از اوقات ممکن است از سرویس‌های ping و یا درخواست مشاهده‌ی صفحات وب سایت در بازه‌های زمانی مشخصی، جهت اطمینان حاصل کردن از برپایی و سلامت آن استفاده کنید. اما این سرویس‌ها الزاما وضعیت سلامت برنامه را نمی‌توانند به خوبی گزارش کنند. به همین جهت امکان ارائه‌ی گزارش‌های دقیق‌تری توسط ویژگی Health Check به ASP.NET Core اضافه شده‌است.

پیاده سازی ویژگی Health Check بدون استفاده از قابلیت‌های ASP.NET Core 2.2

اگر بخواهیم در بررسی سلامت برنامه، وضعیت بانک اطلاعاتی آن‌را گزارش دهیم، می‌توان یک چنین اکشن متدی را طراحی کرد که در آن اتصالی به بانک اطلاعاتی باز شده و اگر در حین فراخوانی مسیر working/، استثنائی رخ داد، با بازگشت status code مساوی 503، عدم سلامت برنامه اعلام شود؛ کاری که سرویس‌های ping متداول نمی‌توانند آن‌را با این دقت انجام دهند:
[Route("working")]
public ActionResult Working()
{
    using (var connection = new SqlConnection(_connectionString))
    {
        try
        {
            connection.Open();
        }
        catch (SqlException)
        {
            return new HttpStatusCodeResult(503, "Generic error");
        }
    }
   return new EmptyResult();
}

بازنویسی قطعه کد فوق با ویژگی جدید Health Check در ASP.NET Core 2.2

اکنون اگر بخواهیم قطعه کد فوق را با کمک ویژگی‌های جدید ASP.NET Core 2.2 بازنویسی کنیم، روش کار به صورت زیر خواهد بود:
namespace MvcHealthCheckTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHealthChecks()
                    .AddCheck("sql", () =>
                        {
                            using (var connection = new SqlConnection(Configuration["connectionString"]))
                            {
                                try
                                {
                                    connection.Open();
                                }
                                catch (SqlException)
                                {
                                    return HealthCheckResult.Unhealthy();
                                }
                            }
                            return HealthCheckResult.Healthy();
                        });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseHealthChecks("/working");
- ابتدا توسط متد services.AddHealthChecks، سرویس بررسی سلامت برنامه، ثبت و معرفی می‌شود.
- سپس توسط متد app.UseHealthChecks، بدون اینکه نیاز باشد کنترلر و اکشن متد جدیدی را جهت بازگشت وضعیت سلامت برنامه، تعریف کنیم، مسیر working/ قابل دسترسی خواهد شد.
تا اینجا اگر این مسیر را به سرویس بررسی uptime برنامه‌ی خود معرفی کنید، صرفا وضعیت قابل دسترسی بودن مسیر working/ را دریافت خواهید کرد. اگر نیاز به گزارش دقیق‌تری وجود داشت، می‌توان به کمک متد AddCheck، یک منطق سفارشی را نیز به آن افزود؛ همانند بررسی امکان اتصال به بانک اطلاعاتی، به روشی که ملاحظه می‌کنید. در اینجا اگر منطق مدنظر با موفقیت اجرا شد، HealthCheckResult.Healthy بازگشت داده می‌شود و یا HealthCheckResult.Unhealthy در صورت عدم موفقیت. هر کدام از این متدها می‌توانند توضیحات و یا اطلاعات بیشتری را نیز توسط پارامترهای خود ارائه دهند.


امکان تهیه سرویس‌های سفارشی بررسی سلامت برنامه

در مثال قبل، منطق بررسی سلامت برنامه را همانجا داخل متد ConfigureServices، به کمک متد services.AddHealthChecks().AddCheck معرفی کردیم. امکان انتقال این کدها به سرویس‌های سفارشی، با پیاده سازی اینترفیس IHealthCheck نیز وجود دارد:
    public class SqlServerHealthCheck : IHealthCheck
    {
        private readonly IConfiguration _configuration;

        public SqlServerHealthCheck(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public Task<HealthCheckResult> CheckHealthAsync(
            HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var connection = new SqlConnection(_configuration["connectionString"]))
            {
                try
                {
                    connection.Open();
                }
                catch (SqlException)
                {
                    return Task.FromResult(HealthCheckResult.Unhealthy());
                }
            }
            return Task.FromResult(HealthCheckResult.Healthy());
        }
    }
در اینجا کدهای AddCheck را به متد CheckHealthAsync منتقل کردیم. پس از آن برای معرفی آن به سیستم می‌توان از روش زیر استفاده کرد:
namespace MvcHealthCheckTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHealthChecks()
                    .AddCheck<SqlServerHealthCheck>("sql");
متد AddCheck، کلاس SqlServerHealthCheck را به صورت یک سرویس جدید با طول عمر Transient به سیستم تزریق وابستگی‌های NET Core. معرفی می‌کند (یعنی با هربار درخواست مسیر working/، یک وهله‌ی جدید از این کلاس ساخته شده و استفاده می‌شود) که امکان تزریق در سازنده‌ی کلاس آن نیز وجود دارد.


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

تا اینجا از متدهای کلی Unhealthy و Healthy برای بازگشت وضعیت سلامت برنامه استفاده کردیم؛ خروجی‌های بهتری را نیز می‌توان ارائه داد:
public Task<HealthCheckResult> CheckHealthAsync(
            HealthCheckContext context,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var connection = new SqlConnection(_configuration["connectionString"]))
            {
                try
                {
                    connection.Open();
                }
                catch (SqlException)
                {
                    return Task.FromResult(new HealthCheckResult(
                                                   status: context.Registration.FailureStatus,
                                                   description: "It is dead!"));
                }
            }
            return Task.FromResult(HealthCheckResult.Healthy("Healthy as a horse"));
        }
در نهایت نیاز است خروجی از نوع HealthCheckResult بازگشت داده شود. این خروجی را یا می‌توان توسط متدهای Healthy و Unhealthy با پارامترهای مخصوص آن‌ها ایجاد کرد و یا مانند این مثال، توسط وهله سازی مستقیم آن.
روش دیگر سفارشی سازی خروجی آن، استفاده از پارامتر دوم متد app.UseHealthChecks است:
namespace MvcHealthCheckTest
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseHealthChecks("/working", new HealthCheckOptions
            {
                ResponseWriter = async (context, report) =>
                {
                    var result = JsonConvert.SerializeObject(new
                    {
                        status = report.Status.ToString(),
                        errors = report.Entries.Select(e =>
                        new
                        {
                            key = e.Key,
                            value = Enum.GetName(typeof(HealthStatus), e.Value.Status)
                        })
                    });
                    context.Response.ContentType = MediaTypeNames.Application.Json;
                    await context.Response.WriteAsync(result);
                }
            });
در اینجا یک خروجی JSON، از ریز خطاهای گزارش شده، تهیه شده و توسط context.Response.WriteAsync به فراخوان ارائه می‌شود.


معرفی کتابخانه‌ای از IHealthCheckهای سفارشی

از مخزن کد AspNetCore.Diagnostics.HealthChecks می‌توانید IHealthCheckهای سفارشی مخصوص SQL Server، MySQL و غیره را نیز دریافت و استفاده کنید.
مطالب
آموزش LightInject IoC Container - قسمت 1
LightInject در حال حاضر یکی از قدرتمند‌ترین IoC Container‌‌ها است که از لحاظ سرعت و کارآیی در بالاترین جایگاه در میان IoC Container‌‌های موجود قرار دارد. جهت بررسی کارایی IoC Container‌ها می‌توانید به این لینک مراجعه کنید . LightInject یک IoC Container فوق العاده سبک وزن می‌باشد که تمامی قابلیت‌های متداولی که از یک Service Container انتظار می‌رود را شامل می‌شود. تنها شامل یک فایل .cs می‌باشد که تمامی کدهای آن در همین یک فایل نوشته شده‌اند. در پروژه‌های کوچک تا بزرگ بدون از دست دادن کارآیی، با بالاترین سرعت ممکن عمل تزریق وابستگی را انجام می‌دهد. در این مجموعه مقالات به بررسی کامل این IoC Container می‌پردازیم و تمامی قابلیت‌های آن را آموزش می‌دهیم.

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

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

PM> Install-Package LightInject.Source

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

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

    public interface IPersonRepository:IRepository<PersonModel>
    {
    }

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

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

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

    public class PersonService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

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

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

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

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

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

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

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

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

    public class WorkerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

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

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

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

    public class ManagerService:IPersonService
    {
        private readonly IPersonRepository _personRepository;

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

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

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

در طی این چند سالی که کارم برنامه نویسی ASP.Net بوده است، هیچ نرم افزار پایه‌ای همانند Exchange server در کیفیت کارهای من تاثیر نداشته است و اساسا تمام هماهنگی‌های برنامه‌های من با کمک Exchange server و Outlook صورت می‌گیرد. از گرفتن تائید تا آلارم فلان درخواست تا یک سری از گزارشات زمانبندی شده و غیره. دیگر کار به جایی رسیده است که اگر کاربران ایمیلی را دریافت نکنند اطلاعات وارد شده در برنامه‌ها را معتبر نمی‌دانند و به برنامه‌ها مراجعه نمی‌کنند!
به همین منظور کتابچه‌ی راهنمای نصب و راه اندازی Exchange server 2010 را تهیه کرده‌ام که در طی حدودا 120 صفحه، به صورت کاربردی و ساده، آنچه را که باید برای راه اندازی و مدیریت این برنامه در یک سازمان بدانید، در اختیار شما قرار می‌دهد.

مقدمه‌ی کتابچه

برنامه‌ی Exchanger server ، نرم افزار ارسال و دریافت ایمیل سازمانی مایکروسافت است که در رده‌ی محصولات سرور آن شرکت قرار می‌گیرد. اولین نگارش Exchanger server در سال 1993 ارائه شد و به صورت محدود در اختیار حدود 500 شرکت قرار گرفت. در سال 1996 اولین نگارش عمومی آن به نام Exchange server 4.0 به عموم ارائه گشت و در سال 1997 با ارائه‌ی Exchange server 5.0 آمار مشتریان این محصول به 32 هزار کاربر رسید. به همراه این نگارش، اولین نسخه‌ی برنامه Outlook web access نیز ارائه گردید. با افزایش استقبال از این محصول، در همان سال نگارش 5.5 آن نیز ارائه شد و ظرفیت بانک‌های اطلاعاتی آن تا 16TB در نگارش سازمانی آن افزایش یافت. در سال 2000، اولین نگارش یکپارچه‌ی آن با Active directory ارائه شد. بزرگترین مشکل این محصول در سال 2000، کوچ از نگارش‌های قدیمی به نگارش جدید بودند که این مشکل در سال 2003 با ارائه Exchange server 2003 برطرف گردید. در اواخر سال 2006 ، Exchange server 2007 ارائه گشت که مهمترین تفاوت آن با نگارش‌های قبلی، ارائه‌ی آن تنها برای سکوهای 64 بیتی بود به همراه بهبودهایی در اندازه‌ی صندوق‌های پستی تا 2 گیگابایت، تضمین فعالیت بی‌وقفه بهبود یافته ، شروع به کنار گذاری Public folders و تمهیداتی جهت ارسال و دریافت پیغام‌های صوتی. در اواخر سال 2009 نگارش 2010 این محصول ارائه گشت که تنها با ویندوز سرور 2008 سازگار می‌باشد و در آن عمده‌ی مشکلات به همراه نگارش 2007 آن برطرف شده است همانند Outlook web access سازگار با تمامی مرورگرهای امروزی، امکان مدیریت تحت وب صندوق‌های پستی کاربران، بازنگری در گزینه‌های تضمین فعالیت بی‌وقفه و ساده سازی آن‌ها، افزایش تعداد بانک‌های اطلاعاتی قابل تعریف در یک سرور و بسیاری از موارد دیگر که در طی چندین فصل به بررسی آن‌ها خواهیم پرداخت.

لینک دریافت: exchange2010.v1.rar

مطالب
شروع به کار با EF Core 1.0 - قسمت 15 - نوشتن آزمون‌های واحد
یکی از مشخصات آزمون‌های واحد، عدم خروج از مرزهای IO سیستم، در حین اجرای آن‌ها است و چون درهنگام کار با بانک‌های اطلاعاتی حتما از مرزهای IO سیستم رد خواهیم شد (کار با شبکه، کار با فایل سیستم، برای به روز رسانی و درج اطلاعات)، نوشتن آزمون‌های واحد واقعی، برای برنامه‌هایی که از ORMها استفاده می‌کنند مشکل است. به همین جهت مباحث mocking، تقلید قسمت‌های مختلف ORMها و جایگزین کردن آن‌ها با نمونه‌های درون حافظه‌ای بسیار مرسوم است. برای رفع این مشکلات، تیم EF Core، یک تامین کننده‌ی بانک اطلاعاتی ویژه‌ی «درون حافظه‌ای» را به نام «Entity Framework Core InMemory provider» ارائه داده‌است. به این ترتیب، این محل ذخیره سازی اطلاعات درون حافظه‌ای، مشکل رد شدن از مرزهای IO سیستم را برطرف کرده و عملا نیاز به کار کردن با فریم ورک‌های mocking را منتفی می‌کند (حداقل برای تقلید قسمت‌های مختلف EF Core).
در این قسمت ابتدا نحوه‌ی فعال سازی فریم ورک آزمون‌های واحد مایکروسافت و سپس نحوه‌ی فعال سازی این تامین کننده‌ی بانک اطلاعاتی درون حافظه‌ای را بررسی خواهیم کرد. به علاوه برای سرویس بلاگ‌های قسمت قبل نیز آزمون واحد خواهیم نوشت.


نحوه‌ی فعالسازی فریم ورک MSTest در یک پروژه‌ی Class library از نوع NET Core.


تنها نکته‌ی مهم فعالسازی MSTest در یک پروژه‌ی Class library جدید که برای نوشتن آزمون‌های واحد مورد استفاده قرار خواهیم داد، تنظیمات فایل project.json آن است که در ذیل آمده است:
{
    "version": "1.0.0-*",
 
    "testRunner": "mstest",
    "dependencies": {
        "Microsoft.NETCore.App": {
            "type": "platform",
            "version": "1.0.0"
        },
        "dotnet-test-mstest": "1.1.1-preview",
        "MSTest.TestFramework": "1.0.1-preview",
        "NETStandard.Library": "1.6.0",
        "Microsoft.EntityFrameworkCore": "1.0.0",
        "Microsoft.EntityFrameworkCore.InMemory": "1.0.0",
        "Core1RtmEmptyTest.DataLayer": "1.0.0-*",
        "Core1RtmEmptyTest.Entities": "1.0.0-*",
        "Core1RtmEmptyTest.Services": "1.0.0-*",
        "Core1RtmEmptyTest.ViewModels": "1.0.0-*"
    },
 
    "frameworks": {
        "netcoreapp1.0": {
            "imports": [
                "dnxcore50",
                "portable-net45+win8"
            ]
        }
    }
}
- در اینجا قید testRunner الزامی است؛ در غیراینصورت آزمون‌های واحد شما شناسایی نخواهند شد. همچنین بسته‌های dotnet-test-mstest و MSTest.TestFramework نیز باید اضافه شوند.
- به علاوه در اینجا ارجاعاتی را به اسمبلی‌های موجودیت‌ها، Services و DataLayer که در قسمت «شروع به کار با EF Core 1.0 - قسمت 14 - لایه بندی و تزریق وابستگی‌ها» بررسی شدند نیز ملاحظه می‌کنید.
- همچنین وابستگی جدید Microsoft.EntityFrameworkCore.InMemory نیز در اینجا قابل ملاحظه است. این وابستگی را تنها به پروژه‌ی آزمون‌های واحد خود اضافه می‌کنیم. از این جهت که تنظیمات آن صرفا در این قسمت جدید قید می‌شوند و نه در سایر قسمت‌های برنامه.

 پس از آن، کار با این فریم ورک، همانند سایر نگارش‌های دات نت خواهد بود:
using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace EFCore.MsTests
{
    [TestClass]
    public class CoreTests
    {
        [TestMethod]
        public void Test1()
        {
            Assert.IsTrue(true);
        }
    }
}
ابتدا کلاس مدنظر، با ویژگی TestClass مزین می‌شود. سپس متد آزمون واحد نوشته شده نیز باید به صورت public void و مزین شده‌ی با ویژگی TestMethod، ارائه شود.
پس از نوشتن اولین آزمون واحد، یکبار پروژه را build کرده و سپس از منوی Test، گزینه‌ی Windows را انتخاب کرده و در اینجا گزینه‌ی Test Explorer را انتخاب کنید. اندکی صبر کنید تا آزمون‌های واحد شما شناسایی شوند و سپس گزینه‌ی Run All را انتخاب کنید:



تغییرات Context برنامه جهت استفاده‌ی از تامین کننده‌ی داخل حافظه‌ای

در مورد نحوه‌ی تعریف و افزودن وابستگی‌های EF Core در مطلب «شروع به کار با EF Core 1.0 - قسمت 1 - برپایی تنظیمات اولیه» پیشتر بحث شد و همچنین در مطلب «شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر»، اطلاعات Context برنامه را به اسمبلی دیگری منتقل کردیم.
اگر از روش بازنویسی متد OnConfiguring برای تنظیم تامین کننده‌ی بانک اطلاعاتی مورد نظر استفاده می‌کنید، متد OnConfiguring کلاس Context برنامه چنین شکلی را پیدا می‌کند:
public class ApplicationDbContext : DbContext, IUnitOfWork
{
    private readonly IConfigurationRoot _configuration;
 
    public ApplicationDbContext(IConfigurationRoot configuration)
    {
        _configuration = configuration;
    }
 
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {
    } 
 
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer(
                _configuration["ConnectionStrings:ApplicationDbContextConnection"]
                , serverDbContextOptionsBuilder =>
                {
                    var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds;
                    serverDbContextOptionsBuilder.CommandTimeout(minutes);
                });
        }
    }
در اینجا دو تغییر جدید قابل ملاحظه هستند:
الف) اضافه شدن سازنده‌ی دومی که <DbContextOptions<ApplicationDbContext را دریافت می‌کند. از آن در سمت کدهای آزمون واحد برنامه جهت ثبت ()options.UseInMemoryDatabase استفاده می‌شود.
ب) به متد OnConfiguring، بررسی optionsBuilder.IsConfigured هم اضافه شده‌است. چون در سمت کدهای آزمون واحد، تامین کننده‌ی بانک اطلاعاتی درون حافظه‌ای اضافه می‌شود، مقدار optionsBuilder.IsConfigured به true تنظیم خواهد شد و دیگر از تامین کننده‌ی SQL Server استفاده نمی‌شود.

اگر از متد OnConfiguring به این شکل استفاده نمی‌کنید، تنها ذکر سازنده‌ی دوم ضروری است. از این جهت که در آزمون‌های واحد، از تنظیمات متد ConfigureServices کلاس آغازین برنامه استفاده نخواهد شد.


نوشتن آزمون‌های واحد مخصوص EF Core

پس از برپایی پیشنیازهای نوشتن آزمون‌ها واحد، شامل تنظیمات فریم ورک MSTest و همچنین افزودن وابستگی‌های مرتبط با فایل project.json ایی که در ابتدای بحث عنوان شد و اصلاح سازنده و متد OnConfiguring کلاس Context برنامه جهت آماده سازی آن‌ها برای پذیرش تامین کننده‌های دیگر، اکنون یک نمونه از آزمون‌های واحد درون حافظه‌ای EF Core، چنین شکلی را خواهد داشت:
using System;
using System.Linq;
using Core1RtmEmptyTest.DataLayer;
using Core1RtmEmptyTest.Entities;
using Core1RtmEmptyTest.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace Core1RtmEmptyTest.MsTests
{
    [TestClass]
    public class CoreTests
    {
        private readonly IServiceProvider _serviceProvider;
 
        public CoreTests()
        {
            var services = new ServiceCollection();
            services.AddEntityFrameworkInMemoryDatabase()
                        .AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase());
 
            services.AddScoped<IUnitOfWork, ApplicationDbContext>();
            services.AddScoped<IBlogService, BlogService>();
 
            _serviceProvider = services.BuildServiceProvider();
        }
 
        [TestMethod]
        public void Find_searches_url()
        {
            // Insert seed data into the database using one instance of the context
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>())
                {
                    context.Set<Blog>().Add(new Blog { Url = "http://sample.com/cats" });
                    context.Set<Blog>().Add(new Blog { Url = "http://sample.com/catfish" });
                    context.Set<Blog>().Add(new Blog { Url = "http://sample.com/dogs" });
                    context.SaveAllChanges();
                }
            }
 
            // Use a separate instance of the context to verify correct data was saved to database
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>())
                {
                    Assert.AreEqual(3, context.Set<Blog>().Count());
                    Assert.AreEqual("http://sample.com/cats", context.Set<Blog>().First().Url);
                }
            }
 
            // Use a clean instance of the context to run the test
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                var blogService = serviceScope.ServiceProvider.GetRequiredService<IBlogService>();
                var results = blogService.GetPagedBlogsAsNoTracking(pageNumber: 0, recordsPerPage: 10);
                Assert.AreEqual(3, results.Count);
            }
        }
    }
}
توضیحات:
همانطور که در قسمت «تغییرات Context برنامه جهت استفاده‌ی از تامین کننده‌ی داخل حافظه‌ای» فوق عنوان شد، در حین انجام آزمون‌های واحد، دیگر به کلاس آغازین برنامه و تنظیمات آن مراجعه نمی‌شود. بنابراین باید شبیه به عملکرد متد ConfigureServices آن‌را در اینجا پیاده سازی کرد. نمونه‌ای از انجام اینکار را در سازنده‌ی کلاس انجام آزمون‌های واحد مشاهده می‌کنید:
        private readonly IServiceProvider _serviceProvider;
 
        public CoreTests()
        {
            var services = new ServiceCollection();
            services.AddEntityFrameworkInMemoryDatabase()
                        .AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase());
 
            services.AddScoped<IUnitOfWork, ApplicationDbContext>();
            services.AddScoped<IBlogService, BlogService>();
 
            _serviceProvider = services.BuildServiceProvider();
        }
در اینجا است که توسط متد AddEntityFrameworkInMemoryDatabase، کار افزودن تامین کننده‌ی بانک اطلاعاتی درون حافظه‌ای انجام شده و سپس Context برنامه نیز از آن مطلع می‌شود (علت افزودن سازنده‌ی دومی که <DbContextOptions<ApplicationDbContext را دریافت می‌کند).
سپس همانند قبل، باید تمام سرویس‌های مدنظر تنظیم شوند تا بتوان از آن‌ها استفاده کرد.

نکته‌ی مهم دیگری را که باید به آن دقت داشت، ایجاد scope و سپس دسترسی به سرویس‌ها از طریق این Scope است. از این جهت که چون خارج از طول عمر یک درخواست وب قرار داریم، دیگر Scopeها برای ما به صورت خودکار ایجاد و تخریب نمی‌شوند و باید همان‌کاری را که ASP.NET Core در پشت صحنه انجام می‌دهد، به صورت دستی پیاده سازی کنیم:
            using (var serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>())
                {
اگر اینکار صورت نگیرد، چون Scope ایی ایجاد و تخریب نمی‌شود، کار کردن با متد serviceProvider.GetRequiredService_ اشتباه بوده و همیشه یک وهله از Context را باز می‌گرداند که مدنظر ما نیست. شبیه به این نکته را در قسمت «مقدار دهی اولیه‌ی جداول بانک‌های اطلاعاتی در EF Core» پیشتر ملاحظه کرده‌اید.


یک نکته‌ی تکمیلی

EF Core به همراه تامین کننده‌ی بانک اطلاعاتی SQLite نیز هست. یکی از نکات ویژه‌ی بانک اطلاعاتی SQLite، امکان تنظیم پارامتری است در رشته‌ی اتصالی آن، که آن‌را نیز تبدیل به یک «بانک اطلاعاتی درون حافظه‌ای» می‌کند. این روش سال‌ها است که جهت انجام آزمون‌های واحد ORMها مورد استفاده قرار می‌گیرد. بنابراین می‌توان آن‌را به عنوان جایگزینی برای مطلب جاری نیز درنظر گرفت.
 var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
services.AddEntityFrameworkSqlite().AddDbContext<CmsDbContext>(options => options.UseSqlite(connection));
اهمیت آن در اینجا است که تامین کننده‌ی بانک اطلاعاتی درون حافظه‌ای EF، قیود را اعمال نمی‌کند ؛ اما بانک اطلاعاتی درون حافظه‌ای SQLite واقعا همانند یک بانک اطلاعاتی رابطه‌ای کامل عمل می‌کند.
مطالب
HTML5 Offline Web Applications
وب به سمتی پیش رفته که کاربران زیادی از تلفن همراه ، تبلت‌ها و دیگر عامل ها(Agent) جهت مرور صفحات وب استفاده می‌کنند. در نتیجه تعداد کاربرانی که مدام در حال حرکت به مرور صفحات وب و استفاده از سرویس‌های برخط می‌پردازند رو به افزایش است. برنامه‌های خارج از شبکه‌ی HTML 5 یا به عبارتی HTML5 Offline Web Applications توسعه دهنگان را قادر می‌سازد تا نرم افزار‌های تحت وبی ارائه دهند که در حالت قطع بودن اینترنت و یا شبکه همچنان به سرویس دادن به کاربران ادامه دهد. دیگر اینگونه نیست که وب تنها در حالت برخط بودن معنی پیدا کند. یک نرم افزار مدیریت هزینه‌ی تحت وب را بررسی کنید که روی تلفن همراه شما اجرا شده ، در محلی که دسترسی به اینترنت نیست قصد استفاده از آن را دارید. چه قدر خوب می‌شود که این نرم افزار به گونه ای پیاده سازی شده باشد که بتواند در حالت برون خطی (Offline) به سرویس دهی ادامه دهد به طور مثال قادر به ذخیره‌ی داده‌های شما به صورت برون خط و همزمان سازی آنها پس از اتصال به اینترنت باشد.
برنامه‌های تحت وب برون خط با کمک قابلیتی به نام نهانگاه برنامه (Application Cache) کار می‌کنند. این قابلیت می‌تواند تمامی بخش‌های سایت را به شکل برون خط و خارج از شبکه، ذخیره کند. با به کار گیری این ویژگی می‌توان تمامی فایل‌های ایستا (JavaScript , HTML , CSS , Image) بر روی ابزار کاربر ذخیره نمود.

 نهانگاه برنامه چه تفاوتی با نهانگاه مرورگر (Browser cache) دارد ؟

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

برای استفاده از این ویژگی اولین باید فایلی به نام cache.manifest ایجاد کرد. این فایل باید با نوع محتوای (Mime type) مناسب برای کاربر ارسال شود. این فایل یک فایل متنی می‌باشد که لیست فایل‌های مورد نظر ما با قواعد خاصی در آن قرار می‌گیرد.
همیشه اولین خط این فایل عبارت CACHE MANIFEST قرار دارد ، پس از این خط عبارت CACHE: وارد می‌شود و فایل‌های مد نظر ما لیست می‌شود.
CACHE MANIFEST
 
#Cache Section
CACHE:
/Content/Images/icons-18-white.png
/Content/Images/icons-36-white.png
/Content/Images/ajax-loader.png
/Content/css
/Scripts/js
ذخیره‌ی برخی فایل‌ها روی سیستم کاربر ضرورتی ندارد ، برای مثال اسکریپتی که از یک وب سرویس برخط اطلاعات آب و هوا را دریافت می‌کند در حالت Offline کاربردی ندارد. برای مشخص کردن این فایل‌ها یک لیست سفید آماده می‌کنیم.
NETWORK
webService.Js
در بخش معرفی فایل‌های آفلاین بازید همه‌ی فایل‌های مد نظر ما معرفی شوند اما در لیست سفید با گذاشتن ستاره به مرورگر اعلام می‌کنیم که هر فایل و مسیری که در بخش قبلی (CACHE) نیامده را همواره از سرور درخواست کن. درج ستاره در بخش NETWORK ضروری است زیرا همه‌ی آدرس‌های سایت باید در یکی از بخش‌های cache.manifest قرار بگیرد ، اگر آدرسی در cache.manifest قرار نگیرد هیچگاه بارگذاری نخواهد شد.
در فایل cache.manifest می‌توان یادداشت هم اضافه کرد ، تمامی کاراکتر هایی که بعد از # قرار گیرند پردازش نمی‌شوند. یادداشت‌ها مفید هستند ، می‌تونید اطلاعات نسخه‌ی فعلی فایل cache.manifest را در آنها قرار دهید.
مثال :
CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
css
images/
stylesheet
.logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
login.php
/myapi
http://api.twitter.com
گفته شد فایل Cache.manifest باید با نوع محتوای مناسب ارسال شود ، برای تعیین نوع محتوا در وب سرور apache باید خط زیر به فایل .htaaccess اضافه شود :
AddType text/cache-manifest .appcache
در ASP.NET Web Forms می‌توان از یک Generic Handler برای ارسال فایل با نوع محتوای مناسب استفاده کرد : 
using System.Web;
 
namespace JavaScriptReference {
 
    public class Manifest : IHttpHandler {
 
        public void ProcessRequest(HttpContext context) {
            context.Response.ContentType = "text/cache-manifest";
            context.Response.WriteFile(context.Server.MapPath("Manifest.txt"));
        }
 
        public bool IsReusable {
            get {
                return false;
            }
        }
    }
}
یا می‌توان در یک فایل ASPX به صورتی دستی نوع محتوا را مشخص کرد : 
CACHE MANIFEST

# Version Jesus 3!

CACHE:

index.html
js/Custom.js
js/Utility.js
styles/index.css
styles/kendo.common.min.css
styles/BlueOpal/loading.gif
styles/BlueOpal/slider-h.gif
styles/BlueOpal/slider-v.gif

NETWORK:
*

<%@ Page Language="VB" ContentType="text/cache-manifest"  ResponseEncoding="utf-8" AutoEventWireup="true" CodeFile="manifest.aspx.vb" Inherits="Configuration_manifest" %>
در ASP.NET MVC علاوه بر اینکه می‌توان دستی نوع محتوای Response را مشخص کرد، می‌توان یک ActionResult منحصر به فرد ایجاد کرد. یک نمونه پیاده سازی شده را اینجا مشاهده کنید.
پس از انجام همه‌ی این پیش نیاز‌ها باید فایل cache manifest را به خصیصه‌ی manifest برچسب html ارجاع دهیم. 
<!DOCTYPE html>
<html manifest="/manifest.aspx">
اکنون قسمت‌های مد نظر سایت ما در حالت عدم دسترسی به شبکه نیز قابل استفاده می‌باشد. حتی این ویژگی در حالت برخط صفحات ما با سرعت بالاتری بارگذاری شوند.
خصیصه‌ی manifest تمامی فایل‌های HTML باید مقدار گیرد در غیر این صورت ممکن است صفحات درون نهانگاه قرار نگیرند.  
برای بررسی مرورگرهایی که این ویژگی را پشتیبانی می‌کنند این لینک  را مشاهده کنید.
نظرات مطالب
بررسی ORM های مناسب جهت استفاده در اندروید
بله این رو هم دیدم ولی موردی که هست این یک ORM برای sqlite نیست و در واقع این یه لایه برای برقراری ارتباط با دیتابیس درونی خودش هست.

در سایت رسمی خودش هم در صفحه اول نوشته:
Realm is not an ORM on top SQLite.
 Instead it uses its own persistence engine,
 built for simplicity (& speed). Users tell us 
they get started with Realm in minutes, 
port their apps in hours & save weeks on each app.

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