مطالب
بررسی خطاهای ممکن در حین راه اندازی اولیه برنامه‌های ASP.NET Core در IIS
نحوه‌ی نصب و راه اندازی برنامه‌های ASP.NET Core را در IIS، پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 22 - توزیع برنامه توسط IIS» بررسی کردیم. در این مطلب می‌خواهیم به تعدادی از خطاهای ممکن در حین راه اندازی اولیه‌ی این نوع برنامه‌ها بپردازیم.


خطای 500.19


این خطا زمانی رخ می‌دهد که ماژول هاستینگ ASP.NET Core، توسط IIS شناسایی نشده باشد. نصب مجدد آن این مشکل را برطرف می‌کند.
لیست تمام ماژول‌های هاستینگ را همواره در اینجا می‌توانید پیدا کنید.


خطای 502.5 و یا گاهی از اوقات 502


باید دقت داشته باشید که اگر تنظیم disableStartUpErrorPage در IIS فعال باشد (قابل افزودن به تگ aspNetCore تنظیمات وب کانفیگ ذیل)، صرفا خطای 502 را دریافت می‌کنید.

این خطا به معنای شکست در اجرای ماژول هاستینگ ASP.NET Core است و ممکن است به یکی از دلایل ذیل ایجاد شده باشد:
الف) در حین اجرای برنامه‌ی شما، استثنایی در کدهای فایل آغازین startup.cs برنامه، رخ داده‌است.
ب) پورت مورد استفاده‌ی برنامه، توسط پروسه‌ی دیگری در حال استفاده است.
ج) برنامه‌ی شما برای SDK با نگارش 1.1.2 تنظیم و کامپایل شده‌است؛ اما بر روی سرور حداکثر، SDK نگارش 1.1.1 نصب شده‌است.
د) ممکن است پروسه‌ی IIS قادر به یافتن و حتی اجرای dotnet.exe نباشد.

برای لاگ کردن مورد «الف»، باید لاگ کردن خطاهای برنامه را در web.config آن فعالسازی کنید:
<system.webServer>
   <handlers>
     <add name="aspNetCore" path="*" verb="*"
modules="AspNetCoreModule" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath="dotnet" arguments=".\MyApp.dll" stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="true" />
</system.webServer>
چند نکته:
- اگر این مورد به مسیر logs\stdout\. تنظیم شده‌است، باید پوشه‌ی logs را در ریشه‌ی پروژه به صورت دستی ایجاد کنید؛ و گرنه IIS آن‌را به صورت خودکار ایجاد نخواهد کرد.
- کاربر App Pool برنامه (با نام پیش‌فرض « IIS AppPool\DefaultAppPool») باید دسترسی نوشتن در این پوشه را داشته باشد؛ وگرنه فایل لاگی در آن ایجاد نخواهد شد.
- همچنین اگر با رعایت تمام این موارد، محتوای این فایل تولید شده باز هم خالی بود، یکبار IIS را ری‌استارت کنید. ممکن است IIS کار نوشتن در فایل لاگ را تمام نکرده باشد و با این کار مجبور به تکمیل و بستن فایل می‌شود.  

- برای حالت «ب» قبل از هر تغییری، یکبار کل سرور را ری‌استارت کنید.

- برای مورد «ج» نیز باید آخرین SDK هاستینگ را بر روی سرور نصب کنید.
لیست تمام SDKهای نصب شده‌ی بر روی سیستم را در مسیر «C:\Program Files\dotnet\sdk» می‌توانید مشاهده کنید. همچنین دستور «dotnet --list-sdks» نیز لیست SDKهای نصب شده را نمایش می‌دهد.

- برای رفع حالت «د»، نیاز است این موارد را بررسی کنید:
1- «Load User Profile» را به true تنظیم کنید.


برای اینکار به قسمت Application pools مراجعه کرده و تنظیمات پیشرفته‌ی App pool مورد استفاده را ویرایش کنید (تصویر فوق).
این تنظیم برای دائمی کردن کلیدهای رمزنگاری برنامه‌های ASP.NET Core نیز ضروری است و باید جزو چک لیست نصب برنامه‌های ASP.NET Core قرار گیرد.
2- مورد «د» حتی می‌تواند به علت عدم تعریف مسیر «C:\Program Files\dotnet\» در path ویندوز باشد. برای این منظور دستور env:path$ را در power shell اجرا کنید و بررسی کنید که آیا این مسیر در خروجی آن موجود است یا خیر؟ اگر نبود، پس از اضافه کردن آن به path ویندوز، باید یکبار IIS را هم ریست کنید تا این تنظیمات جدید را بخواند.
3- مورد «د» ممکن است به علت اشتباه تنظیم پوشه‌ی اصلی برنامه در IIS نیز باشد. یعنی dotnet.exe قادر به یافتن اسمبلی‌های برنامه نیست.
4- برای رفع مورد «د» دو دسترسی دیگر را نیز باید بررسی کنید:
الف) آیا کاربر Application pool برنامه به پوشه‌ی برنامه دسترسی read & execute را دارد یا خیر؟
ب) آیا کاربر Application pool برنامه به پوشه‌ی C:\Program Files\dotnet دسترسی read & execute را دارد یا خیر؟
اگر خیر، نحوه‌ی دسترسی دادن به آن‌ها به صورت زیر است:
Right click on the folder -> Properties -> Security tab -> Click at Edit button ->
Enter `IIS AppPool\DefaultAppPool` user (IIS AppPool\<app_pool_name>) -> Click at Check names -> OK ->
Then give it `read & execute` or other permissions.


خطای 502.3 و یا گاهی از اوقات 500


این خطا به صورت خلاصه به معنای «Bad Gateway: Forwarder Connection Error» است و زمانی رخ می‌دهد که پروسه‌ی dotnet.exe به درخواست رسیده شده یا پاسخی نداده‌است (مشاهده خطای 0x80072EE2  یا  ERROR_WINHTTP_TIMEOUT) و یا بیش از اندازه این پاسخ دهی طول کشیده‌است (این تنظیمات را در configuration editor می‌توانید مشاهده کنید که در حقیقت همان تگ aspNetCore در تنظیمات وب کانفیگ فوق است).


برای دیباگ بهتر این مورد نیاز است علاوه بر تنظیم web.config فوق، به فایل appsettings.json مراجعه کرده و سطح پیش فرض لاگ کردن اطلاعات را که warning است به information تغییر دهید:
"Console": {
     "LogLevel": {
        "Default": "Information"
     }
}
در این حالت درخواستی که پردازش نشده‌است نیز در لاگ‌ها حضور خواهد داشت و ممکن است این درخواست به علت عدم تنظیم CORS بدون پاسخ باقی مانده باشد.
و یا اگر پردازشی دارید که بیش از 2 دقیقه طول می‌کشد (مطابق تنظیمات تصویر فوق)، می‌توانید مقدار request time out را بیشتر کنید.


خطای 0x80004005 : 80008083

Application ‘<IIS path>’ with physical root ‘<Application path>’ failed to start 
process with commandline ‘”dotnet” .\MyApp.dll’, ErrorCode = ‘0x80004005 : 80008083.
خطای 0x80008083 به معنای تداخل نگارش‌ها است و خطای 0x80004005 به معنای مفقود بودن یک فایل یا عدم دسترسی به آن است.
این خطا زمانی رخ می‌دهد که برنامه‌ی خود را ارتقاء داده باشید، اما ماژول هاستینگ ASP.NET Core را بر روی سرور به روز رسانی نکرده باشید.


خطای 500.19

HTTP Error 500.19 - Internal Server Error
The requested page cannot be accessed because the related configuration data for the page is invalid.
اگر برنامه‌ی شما از امکانات URL Rewrite خود IIS استفاده می‌کند، عدم نصب بودن آن بر روی سرور، این خطا را سبب خواهد شد.
برای اینکار ابتدا IIS را متوقف کنید. سپس SDK جدید را نصب و پس از آن IIS را مجددا راه اندازی نمائید.


خطای 503

برنامه اجرا نشده و سطر ذیل در Event Viewer ویندوز قابل مشاهده است:
 The Module DLL C:\WINDOWS\system32\inetsrv\aspnetcore.dll failed to load.  The data is the error.
اگر اخیرا سیستم عامل را ارتقاء داده‌اید، ممکن است این خطا را دریافت کنید. راه حل آن نصب مجدد ماژول هاستینگ ASP.NET Core است تا نصب قبلی تعمیر شود.
مطالب
آماده سازی زیرساخت تهیه Integration Tests برای ServiceLayer

پیشنیاز

در این مطلب قصد داریم تست ServiceLayer را به جای تست درون حافظه‌ای که با ابزارهای Mocking در قالب Unit Testing انجام میگیرد، به کمک یک دیتابیس واقعی سبک وزن در قالب Integration Testing انجام دهیم.


قدم اول

یک پروژه تست را ایجاد کنید؛ بهتر است برای نظم دهی به ساختار Solution، پروژه‌های تست را در پوشه ای به نام Tests نگهداری کنید.



قدم دوم

بسته‌های نیوگت زیر را نصب کنید:

PM> install-package NUnit
PM> install-package Shouldly
PM> install-package EntityFramework
PM> install-package FakeHttpContext


قدم سوم

نسخه دیتابیس انتخابی برای تست خودکار، LocalDB می باشد. لازم است در ابتدای اجرای تست‌ها دیتابیس مربوط به Integration Test ایجاد شده و بعد از اتمام نیز دیتابیس مورد نظر حذف شود؛ برای این منظور از کلاس TestSetup استفاده خواهیم کرد.

[SetUpFixture]
public class TestSetup
{
    [OneTimeSetUp]
    public void SetUpDatabase()
    {
        DestroyDatabase();
        CreateDatabase();
    }

    [OneTimeTearDown]
    public void TearDownDatabase()
    {
        DestroyDatabase();
    }

   //...
}

با توجه به اینکه کلاس TestSetup با [SetUpFixture] تزئین شده است، Nunit قبل از اجرای تست‌ها سراغ این کلاس آمده و متد SetUpDatebase را به دلیل تزئین شدن با [OneTimeSetUp]، قبل از اجرای تست‌ها و متد TearDownDatabase را بدلیل تزئین شدن با [OneTimeTearDown]  بعد از اجرای تمام تست‌ها، اجرا خواهد کرد.


متد CreateDatabase

private static void CreateDatabase()
{
    ExecuteSqlCommand(Master, string.Format(SqlResource.DatabaseScript, FileName));

    //Use T-Sql Scripts For Create Database
    //ExecuteSqlCommand(MyAppTest, SqlResources.V1_0_0);

    var migration =
        new MigrateDatabaseToLatestVersion<ApplicationDbContext, DataLayer.Migrations.Configuration>();
    migration.InitializeDatabase(new ApplicationDbContext());

}

private static SqlConnectionStringBuilder Master =>
    new SqlConnectionStringBuilder
    {
        DataSource = @"(LocalDB)\MSSQLLocalDB",
        InitialCatalog = "master",
        IntegratedSecurity = true
    };

private static string FileName => Path.Combine(
    Path.GetDirectoryName(
        Assembly.GetExecutingAssembly().Location),
    "MyAppTest.mdf");

برای مدیریت محل ذخیره سازی فایل‌های دیتابیس، ابتدا دستورات ایجاد «دیتابیس تست» را برروی دیتابیس master اجرا می‌کنیم و در ادامه برای ساخت جداول از مکانیزم Migration خود EF استفاده شده است.

لازم است رشته اتصال به این دیتابیس ایجاد شده را در فایل App.config پروژه تست قرار دهید:

<connectionStrings>
  <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDB)\MSSQLLocalDb;Initial Catalog=MyAppTest;Integrated Security=True;" />
</connectionStrings>


متد DestroyDatabase 

private static void DestroyDatabase()
{
    var fileNames = ExecuteSqlQuery(Master, SqlResource.SelecDatabaseFileNames,
        row => (string)row["physical_name"]);

    if (!fileNames.Any()) return;

    ExecuteSqlCommand(Master, SqlResource.DetachDatabase);

    fileNames.ForEach(File.Delete);
}

در این متد ابتدا آدرس فایل‌های مرتبط با «دیتابیس تست» واکشی شده و در ادامه دستورات Detach دیتابیس انجام شده و فایل‌های مرتبط حذف خواهند شد. فایل‌های دیتابیس در مسیری شبیه به آدرس نشان داده شده‌ی در شکل زیر ذخیره خواهند شد.


قدم چهارم

برای جلوگیری از تداخل بین تست‌ها لازم است تک تک تست‌ها از هم ایزوله باشند؛ یکی از راه حل‌های موجود، استفاده از تراکنش‌ها می‌باشد. برای این منظور امکان AutoRollback را به صورت خودکار به متدهای تست با استفاده از Attribute زیر اعمال خواهیم کرد:

public class AutoRollbackAttribute : Attribute, ITestAction
{
    private TransactionScope _scope;

    public void BeforeTest(ITest test)
    {
        _scope = new TransactionScope(TransactionScopeOption.RequiresNew,new TransactionOptions {IsolationLevel = IsolationLevel.Snapshot});
    }

    public void AfterTest(ITest test)
    {
        _scope?.Dispose();
        _scope = null;
    }

    public ActionTargets Targets => ActionTargets.Test;
}

متدهای BeforTest و AfterTest به ترتیب قبل و بعد از اجرای متدهای تست تزئین شده با این Attribute اجرا خواهند شد. 


در مواقعی هم که به HttpConext نیاز دارید، می‌توانید از کتابخانه FakeHttpContext بهره ببرید. برای این مورد هم میتوان Attributeای را به مانند AutoRollback در نظر گرفت.

public class HttpContextAttribute:Attribute,ITestAction
{
    private FakeHttpContext.FakeHttpContext _httpContext;

    public void BeforeTest(ITest test)
    {
        _httpContext = new FakeHttpContext.FakeHttpContext();

    }

    public void AfterTest(ITest test)
    {
        _httpContext?.Dispose();
        _httpContext = null;
    }

    public ActionTargets Targets => ActionTargets.Test;
}

کاری که FakeHttpContext انجام می‌دهد، مقدار دهی HttpContext.Current با یک پیاده سازی ساختگی می‌باشد.


قدم پنجم

به عنوان مثال اگر بخواهیم برای سرویس «گروه کاربری»، Integration Test بنویسیم، به شکل زیر عمل خواهیم کرد:

namespace MyApp.IntegrationTests.ServiceLayer
{
    [TestFixture]
    [AutoRollback]
    [HttpContext]
    public class RoleServiceTests
    {
        private IRoleApplicationService _roleService;

        [SetUp]
        public void Init()
        {
        }

        [TearDown]
        public void Clean()
        {
        }

        [OneTimeSetUp]
        public void SetUp()
        {
            _roleService = IoC.Resolve<IRoleApplicationService>();

            using (var uow = IoC.Resolve<IUnitOfWork>())
            {
                RoleInitialDataBuilder.Build(uow);
            }
        }

        [OneTimeTearDown]
        public void TearDown()
        {
        }

        [Test]
        [TestCase("Role1")]
        public void Should_Create_New_Role(string role)
        {
            var viewModel = new RoleCreateViewModel
            {
                Name = role
            };

            _roleService.Create(viewModel);

            using (var context = IoC.Resolve<IUnitOfWork>())
            {
                var user = context.Set<Role>().FirstOrDefault(a => a.Name == role);
                user.ShouldNotBeNull();
            }
        }

        [Test]
        public void Should_Not_Create_New_Role_With_Admin_Name()
        {
            var viewModel = new RoleCreateViewModel
            {
                Name = "Admin"
            };

            Assert.Throws<DbUpdateException>(() => _roleService.Create(viewModel));
        }

        [Test]
        public void Should_AdminRole_Exists()
        {
            using (var context = IoC.Resolve<IUnitOfWork>())
            {
                var user = context.Set<Role>().FirstOrDefault(a => a.Name == "Admin");
                user.ShouldNotBeNull();
            }
        }

        [Test]
        public void Should_Not_Create_New_Role_Without_Name()
        {
            Assert.Throws<ValidationException>(() => _roleService.Create(new RoleCreateViewModel { Name = null }));
        }
    }
}

با این خروجی:



کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید. 
مطالب
نمایش HTML در برنامه‌های Angular
فرض کنید قصد داریم خاصیت htmlContent زیر را در قالب این کامپوننت نمایش دهیم:
export class ShowHtmlComponent {
  htmlContent = "Template <script>alert(\"Hello!\")</script> <b>Syntax</b>";
}
اگر از روش متداول binding استفاده شود:
<h3>Binding innerHTML</h3>
<p>Bound value:</p>
<p>{{htmlContent}}</p>
چنین خروجی حاصل خواهد شد:


همچنین اگر به کنسول developer tools مرورگر مراجعه کنیم، چنین اخطاری نیز درج شده است:
 WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
به این معنا که Angular به صورت توکار تمام خروجی‌ها را به صورت encode شده نمایش می‌دهد و در مقابل حملات XSS مقاوم است. Sanitizing نیز در اینجا به معنای تغییر ورودی و تبدیل آن به مقداری است که جهت درج در DOM امن است.


روش نمایش HTML در برنامه‌های Angular

اما اگر خواستیم اطلاعات HTML ایی را به همان صورتی که هستند نمایش دهیم چطور؟ در این حالت باید از روش ویژه‌ی ذیل استفاده کرد:
<p>Result of binding to innerHTML:</p>
<p [innerHTML]="htmlContent"></p>
برای نمایش HTML نیاز است آن‌را به ویژگی innerHTML متصل کرد؛ با این خروجی:


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

روش دیگر کار با innerHTML، تعریف یک template reference variable در قالب کامپوننت است:
<p #dataContainer></p>
و سپس دسترسی به آن از طریق یک ViewChild و انتساب مقداری بهinnerHTML  آن به صورت ذیل:
export class ShowHtmlComponent implements OnInit {

  @ViewChild("dataContainer") dataContainer: ElementRef;

  ngOnInit() {
    this.dataContainer.nativeElement.innerHTML = "nativeElement <script>alert(\"Hello!\")</script> <b>Syntax</b>";
  }
}
با این خروجی:


که اینبار قسمت script آن به طور کامل حذف شده‌است.


حالات مختلفی که Angular برنامه را از حملات XSS محافظت می‌کند

در ذیل، لیست مواردی را مشاهده می‌کنید که به صورت پیش‌فرض توسط Angular در مقابل حملات XSS محافظت می‌شوند و اطلاعات انتساب داده شده‌ی به آن‌ها تمیزسازی خواهند شد:
HTML 
Attributes – 
<div [innerHTML]="UNTRUSTED"></div> 
OR <input value="UNTRUSTED">

Style— 
<div [style]="height:UNTRUSTED"></div>

URL — 
<a [href]="UNTRUSTED-URL"></a> 
OR <script [src]="UNTRUSTED-URL"></script> 
OR <iframe src="UNTRUSTED-URL" />

GET Parameter – 
<a href="/user?id=UNTRUSTED">link</a>

JavaScript Variable –
<script> var value='UNTRUSTED';</script>


تبدیل کردن یک HTML نا امن ورودی به یک HTML امن در Angular

بهتر است اطلاعات دریافتی از کاربران پیش از ارسال به سرور تمیز شوند. برای این منظور می‌توان از سرویس ویژه‌ای به نام DomSanitizer کمک گرفت. کار این سرویس، امن سازی اطلاعات نمایش داده شده‌ی در برنامه‌های Angular است.
export class ShowHtmlComponent implements OnInit {
  sanitizedHtml: string;

  constructor(private sanitizer: DomSanitizer) { }

  ngOnInit() {
    this.sanitizedHtml = this.sanitizer.sanitize(SecurityContext.HTML, "<b>Sanitize</b><script>attackerCode()</script>");
  }
}
در این حالت سرویس DomSanitizer به سازنده‌ی کلاس تزریق شده و سپس می‌توان از متدهای مختلف آن مانند sanitize استفاده کرد. خروجی آن صرفا حذف تگ اسکریپت و نگهداری کدهای درون آن است.


در این حالت می‌توان موارد ذیل را کنترل کرد. برای مثال اگر مقدار دریافتی CSS است، می‌توان از SecurityContext.STYLE استفاده کرد و سایر حالات آن مانند امن سازی HTML، اسکریپت و آدرس‌های اینترنتی به شرح ذیل هستند:
SecurityContext.NONE
SecurityContext.HTML
SecurityContext.STYLE
SecurityContext.SCRIPT
SecurityContext.URL
SecurityContext.RESOURCE_URL


غیرفعال کردن سیستم امنیتی Angular جهت نمایش کامل یک مقدار HTML ایی

اگر خواستیم اطلاعات HTML ایی را با فرض امن بودن آن، به همان نحوی که هست نمایش دهیم چطور؟
سرویس DomSanitizer شامل متدهای ذیل نیز می‌باشد:
export enum SecurityContext { NONE, HTML, STYLE, SCRIPT, URL, RESOURCE_URL }

export abstract class DomSanitizer implements Sanitizer {
  abstract sanitize(context: SecurityContext, value: SafeValue|string|null): string|null;
  abstract bypassSecurityTrustHtml(value: string): SafeHtml;
  abstract bypassSecurityTrustStyle(value: string): SafeStyle;
  abstract bypassSecurityTrustScript(value: string): SafeScript;
  abstract bypassSecurityTrustUrl(value: string): SafeUrl;
  abstract bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl;
}
اولین متد آن sanitize است که در مورد آن توضیح داده شد. سایر متدها، کار غیرفعال سازی سیستم امنیتی توکار Angular را انجام می‌دهند.
برای کار با آن‌ها همانند مثال استفاده‌ی از متد sanitize می‌توان سرویس DomSanitizer را به سازنده‌ی یک کامپوننت تزریق کرد و یا می‌توان این عملیات تکراری فرمت اطلاعات ورودی را تبدیل به یک Pipe جدید کرد:
import { Pipe, PipeTransform } from "@angular/core";
import { DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl } from "@angular/platform-browser";

@Pipe({
  name: "safe"
})
export class SafePipe implements PipeTransform {
  constructor(protected sanitizer: DomSanitizer) { }

  public transform(value: any, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
    switch (type) {
      case "html":
        return this.sanitizer.bypassSecurityTrustHtml(value);
      case "style":
        return this.sanitizer.bypassSecurityTrustStyle(value);
      case "script":
        return this.sanitizer.bypassSecurityTrustScript(value);
      case "url":
        return this.sanitizer.bypassSecurityTrustUrl(value);
      case "resourceUrl":
        return this.sanitizer.bypassSecurityTrustResourceUrl(value);
      default:
        throw new Error(`Invalid safe type specified: ${type}`);
    }
  }
}
کار این Pipe غیرفعال کردن سیستم امنیتی Angular و نمایش html، style و غیره به همان صورتی که هستند، می‌باشد.
برای استفاده‌ی از آن، ابتدا این Pipe به قسمت declarations ماژول مدنظر اضافه خواهد شد:
@NgModule({
  imports: [
  // ...
  ],
  declarations: [ SafePipe]
})
و سپس در قالب کامپوننت به نحو ذیل می‌توان با آن کار کرد:
<p [innerHTML]="htmlContent | safe: 'html'"></p>
در این حالت متد bypassSecurityTrustHtml بر روی htmlContent، فراخوانی شده و نتیجه‌ی نهایی نمایش داده خواهد شد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
مطالب
ایجاد یک DbContext مشترک بین entityهای پروژه‌های متفاوت
فر ض کنید پروژه بزرگی دارید که هر قسمت را به یک برنامه نویس می‌سپارید تا آن قسمت را در پروژه مجزایی طراحی و برنامه نویسی کند. هر برنامه نویس Entity‌های خاص خود را در لایه‌های مربوط به پروژه خود تعریف می‌کند و از آنها استفاده می‌کند. حال یکی از برنامه نویس‌ها می‌خواهد از Entity های پروژه دیگر استفاده کند. در این صورت اگر از دو Context شیء‌ایی را بسازد و آنها را با یکدیگر Join  بزند، خطایی مربوط به تعلق داشتن دو  Entity به دو Context متفاوت را می‌گیرد.

در پروژه‌های کوچک، کل تیم بر روی ماژول‌های مختلف یک پروژه کار می‌کنند و یک DbContext مشترک دارند. اما راه حل این مشکل در پروژه‌های بزرگ چیست؟ 
یکی از راه‌های پیشنهادی، استفاده از یک کلاس DbContextBase است که همه پروژه‌ها بایستی Context خود را از این کلاس به ارث ببرند که در این صورت باز هم مشکل ساخت چند DbContext وجود خواهد داشت که فقط می‌توان از Entity‌های موجود در DbContextBase و DbContext پروژه جاری استفاده کرد. اما در شرکت‌های بزرگ که پروژه‌هایی مانندERP دارند، روش دیگری استفاده می‌شود که در ادامه خواهیم دید.
روش مورد استفاده به این صورت است که در زمان اجرا یک DbContext برای همه Entity‌های پروژه‌های مختلف ساخته می‌شود. اجازه بدهید همراه با مثال، این پروژه را پیش برویم. فرض کنید دو تیم برنامه نویسی داریم که هر کدام بر روی پروژه‌های مجزای SampleProject1 و SampleProject2 کار میکنند که Entity‌های هر کدام در لایه‌های Common قرار گرفته‌اند.

در SampleProject1 مدل Product را داریم:

public partial class Product : Entity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Nullable<byte> ProductTypeId { get; set; }
    }
و در SampleProject2، مدل ProductType را داریم که هر دو Entity از کلاس Entity ارث بری می‌کند: 
 public partial class ProductType : Entity
    {
        public byte Id { get; set; }
        public string Name { get; set; }
    }
همه پروژه‌ها را در پروژه‌ی SampleProject1.Console، به عنوان رفرنس اضافه می‌کنیم؛ بجز SampleProject2.Console و Output path همه پروژه‌ها را به یک پوشه مشترک هدایت می‌کنیم. در ادامه برای بدست آوردن Entity‌ها از کد زیر استفاده می‌کنیم:
            List<Assembly> allAssemblies = new List<Assembly>();
            string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            foreach (string dll in Directory.GetFiles(path, "*.Common.dll"))
                allAssemblies.Add(Assembly.LoadFile(dll));

            var type = typeof(Entity);
      
            List<Type> types = allAssemblies
             .SelectMany(s => s.GetTypes())
             .Where(p => type.IsAssignableFrom(p)).ToList();

            List<string> entities = new List<string>();
            foreach (var item in types)
            {
                entities.Add(item.Name);
            }

            types.Add(typeof(Entity));
و سپس برای Generate کردن کلاس DbContext از کلاس زیر استفاده می‌کنیم:
public class ContextGenerator
    {
        public void Generate(List<string> entities, params Type[] types)
        {
            StringBuilder code = new StringBuilder();

            code.AppendLine(@"
           using System.Data.Entity;
           using System.Data.Entity.Core.EntityClient;
           using SampleProject1.Common.Models;
           using SampleProject1.Common.Models.Mapping;
           using SampleProject2.Common.Models;
           using SampleProject2.Common.Models.Mapping;

           namespace DbContextGenerator
           {
                public partial class TestContext : DbContext
                {
                    static TestContext()
                    {
                        Database.SetInitializer<TestContext>(null);
                    }

                    public TestContext()
                        : base(""Data Source=.;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True"")
                    {
                        }
                ");

            var pluralizeHelper = new PluralizeHelper();

            foreach (var entity in entities)
            {
                code.AppendLine($@"public DbSet<{entity}> {pluralizeHelper.Pluralize(entity)} {{ get; set; }}");
            }

            code.AppendLine(@"protected override void OnModelCreating(DbModelBuilder modelBuilder)");
            code.AppendLine(@"{");

            foreach (var entity in entities)
            {
                code.AppendLine($@"modelBuilder.Configurations.Add(new {entity}Map());");
            }
            code.AppendLine(@"}");
            code.AppendLine(@"}");
            code.AppendLine(@"}");
           
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters();

            parameters.ReferencedAssemblies.Add("System.Drawing.dll");
            parameters.ReferencedAssemblies.Add("System.Data.dll");
            parameters.ReferencedAssemblies.Add("System.Data.Entity.dll");
            parameters.ReferencedAssemblies.Add("System.ComponentModel.dll");

            foreach (var type in types)
            {
                parameters.ReferencedAssemblies.Add(type.Assembly.Location);
            }

            parameters.ReferencedAssemblies.Add(typeof(DbSet).Assembly.Location);
            parameters.ReferencedAssemblies.Add(typeof(DbContext).Assembly.Location);
            parameters.ReferencedAssemblies.Add(typeof(IQueryable).Assembly.Location);
            parameters.ReferencedAssemblies.Add(typeof(IQueryable<>).Assembly.Location);
            parameters.ReferencedAssemblies.Add(typeof(System.ComponentModel.IListSource).Assembly.Location);

            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = false;
            parameters.OutputAssembly = "ProjectContext.dll";

            CompilerResults results = provider.CompileAssemblyFromSource(parameters, code.ToString());

            if (results.Errors.HasErrors)
            {
                StringBuilder sb = new StringBuilder();

                foreach (CompilerError error in results.Errors)
                {
                    sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
                }

                throw new InvalidOperationException(sb.ToString());
            }
        }

    }
و نحوه فراخوانی آن:
 new ContextGenerator().Generate(entities, types.ToArray()); // generate dbContext
همانطور که مشاهده می‌کنید، برای تولید کد، از کلاس CSharpCodeProvider استفاده میکنیم که نتیجه اجرای کد بالا، ساخت DLLی به نام ProjectContext.dll است. با مشاهده DLL ساخته شده توسط نرم افزار ILSpy، کد جنریت شده به صورت زیر خواهد بود: 

حال برای استفاده از Context تولید شده، به صورت زیر شیءایی را ساخته:

 static DbContext _dbContext=null;
        public static DbContext GetDbContextInstance()
        {
            if (_dbContext == null)
            {
                string path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
                var dllversionAssm = Assembly.LoadFile(path + "\\ProjectContext.dll");
                Type type = dllversionAssm.GetType("DbContextGenerator.TestContext");
                _dbContext = (DbContext)Activator.CreateInstance(type);
            }
            return _dbContext;
        }

و سپس برای ساخت DbSet از هر Entity به کد زیر نیاز خواهیم داشت:

public static System.Data.Entity.DbSet<T> Get<T>() where T : class
        {
            var set = GetDbContextInstance().Set<T>();
            return set;
        }

هم اکنون می‌توان رکوردهای Entity‌ها را واکشی کرده و یا آن‌ها را با یکدیگر Join بزنیم:

            var products = Get<Product>().ToList();

            var productTypes = Get<ProductType>().ToList();


            var query = from p in Get<Product>()
                        join pt in Get<ProductType>() on p.ProductTypeId equals pt.Id
                        select new
                        {
                            Id = p.Id,
                            Name = p.Name,
                            ProductType = pt.Name

                        };

            var JoinResult = query.ToList();

و نتیجه واکشی ها 


کد کامل این پروژه  

مطالب
بررسی دقیق عملکرد AutoMapper
همانطور که اطلاع دارید، AutoMapper ابزاری برای نگاشت خودکار بین Model و Dto می‌باشد؛ که به صورت نادرست تصور کاهش سرعت در استفاده کردن از آن، بین توسعه دهندگان جا افتاده‌است. در این مقاله قصد داریم به صورت دقیق، به بررسی سرعت عملکرد استفاده از AutoMapper و مقایسه آن با نگاشت دستی بپردازیم.
کد‌های کامل این قسمت را میتوانید از اینجا clone کرده و شخصا تست نمایید.

ابتدا یک پروژه‌ی Console Application را ساخته و AutoMapper را به همراه Ef6، نصب مینماییم. سپس دو کلاس جدید را به نام‌های User و Address به صورت زیر در پوشه‌ی Models مینویسیم.
using System.Collections.Generic;

namespace AutoMapperComparison.Models
{
    public class User
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public ICollection<Address> Addresses { get; set; }
    }
}
using System.ComponentModel.DataAnnotations.Schema;

namespace AutoMapperComparison.Models
{
    public class Address
    {
        public int Id { get; set; }

        public double? Code { get; set; }

        public string Title { get; set; }

        public int UserId { get; set; }

        [ForeignKey(nameof(UserId))]
        public virtual User User { get; set; }
    }
}
بدیهی است که این دو مدل با همدیگر رابطه‌ی 1 به چند دارند. حال کافیست AppDbContext خود را به صورت زیر تعریف نماییم.
نکته: در متد Seed، برای ثبت رکورد‌های اولیه، از BulkInsert استفاده شده است (باید پکیج BulkInsert را نیز نصب نمایید)
using EntityFramework.BulkInsert.Extensions;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;

namespace AutoMapperComparison.Models
{
    public class AppDbContextInitializer : DropCreateDatabaseAlways<AppDbContext>
    {
        protected override void Seed(AppDbContext context)
        {
            User user = context.Users.Add(new User { Name = "Test" });

            context.SaveChanges();

            List<Address> addresses = new List<Address>();

            for (int i = 0; i < 500000; i++)
            {
                addresses.Add(new Address { Id = i, Code = 1, Title = "Test", UserId = user.Id });
            }

            context.BulkInsert(addresses);

            base.Seed(context);
        }
    }

    public class AppDbContext : DbContext
    {
        static AppDbContext()
        {
            Database.SetInitializer(new AppDbContextInitializer());

            //Database.SetInitializer<AppDbContext>(null);
        }

        public AppDbContext()
            : base(new SqlConnection(@"Data Source=.;Initial Catalog=AppDbContext;Integrated Security=True"), contextOwnsConnection: true)
        {
            Configuration.AutoDetectChangesEnabled = false;
            Configuration.EnsureTransactionsForFunctionsAndCommands = false;
            Configuration.LazyLoadingEnabled = false;
            Configuration.ProxyCreationEnabled = false;
            Configuration.ValidateOnSaveEnabled = false;
            Configuration.UseDatabaseNullSemantics = false;
        }

        public DbSet<User> Users { get; set; }

        public DbSet<Address> Addresses { get; set; }
    }
}
 برای اینکه مقایسه انجام شده دقیق باشد، تمامی Configuration‌های اضافی را نیز غیر فعال نموده‌ام.
فقط نیاز داریم یک Dto را برای Address نیز تعریف کنیم؛ چون قرار است نگاشت از Model به Dto از روی Address و AddressDto انجام شود.
کلاس AddressDto را به صورت زیر ایجاد میکنیم:
namespace AutoMapperComparison.Models
{
    public class AddressDto
    {
        public int Id { get; set; }

        public double? Code { get; set; }

        public string Title { get; set; }

        public int UserId { get; set; }

        public string UserName { get; set; }
    }
}
قرار است به صورت خودکار از طریق AutoMapper و همچنین به صورت دستی، نگاشت از Model به Dto مربوطه انجام شود.
 حال نیاز است فایل Program.cs را باز کرده و تغییرات زیر را اعمل نماییم:
using AutoMapper;
using AutoMapper.QueryableExtensions;
using AutoMapperComparison.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace AutoMapperComparison
{
    public class Program
    {
        public static void Main()
        {
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Address, AddressDto>();
            });

            Console.WriteLine($"Create Db {DateTimeOffset.UtcNow}");
            using (AppDbContext db = new AppDbContext())
            {
                db.Database.Initialize(force: true);
                db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS"); //Removes all clean buffers from the buffer pool, and columnstore objects from the columnstore object pool
                Console.WriteLine(db.Addresses.ProjectTo<AddressDto>());
                Console.WriteLine(db.Addresses.Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }));
            }

            Console.WriteLine($"Normal Select {DateTimeOffset.UtcNow}");
            using (AppDbContext db = new AppDbContext())
            {
                db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS");
                Stopwatch watch = Stopwatch.StartNew();
                List<AddressDto> addresses = db.Addresses.AsNoTracking().Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }).ToList();
                List<AddressDto> addresses2 = db.Addresses.AsNoTracking().Select(add => new AddressDto { Id = add.Id, Code = add.Code, Title = add.Title, UserId = add.UserId, UserName = add.User.Name }).ToList();
                watch.Stop();
                Console.WriteLine($"{watch.ElapsedMilliseconds} {addresses.Count} {addresses2.Count}");
            }

            Console.WriteLine($"AutoMapper Exec {DateTimeOffset.UtcNow}");
            using (AppDbContext db = new AppDbContext())
            {
                db.Database.ExecuteSqlCommand("DBCC DROPCLEANBUFFERS");
                Stopwatch watch = Stopwatch.StartNew();
                List<AddressDto> addresses = db.Addresses.AsNoTracking().ProjectTo<AddressDto>().ToList();
                List<AddressDto> addresses2 = db.Addresses.AsNoTracking().ProjectTo<AddressDto>().ToList();
                watch.Stop();
                Console.WriteLine($"{watch.ElapsedMilliseconds} {addresses.Count} {addresses2.Count}");
            }

            Console.ReadKey();
        }
    }
}
نکته: از دستور DBCC DROPCLEANBUFFERS جهت خالی کردن بافر sql server برای رسیدن به نتیجه‌ی هرچه دقیق‌تر استفاده شده است.
بعد از اجرا کردن، ابتدا بررسی میکنیم که کوئری اجرا شده‌ی دو نوع مختلف، هیچ تفاوتی با هم نداشته باشند.


حال نتایج بدست آمده، در قسمت پایین‌تر آن نمایان میشود:


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

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

Normal   AutoMapper
 2451  2378
 2120  2111
 2202  2124

اگر این مقدار جزئی از تفاوت بین دو نوع مختلف آزمایش را مورد نظر نگیریم، میتوان گفت که هر دو روش نتیجه‌ی کاملا یکسانی خواهند داشت. فقط با استفاده از AutoMapper کد‌های کمتری نوشته شده‌است!

اما دلیل چیست؟ از آنجایی که ProjectTo از Dto به Model انجام شده و Lambda Expressionی که به سمت Entity Framework فرستاده شده‌است با روش Normal کاملا برابر است و بقیه‌ی عملیات توسط EF انجام میشود، با قاطعیت میتوان گفت که هر دو روش ذکر شده از نظر Performance کاملا یکسان خواهند بود.

نکته: البته به این موضوع باید توجه شود که اگر همین آزمایش را بطور مثال با استفاده از یک Listی از رکورد‌های درون Memory ساخته شده توسط خودمان انجام دهیم، آن موقع نتیجه‌ی یکسانی نخواهیم داشت، به دلیل اینکه EFی دیگر وجود نخواهد داشت که مسئولیت بازگشت داده‌ها را بر عهده بگیرد. از آنجائیکه اکثر کارهایی که توقع داریم AutoMapper برای ما انجام دهد، توسط ORM بازگشت داده میشود، پس میتوان گفت نکته‌ی فوق تقریبا در دنیای واقعی رخ نخواهد داد و باعث مشکل نخواهد شد.

مطالب
شروع به کار با EF Core 1.0 - قسمت 11 - بررسی رابطه‌ی Self Referencing
پیشنیازها
- بررسی نحوه تعریف نگاشت جداول خود ارجاع دهنده (Self Referencing Entity)
- مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
- آشنایی با SQL Server Common Table Expressions - CTE
- بدست آوردن برگهای یک درخت توسط Recursive CTE


در پیشنیازهای بحث، روش تعریف روابط خود ارجاع دهنده و یا Self Referencing را تا EF 6.x می‌توانید مطالعه کنید. در این قسمت قصد داریم معادل این روش‌ها را در EF Core بررسی کنیم.


روش تعریف روابط خود ارجاع دهنده توسط Fluent API در EF Core

اگر همان مدل کامنت‌های چندسطحی یک بلاگ را درنظر بگیریم:
    public class BlogComment
    {
        public int Id { get; set; }

        public string Body { get; set; }

        public DateTime Date1 { get; set; }


        public virtual BlogComment Reply { get; set; }
        public int? ReplyId { get; set; }

        public virtual ICollection<BlogComment> Children { get; set; }
    }
اینبار تنظیمات Fluent API معادل EF Core آن به صورت ذیل خواهد بود:
    public class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BlogComment>(entity =>
            {
                entity.HasIndex(e => e.ReplyId);

                entity.HasOne(d => d.Reply)
                    .WithMany(p => p.Children)
                    .HasForeignKey(d => d.ReplyId);
            });
        }

        public virtual DbSet<BlogComment> BlogComments { get; set; }
    }
هدف از مدل‌های خود ارجاع دهنده، مدلسازی اطلاعات چند سطحی است؛ مانند منوهای چندسطحی یک سایت، کامنت‌های چند سطحی یک مطلب، سلسله مراتب کارمندان یک شرکت و امثال آن.
نکته‌ها‌ی مهم مدلسازی این نوع روابط، موارد ذیل هستند:
الف) وجود خاصیتی از جنس کلاس اصلی در همان کلاس
   public virtual BlogComment Reply { get; set; }
ب) که در حقیقت مشخص می‌کند، والد رکورد جاری کدام رکورد قبلی است:
   public int? ReplyId { get; set; }
برای اینکه چنین رابطه‌ای تشکیل شود، باید این فیلد، مقدارش را از کلید اصلی جدول تامین کند (تشکیل یک کلید خارجی که به کلید اصلی جدول اشاره می‌کند).
علت نال پذیر بودن این خاصیت، نیاز به ثبت ریشه‌ای است که خودش والد است و فرزند رکوردی پیشین، نیست.


ج) همچنین برای سهولت دریافت فرزندان یک ریشه، مجموعه‌ای از جنس همان کلاس نیز تشکیل می‌شود.
 public virtual ICollection<BlogComment> Children { get; set; }


روش تعریف روابط خود ارجاع دهنده توسط Data Annotations در EF Core

در ادامه معادل تنظیمات فوق را توسط ویژگی‌ها ملاحظه می‌کنید که در اینجا توسط ویژگی‌های InverseProperty و ForeignKey، کار تشکیل روابط صورت گرفته‌است:
    public class BlogComment
    {
        public int Id { get; set; }
        public string Body { get; set; }
        public DateTime Date1 { get; set; }

        [ForeignKey("ReplyId")]
        [InverseProperty("Children")]
        public virtual BlogComment Reply { get; set; }
        public int? ReplyId { get; set; }

        [InverseProperty("Reply")]
        public virtual ICollection<BlogComment> Children { get; set; }
    }
البته قسمت تشکیل ایندکس بر روی ReplyId را فقط توسط Fluent API می‌توان انجام داد:
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BlogComment>(entity =>
            {
                entity.HasIndex(e => e.ReplyId);
            });
        }


ثبت اطلاعات و کوئری گرفتن از روابط خود ارجاع دهنده در EF Core

در اینجا نحوه‌ی ثبت دو سری نظر و زیر نظر را مشاهده می‌کنید:
var comment1 = new BlogComment { Body = "نظر من این است که" };
var comment12 = new BlogComment { Body = "پاسخی به نظر اول", Reply = comment1 };
var comment121 = new BlogComment { Body = "پاسخی به پاسخ به نظر اول", Reply = comment12 };

context.BlogComments.Add(comment121);

var comment2 = new BlogComment { Body = "نظر من این بود که" };
var comment22 = new BlogComment { Body = "پاسخی به نظر قبلی", Reply = comment2 };
var comment221 = new BlogComment { Body = "پاسخی به پاسخ به نظر من اول", Reply = comment22 };
context.BlogComments.Add(comment221);

context.SaveChanges();


نظرات اصلی، با ReplyId مساوی نال قابل تشخیص هستند. سایر نظرات، توسط همین ReplyId به یکی از Idهای موجود، متصل شده‌اند.

در تصویر فوق و با توجه به اطلاعات ثبت شده، فرض کنید می‌خواهیم ریشه‌ی با id مساوی 1 و تمام زیر ریشه‌های آن‌را بیابیم. انجام یک چنین کاری نه در EF Core و نه در EF 6.x، پشتیبانی نمی‌شود. بدیهی است در اینجا هنوز روش‌های Include و ThenInclue هم جواب می‌دهند؛ اما چون Lazy loading فعال نیست، عملا نمی‌توان تمام زیر ریشه‌ها را یافت و همچنین به چندین و چند رفت و برگشت به ازای هر زیر ریشه خواهیم رسید که اصلا بهینه نیست.
برای اینکار نیاز است مستقیما کوئری نویسی کرد که در مطلب «شروع به کار با EF Core 1.0 - قسمت 10 - استفاده از امکانات بومی بانک‌های اطلاعاتی» زیر ساخت آن‌را بررسی کردیم:
var id = 1;
var childIds = new List<int>();
 
var connection = context.Database.GetDbConnection();
connection.Open();
var command = connection.CreateCommand();
command.CommandText =
    @"WITH Hierachy(ChildId, ParentId) AS (
                SELECT Id, ReplyId
                    FROM BlogComments
                UNION ALL
                SELECT h.ChildId, bc.ReplyId
                    FROM BlogComments bc
                    INNER JOIN Hierachy h ON bc.Id = h.ParentId
            )
 
            SELECT h.ChildId
                FROM Hierachy h
                WHERE h.ParentId = @Id";
command.Parameters.Add(new SqlParameter("id", id));
 
var reader = command.ExecuteReader();
while (reader.Read())
{
    var childId = (int)reader[0];
    childIds.Add(childId);
}
reader.Dispose();

 
var list = context.BlogComments
                .Where(blogComment => blogComment.Id == id ||
                                      childIds.Contains(blogComment.Id))
                .ToList();
زمانیکه کدهای فوق اجرا می‌شوند، تنها دو کوئری ذیل به بانک اطلاعاتی ارسال خواهند شد:
exec sp_executesql N'WITH Hierachy(ChildId, ParentId) AS (
                            SELECT Id, ReplyId
                                FROM BlogComments
                            UNION ALL
                            SELECT h.ChildId, bc.ReplyId
                                FROM BlogComments bc
                                INNER JOIN Hierachy h ON bc.Id = h.ParentId
                        )

                        SELECT h.ChildId
                            FROM Hierachy h
                            WHERE h.ParentId = @Id',N'@id int',@id=1
که لیست Idهای تمام زیر ریشه‌های مربوط به id مساوی یک را بر می‌گرداند:


و پس از آن:
 exec sp_executesql N'SELECT [blogComment].[Id], [blogComment].[Body], [blogComment].[Date1], [blogComment].[ReplyId]
FROM [BlogComments] AS [blogComment]
WHERE [blogComment].[Id] IN (@__id_0, 3, 5)',N'@__id_0 int',@__id_0=1
اکنون که Idهای مساوی 3 و 5 را یافتیم، با استفاده از متد Contains آن‌ها را تبدیل به where in کرده و لیست نهایی گزارش را تهیه می‌کنیم:


یافتن Idهای زیر ریشه‌ها توسط روش CTE (پیشنیازهای ابتدای بحث) و سپس کوئری گرفتن بر روی این Idها، بهینه‌ترین روشی‌است که در EF می‌توان جهت کار با مدل‌های خود ارجاع دهنده بکار گرفت.
مطالب
شروع به کار با EF Core 1.0 - قسمت 9 - بررسی رابطه‌ی One-to-One
بررسی رابطه‌ی One-to-Zero-or-One

زمانیکه نیاز است موجودیت A با هیچ و یا حداکثر یک وهله از موجودیت B در ارتباط باشد، به یک چنین رابطه‌ای One-to-Zero-or-One می‌گویند. برای اینکه یک چنین ارتباطی را تشکیل دهیم، نیاز است کلید اصلی یک جدول، در جدول مرتبط به آن، هم به عنوان کلید اصلی و هم به عنوان کلید خارجی معرفی شود؛ همانند شکل زیر که در آن CartableId، همزمان به صورت PK و FK تعریف شده‌است که به آن one-to-one association with shared primary key نیز می‌گویند:



الف) مدلسازی رابطه‌ی One-to-Zero-or-One توسط Fluent API

در اینجا دو موجودیت را ملاحظه می‌کنید که توسط دو navigation property به هم متصل شده‌اند:
    public class UserProfile
    {
        public int UserProfileId { get; set; }
        public string UserName { get; set; }

        public virtual Cartable Cartable { get; set; }
    }

    public class Cartable
    {
        public int CartableId { get; set; }

        public virtual UserProfile UserProfile { get; set; }
    }
برای اینکه بتوان CartableId را هم به عنوان PK و هم FK معرفی کرد، نیاز است از Fluent API به نحو ذیل استفاده کنیم:
    public class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Cartable>(entity =>
            {
                entity.Property(e => e.CartableId).ValueGeneratedNever();

                entity.HasOne(d => d.UserProfile)
                    .WithOne(p => p.Cartable)
                    .HasForeignKey<Cartable>(d => d.CartableId);
            });
        }

        public virtual DbSet<Cartable> Cartables { get; set; }
        public virtual DbSet<UserProfile> UserProfiles { get; set; }
    }
در اینجا رابطه‌ی یک به یک، توسط متدهای HasOne و WithOne معرفی شده‌است. به علاوه FK بودن CartableId به صورت صریح توسط متد HasForeignKey نیز مشخص گردیده‌است.
همچنین بر روی CartableId، فراخوانی متد ValueGeneratedNever را مشاهده می‌کنید. این متد را در قسمت «روش‌های مختلف تولید خودکار مقادیر خواص» مطلب «شروع به کار با EF Core 1.0 - قسمت 5 - استراتژهای تعیین کلید اصلی جداول و ایندکس‌ها» پیشتر بررسی کردیم. هدف آن این است که به بانک اطلاعاتی اعلام کند، این فیلد یک کلید اصلی از نوع خود افزایش یابنده نیست و مقدار آن توسط برنامه به صورت صریح تنظیم می‌شود (چون کلید خارجی نیز هست و به کلید اصلی جدول سمت دیگر رابطه اشاره می‌کند).

ب) مدلسازی رابطه‌ی One-to-Zero-or-One توسط Data Annotations
برای تنظیم این رابطه توسط ویژگی‌ها نیاز است DatabaseGenerated به None تنظیم شود تا کلید اصلی CartableId به صورت خودکار توسط بانک اطلاعاتی تولید نشود. همچنین این کلید اصلی نیز باید به صورت کلید خارجی نیز معرفی شود. به علاوه توسط InversePropertyها، باید هر دو سطر به هم مرتبط، ذکر شوند:
    public class Cartable
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CartableId { get; set; }

        [ForeignKey("CartableId")]
        [InverseProperty("Cartable")]
        public virtual UserProfile UserProfile { get; set; }
    }

    public class UserProfile
    {
        public int UserProfileId { get; set; }
        public string UserName { get; set; }

        [InverseProperty("UserProfile")]
        public virtual Cartable Cartable { get; set; }
    }


بررسی رابطه‌ی One-to-One

تشکیل رابطه‌ی One-to-One که در آن برخلاف رابطه‌ی One-to-Zero-or-One، وجود هر دو سر رابطه اجباری هستند، در SQL Server میسر نیست (زیرا زمانیکه یک چنین رابطه‌ای تشکیل شود، نمی‌توان رکوردی را Insert کرد! چون زمانیکه هنوز یک سر رابطه ثبت نشده‌است، چگونه می‌توان Id آن‌را در سر دیگری به اجبار ثبت کرد؟!). SQL Server این رابطه را نیز به صورت همان One-to-Zero-or-One تشکیل می‌دهد. تنظیمات آن نیز با قبل تفاوتی ندارد. در این حالت اجباری بودن و یا نبودن یک سر رابطه همانند نکات قسمت «تعیین اجباری بودن یا نبودن ستون‌ها در EF Core» در مطلب «شروع به کار با EF Core 1.0 - قسمت 6 - تعیین نوع‌های داده و ویژگی‌های آن‌ها» است و این تنظیمات در اینجا صرفا از دیدگاه EF Core مفهوم دارند.
جهت تکمیل بحث، روش تشکیل رابطه‌ی One-to-One بدون استفاده از روش به اشتراک گذاری کلید اصلی (one-to-one association with shared primary key) به صورت زیر است:


همانطور که مشاهده می‌کنید، در اینجا هر بلاگ حداکثر یک تصویر را می‌تواند داشته باشد. علت آن نیز به ذکر MyBlogForeignKey بر می‌گردد که یک کلید خارجی نال نپذیر است.
    public class MyBlog
    {
        public int MyBlogId { get; set; }
        public string Url { get; set; }

        public MyBlogImage MyBlogImage { get; set; }
    }

    public class MyBlogImage
    {
        public int MyBlogImageId { get; set; }
        public byte[] Image { get; set; }
        public string Caption { get; set; }

        public int MyBlogForeignKey { get; set; }
        public MyBlog MyBlog { get; set; }
    }
با این تنظیمات:
modelBuilder.Entity<MyBlog>()
   .HasOne(p => p.MyBlogImage)
   .WithOne(i => i.MyBlog)
   .HasForeignKey<MyBlogImage>(b => b.MyBlogForeignKey);
در اینجا جدول MyBlogImage هنوز Id خود افزاینده‌ی خود را دارد، اما ثبت رکورد آن بدون وجود کلید خارجی MyBlog میسر نیست.
مطالب
اشیاء context در ASP.NET
در asp.net  تعدادی اشیاء پایه، حاوی اطلاعات بسیار با ارزشی در خصوص درخواست جاری، application و پاسخی که ارسال می‌شود هستند و به صورت غیر مستقیم جهت دست‌یابی به قسمتهای مرکزی و هسته‌ای چهارچوب asp.net مانند security , stat data می‌توان این اشیاء را بکار گرفت.
بررسی این اشیاء از این جهت حائز اهمیت است که در کنترلر‌ها و ویوها می‌توان پاسخهای ارسالی به کلاینت‌ها را بر حسب شرایط مختلفی مانند درخواست رسیده یا حالت خاص دیگری تغییر داد. ضمن اینکه از این اشیاء در ماژول‌ها و هندلر‌ها نیز استفاده می‌شود. لذا قبل از پرداختن به مفاهیم مرتبط به ماژولها و هندلرها بهتر است این اشیاء بررسی شوند.

کلاس httpcontext هسته‌ی چهارچوب Asp.net است که خواص بسیار مهمی به شرح ذیل را فراهم می‌آورد:
Application : شیء HttpAplicationState را جهت مدیریت Application State Data بر می‌گرداند.
ApplicationInstance  : شیء HttpApplication مرتبط با درخواست جاری را بر می‌گرداند.
Cache : شیء Cache را جهت کش داده‌ها بر می‌گرداند.
Current : خصوصیت استاتیکی که شیء HttpContext درخواست جاری را بر می‌گرداند.
CurrentHandler : وهله‌ای از IHttpHandler را که محتویات درخواست جاری را تولید خواهد کرد،  بر می‌گرداند.
IsDebuggingEnabled : مقدار True یا False را بسته به اینکه Debug فعال باشد یا خیر بر می‌گرداند.
Items : یک Collection را که می‌تواند جهت انتقال State Data بین اجزاء مختلف فریم ورک که در پردازش یک درخواست همکاری می‌کنند، بر گرداند.
GetSection : قسمت خاصی از فایل Web.Config را بر می‌گرداند.
Request : شیء HttpRequest را که حاوی جزئیات درخواست پردازش شده است، بر می‌گرداند.
Response : شیء HttpResponse را بر می‌گرداند که حاوی جزئیات Response ای است که آماده ارسال به مرورگر است.
Session : برگرداننده‌ی شیء HttpSession است. این خاصیت قبل از وقوع رخداد PostAcquireRequestState مربوط به Application، مقدار null را خواهد داشت.
TimeStamp : شیء از نوع Datetime و معرف زمانی است که شیء HttpContext ایجاد شده‌است.
Trace : جهت ثبت اطلاعات تشخیصی به کار می‌رود.

در فایل global از طریق خاصیت Context به وهله‌ای از HttpContext دسترسی داریم. ولی این خاصیت فراگیر نبوده و در ویوها و کنترلر‌ها از طریق خاصیت HttpContext  در دسترس است که در کنترلر خاصیتی از کلاس پایه Controller و در ویو خاصیتی از کلاس پایه WebViewPage می‌باشد و در ماژول‌ها از طریق پارامتر ورودی متد Init در دسترس است‌.
در هندلر‌ها متد ProcessRequest وهله‌ای از شیء HttpContext را دریافت می‌کند.
به طور سراسری در هر نقطه‌ای از برنامه از طریق خاصیت استاتیک HttpContext.Current  به شیء HttpContext مرتبط با درخواست جاری دسترسی خواهیم داشت.
در کلاسهای مدل نیز از طریق خاصیت استاتیک یاد شده می‌توان به این شیء دسترسی داشت؛ ولی بهتر است اگر مدل نیاز به اطلاعاتی در خصوص یک Request داشت این اطلاعات از طریق اشیاء Context در کنترلر دریافت و به عنوان آرگومان به مدل ارسال شوند.
به صورت کلی، خلاصه‌ی مطالب فوق به صورت ذیل می‌باشد:

بخش مورد نظر 
روش دسترسی به اشیاء context
 Controller 
 از طریق خاصیت HttpContext
View 
خاصیت Context
Global Application Class خاصیت Context
Modules 
آرگومان متد Init
Handlers 
 آرگومان متد ProcessRequest
به صورت کلی از طریق خاصیت استاتیک HttpContext.Current


خواص ذکر شده‌ی در جدول فوق، همگی نوع یکسانی ندارند. در فایل  global و هندلر‌ها و ماژولها، دقیقا به همان HtpContext ای دسترسی داریم که قبلا با آن‌ها آشنا شدیم. این نوع از اشیاء (منظور خواص کلاس HttpContext) قبل از اینکه فریم ورک MVC، دست به کار مدیریت یک درخواست شود، کار خود را شروع می‌کنند که این امر کار آزمایش واحد را مشکل می‌کند. برای رفع این مشکل خواص مهیا در کنترلر و ویو وهله‌هایی از کلاسهای متفاوتی را برمی گردانند که از کلاس Context مشتق شده‌اند و ضمنا آزمایش واحد را به سادگی پشتیبانی می‌کنند(+ ) . خاصیت HttpContext کلاس Controller وهله‌ای از کلاس HttpContextBase را بر می‌گردانند. همه‌ی اشیاء context موجود در این کلاس، دارای پسوند Base هستند؛ مانند : HttpRequestBase, HttpResponseBase و...
اگر نیاز داشتید که یک شیء از نوع پسوند دار Base را از یک شیء فاقد این پسوند ایجاد کنید، برای مثال یک شیء HttpRequest دارید، ولی متدی دارید که آرگومان آن وهله‌ای از کلاس HttpRequestBase است، برای این کار کلاسهایی با پسوند Wrapper مانند HttpContextWrapper ,…  وجود دارند که از کلاسهایی با پسوند Base مشتق شده و در متد سازنده خود آرگومانی از کلاسهای اصلی فاقد پسوند Base را می‌گیرند.
ولی عکس آن امکان پذیر نیست و نمی‌توان کلاس با پسوند Base را به نوع اولیه برگرداند. ولی همان طور که گفتیم هر جایی که نیاز داشتیم، می‌توان از طریق خاصیت استاتیک System.Web.HTTPContext.Current به اشیاء Context اصلی دسترسی داشت.
بسیاری از کلاسهای فریم ورک Asp.Net دارای خاصیت‌هایی هستند که به خوااص کلاس HttpContext نگاشت شده‌اند. یکی از این نمونه‌ها کلاس HttpApplication است که کلاس پایه‌ی فایل global نیز هست. همان طور که در ذیل نیز می‌بینید، بسیاری از خواص و متدهای کلاس HttpApplication به نوعی مرتبط با اشیاء context هستند.

Application : به خاصیت HttpContext.Application نگاشت شده‌است که دسترسی به state data application را مهیا می‌سازد.
CompleteRequest : به چرخه حیات جاری خاتمه خاتمه داده و آن را مستقیما به رخداد LogRequest منتقل می‌کند.
Context : شیء HttpContext مرتبط با درخواست جاری را بر می‌گرداند.
Init : هر گاه متد Init یکی از ماژولهای ثبت شده در برنامه فراخوانی شود، این متد صدا زده خواهد شد.
Modules : یک شیء HttpModuleCollection را که حاوی جزئیات ماژولهای برنامه است، بر می‌گرداند.
RegisterModule(type: متد استاتیکی است که یک ماژول جدید را ثبت می‌کند.
Request : مقدار HttpContext.Request را برگشت می‌دهد و در صورتی که این مقدار null باشد، استثنایی رخ خواهد داد.
Response : مقدار HttpContext.Response را برگشت داده و در صورت null بودن این مقدار، استثنایی رخ می‌دهد.
Server : به خاصیت HttpContext.Server نگاشت شده است.
Session : مقدار HttpContext.Session را برگشت می‌دهد که در صورت null بودن این مقدار، استثنایی رخ خواهد داد.

در این مقاله با خواص کلاس HttpContext که اشیائی مهم و پرکاربرد بوده و حاوی اطلاعات بسیار سودمندی هستند، آشنا شدیم. ضمنا همان طور که در ابتدای مقاله گفته شد، از این اشیاء در ماژول‌ها و هندلرها استفاده‌ی زیادی می‌شود.
مطالب
ایجاد سرویس Account Manager با تکنولوژی های Identity 2.1 و Web API 2.2
ASP.NET Identity 2.1 جدیدترین فریم ورک عضویت و مدیریت کاربر است که چندی پیش توسط شرکت مایکروسافت منتشر شد. این سیستم عضویت می‌تواند به تمامی فریم‌ورک‌های دات نتی مانند Web API، MVC و ... متصل گردد.
در این دوره چند قسمتی به همراه یک پروژه‌ی نمونه، نحوه‌ی ارتباط Identity و Web API را نمایش خواهیم داد. در قسمت front-end این پروژه‌ی SPA، ما از AngularJs استفاده خواهیم نمود. قسمت front-end که توسط AngularJs توسعه داده می‌شود از bearet token based authentication استفاده می‌کند. این متد از JSON Web Token برای فرایند Authorization بهره می‌گیرد که به اختصار آن را JWT نیز می‌نامند. این روش سیستم اعتبار سنجی role based بوده و  تمامی آنچه را که در یک سیستم membership داریم، پوشش می‌دهد. توجه داشته باشید که برای فهم بهتر تمامی مراحل، ما پروژه را بدون هیچ قالب از پیش تعریف شده‌ای در VS2013 آغاز می‌کنیم.
به دلیل اینکه پروژه در طی چند قسمت انجام می‌پذیرد آن را به بخش‌های زیر تقسیم می‌کنیم.
  1.  تنظیمات اولیه ASP.NET Identity 2.1 با Web API
  2.  ایجاد Account Confirmation به وسیله Identity به همراه تنظیمات policy برای user name و password
  3.  توسعه OAuth Json Web Token Authentication به کمک Web API و Identity
  4.  ایجاد یک سیستم Role Based و تایید صلاحیت‌های مربوط به آن
  5.  توسعه Web API Claims Authorization در Identity 2.1
  6.  توسعه بخش front-end با AngularJs

تنظیمات اولیه ASP.NET Identity 2.1 با Web API 

1. تنظیمات ASP.Net Identity 2.1

1-1. ساخت یک پروژه Web API

در ابتدا ما یک empty solution را با نام "AspNetIdentity" همانند شکل مقابل می‌سازیم.

یک ASP.NET Web Application با نام "AspNetIdentity.WebApi" را به این solution اضافه می‌نماییم. در بخش select template ما empty template را انتخاب می‌کنیم. همچنین در قسمت add folders and core references for: نیز هیچیک از گزینه‌ها را انتخاب نمی‌کنیم.

1-2. نصب package‌های مورد نیاز از Nuget

در زیر package‌های مورد نیاز برای ASP.NET Web API و Owin را مشاهده میکنید. همچنین package‌های مربوط به ASP.Net Identity 2.1 نیز در زیر قرار داده شده‌اند.

Install-Package Microsoft.AspNet.Identity.Owin -Version 2.1.0
Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.1.0
Install-Package Microsoft.Owin.Host.SystemWeb -Version 3.0.0
Install-Package Microsoft.AspNet.WebApi.Owin -Version 5.2.2
Install-Package Microsoft.Owin.Security.OAuth -Version 3.0.0
Install-Package Microsoft.Owin.Cors -Version 3.0.0

1-3. اضافه کردن user class و database context

حال که تمامی پکیج‌های مورد نیاز را به پروژه خود اضافه نمودیم، قصد داریم تا اولین کلاس EF را با نام "ApplicationUser" به پروژه اضافه کنیم. این کلاس، کاربری را که قصد ثبت نام در membership system، دارد را نمایش می‌دهد. برای این کار ما یک کلاس جدید را به نام "ApplicationUser" می‌سازیم و کلاس "Microsoft.AspNet.Identity.EntityFramework.IdentityUser" را در آن به ارث می‌بریم.

برای این کار ما یک پوشه‌ی جدید را در برنامه با نام "Infrastructure" می‌سازیم و درون آن کلاس ذکر شده را اضافه می‌کنیم:

 public class ApplicationUser : IdentityUser
    {
        [Required]
        [MaxLength(100)]
        public string FirstName { get; set; }
 
        [Required]
        [MaxLength(100)]
        public string LastName { get; set; }
 
        [Required]
        public byte Level { get; set; }
 
        [Required]
        public DateTime JoinDate { get; set; }
 
    }

پس از افزودن کلاس User، نوبت به اضافه نمودن Db Context است. این کلاس وظیفه‌ی ارتباط با پایگاه داده را بر عهده دارد. ما یک کلاس جدید را با نام ApplicationDbContext، به پوشه‌ی Infrastructure اضافه می‌نماییم. کد مربوط به این کلاس به صورت زیر است:

 public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
            Configuration.ProxyCreationEnabled = false;
            Configuration.LazyLoadingEnabled = false;
        }
 
        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
 
    }

همانطور که ملاحظه می‌کنید این کلاس از IdentityDbContext ارث بری نموده است. این کلاس یک نسخه‌ی جدیدتر از DbContext است که تمامی نگاشت‌های Entity Framework Code First را انجام می‌دهد. در ادامه ما یک Connection String را با نام DefaultConnection در فایل web.config اضافه می‌نماییم.

همچنین متد static ایی را با نام Create که در تکه کد فوق از سوی Owin Startup class فراخوانی می‌گردد که در ادامه به شرح آن نیز خواهیم پرداخت.

ConnectionString قرار داده شده در فایل web.config در قسمت زیر قرار داده شده است:

<connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=.\sqlexpress;Initial Catalog=AspNetIdentity;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
  </connectionStrings>

قدم 4: ساخت پایگاه داده و فعال سازی DB Migration

حال ما باید EF CodeFirst Migration را برای آپدیت کردن دیتابیس، بجای دوباره ساختن آن به ازای هر تغییری، فعال نماییم. برای این کار بایستی در قسمت NuGet Package Manager Console عبارت زیر را وارد نماییم:

enable-migrations
add-migration InitialCreate

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

در بخش بعدی، کلاس‌های مربوط به UserManager را ایجاد خواهیم کرد و پس از آن فایل Startup Owin را برای مدیریت کاربران، تشریح می‌کنیم.

مطالب
Asp.Net Identity #2
پیشتر در اینجا در مورد تاریخچه‌ی سیستم Identity مطالبی را عنوان کردیم. در این مقاله می‌خواهیم نحوه‌ی برپایی سیستم Identity را بحث کنیم.
ASP.NET Identity مانند ASP.NET Membership به اسکیمای SQL Server وابسته نیست؛ اما Relational Storage همچنان واحد ذخیره سازی پیش فرض و آسانی می‌باشد. بدین جهت که تقریبا بین همه‌ی توسعه دهندگان جا افتاده است. ما در این نوشتار از LocalDB جهت ذخیره سازی جداول استفاده می‌کنیم. ذکر این نکته ضروری است که سیستم Identity از Entity Framework Code First جهت ساخت اسکیما استفاده می‌کند.
پیش از هر چیز، ابتدا یک پروژه‌ی وب را ایجاد کنید؛ مانند شکل زیر:

در مرحله‌ی بعد سه پکیج زیر را باید نصب کنیم :

- Microsoft.AspNet.Identity.EntityFramework

-   Microsoft.AspNet.Identity.OWIN

-  Microsoft.Owin.Host.SystemWeb

بعد از اینکه پکیج‌های بالا را نصب کردیم، باید فایل Web.config را بروز کنیم. اولین مورد، تعریف یک Connection String می‌باشد:
<connectionStrings>
 <add name="IdentityDb" 
      providerName="System.Data.SqlClient"
      connectionString="Data Source=(localdb)\v11.0;Initial Catalog=IdentityDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=True"/>
 </connectionStrings>
بعد از آن، تعریف یک کلید در قسمت AppSettings تحت عنوان Owin:AppStartup است:
<appSettings>
 <add key="webpages:Version" value="3.0.0.0" />
 <add key="webpages:Enabled" value="false" />
 <add key="ClientValidationEnabled" value="true" />
 <add key="UnobtrusiveJavaScriptEnabled" value="true" />
 <add key="owin:AppStartup" value="Users.IdentityConfig" />
 </appSettings>
Owin مدل شروع برنامه (Application Startup Model) خودش را تعریف می‌کند که از کلاس کلی برنامه (منظور Global.asax) جداست. AppSetting تعریف شده با نام owin:Startup شناخته می‌شود و مشخص کننده کلاسی است که Owin وهله سازی خواهد کرد. وقتی برنامه شروع به کار می‌کند، تنظیمات خودش را از این کلاس دریافت خواهد کرد (در این نوشتار کلاس IdentityConfig می‌باشد).

ساخت کلاس User
مرحله‌ی بعد ساخت کلاس User می‌باشد. این کلاس بیانگر یک کاربر می‌باشد. کلاس User باید از کلاس IdentityUser ارث بری کند که این کلاس در فضای نام Microsoft.AspNet.Identity.EntityFramework قرار دارد. کلاس IdentityUser فراهم کننده‌ی یک کاربر عمومی و ابتدایی است. اگر بخواهیم اطلاعات اضافی مربوط به کاربر را ذخیره کنیم باید آنها در کلاسی که از کلاس IdentityUser ارث بری می‌کند قرار دهیم. کلاس ما در اینجا AppUser نام دارد.
using System;
using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models 
{
   public class AppUser : IdentityUser 
  {
      // پروپرتی‌های اضافی در اینجا
  }
}

ساخت کلاس Database Context برنامه
مرحله‌ی بعد ساخت کلاس DbContext برنامه می‌باشد که بر روی کلاس AppUser عمل میکند. کلاس Context برنامه که ما در اینجا آن را AppIdentityDbContext تعریف کرده‌ایم، از کلاس <IdentityDbContext<T ارث بری می‌کند که T همان کلاس User می‌باشد (در مثال ما AppUser) .
using System.Data.Entity;
using Microsoft.AspNet.Identity.EntityFramework;
using Users.Models;
namespace Users.Infrastructure {
 public class AppIdentityDbContext : IdentityDbContext<AppUser> 
{
   public AppIdentityDbContext() 
              : base("IdentityDb") { }

  static AppIdentityDbContext() 
 {
    Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());
  }
 public static AppIdentityDbContext Create() {
 return new AppIdentityDbContext();
 }
 }
public class IdentityDbInit
 : DropCreateDatabaseIfModelChanges<AppIdentityDbContext> {
 protected override void Seed(AppIdentityDbContext context) {
 PerformInitialSetup(context);
 base.Seed(context);
 }
 public void PerformInitialSetup(AppIdentityDbContext context) {
 // initial configuration will go here
 }
 }
}

ساخت کلاس User Manager
یکی از مهمترین کلاسهای Identity کلاس User Manager (مدیر کاربر) می‌باشد که نمونه‌هایی از کلاس User را مدیریت می‌کند. کلاسی را که تعریف می‌کنیم، باید از کلاس <UserManager<T ارث بری کند که T همان کلاس User می‌باشد. کلاس UserManager خاص EF نمی‌باشد و ویژگی‌های عمومی بیشتری برای ساخت و انجام عملیات بر روی داده‌های کاربر را فراهم می‌نماید.

کلاس UserManager حاوی متدهای بالا است. اگر دقت کنید، می‌بینید که تمامی متدهای بالا به کلمه‌ی Async ختم می‌شوند. زیرا Asp.Net Identity تقریبا کل ویژگیهای برنامه نویسی Async را پیاده سازی کرده است و این بدین معنی است که عملیات میتوانند به صورت غیر همزمان اجرا شده و دیگر فعالیت‌ها را بلوکه نکنند.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;

namespace Users.Infrastructure 
{
 public class AppUserManager : UserManager<AppUser> 
{

 public AppUserManager(IUserStore<AppUser> store)
 : base(store) {
 }
 public static AppUserManager Create( IdentityFactoryOptions<AppUserManager> options, IOwinContext context) 
{
 AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
 AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
 return manager;
 }
 }
}

زمانی که Identity نیاز به وهله‌ای از کلاس AppUserManager داشته باشد، متد استاتیک Create را صدا خواهد زد. این عمل زمانی اتفاق می‌افتد که عملیاتی بر روی داده‌های کاربر انجام گیرد. برای ساخت وهله‌ای از کلاس AppUserManager نیاز به کلاس <UserStor<AppUser دارد. کلاس <UserStore<T یک پیاده سازی از رابط <IUserStore<T  توسط  EF میباشد که وظیفه‌ی آن فراهم کننده‌ی پیاده سازی Storage-Specific متدهای تعریف شده در کلاس User Manager (که در اینجا AppUserManager ) می‌باشد. برای ساخت <UserStore<T نیاز به وهله‌ای از کلاس AppIdentityDbContext می‌باشد که از طریق Owin به صورت زیر قابل دریافت است:

AppIdentityDbContext db = context.Get<AppIdentityDbContext>();

یک پیاده سازی از رابط IOwinContext، به عنوان پارامتر به متد Create پاس داده می‌شود. در این پیاده سازی، یک تابع جنریک به نام Get تعریف شده که اقدام به برگشت وهله ای از اشیای ثبت شده‌ی در کلاس شروع Owin می‌نماید.


ساخت کلاس شروع Owin

اگر خاطرتان باشد یک کلید در قسمت AppSettings فایل Web.config به صورت زیر تعریف کردیم:

<add key="owin:AppStartup" value="Users.IdentityConfig" />

قسمت Value کلید بالا از دو قسمت تشکیل شده است: Users بیانگر فضای نام برنامه‌ی شماست و IdentityConfig بیانگر کلاس شروع می‌باشد. (البته شما می‌توانید هر نام دلخواهی را برای کلاس شروع بگذارید. فقط دقت داشته باشید که نام کلاس شروع و مقدار، با کلیدی که تعریف می‌کنید یکی باشد)

Owin مستقل از ASP.NET اعلام شده است و قراردادهای خاص خودش را دارد. یکی از این قراردادها تعریف یک کلاس و وهله سازی آن به منظور بارگذاری و پیکربندی میان افزار و انجام دیگر کارهای پیکربندی که نیاز است، می‌باشد. به طور پیش فرض این کلاس Start نام دارد و در پوشه‌ی App_Start تعریف می‌شود. این کلاس حاوی یک متد به نام Configuration می‌باشد که بوسیله زیرساخت Owin فراخوانی می‌شود و یک پیاده سازی از رابط Owin.IAppBuilder به عنوان آرگومان به آن پاس داده می‌شود که کار پشتیبانی از Setup میان افزار مورد نیاز برنامه را برعهده دارد.

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Users.Infrastructure;
namespace Users 
{
 public class IdentityConfig 
{
 public void Configuration(IAppBuilder app) 
{
 app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);
 app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
 app.UseCookieAuthentication(new CookieAuthenticationOptions {
 AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
 LoginPath = new PathString("/Account/Login"),
 });
 }
 }
}

رابط IAppBuilder بوسیله تعدادی متد الحاقی که در کلاسهایی که در فضای نام Owin تعریف شده‌اند، تکمیل شده است. متد CreatePerOwinContext کار ساخت وهله‌ای از کلاس AppUserManager و کلاس AppIdentityDbContext را برای هر درخواست بر عهده دارد. این مورد تضمین می‌کند که هر درخواست، به یک داده‌ی تمیز از Asp.Net Identity دسترسی خواهد داشت و نگران اعمال همزمانی و یا کش ضعیف داده نخواهد بود. متد UseCookieAuthentication به Asp.Net Identity می‌گوید که چگونه از کوکی‌ها برای تصدیق هویت کاربران استفاده کند که Optionهای آن از طریق کلاس CookieAuthenticationOptions مشخص می‌شود. مهمترین قسمت در اینجا پروپرتی LoginPath می‌باشد و مشخص می‌کند که کلاینت‌های تصدیق هویت نشده، هنگام دسترسی به یک منبع محافظت شده، به کدام URL هدایت شوند که توسط یک رشته به متد PathString پاس داده می‌شود.

خوب دوستان برپایی سیستم Identity به پایان رسید. انشالله در قسمت بعدی به چگونگی استفاده‌ی از این سیستم خواهیم پرداخت.