مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک
همانطور که در قسمت قبل، با معرفی مقدماتی Middlewareها عنوان شد، تمام قابلیت‌های یک برنامه‌ی ASP.NET Core، به صورت پیش فرض غیرفعال هستند؛ مگر آنکه Middlewareهای مخصوص آن‌ها را به صورت دستی و با آگاهی کامل، به کلاس آغازین برنامه اضافه کنید. در این قسمت قصد داریم تعداد دیگری از این Middlewareهای توکار را مورد بررسی قرار دهیم.


فعال سازی پردازش فایل‌های استاتیک در برنامه‌های ASP.NET Core 1.0

در مورد پوشه‌ی جدید wwwroot در «قسمت 2 - بررسی ساختار جدید Solution» مطالبی عنوان شدند. جهت یادآوری:
اگر فایل Program.cs را بررسی کنید، یک چنین تعاریفی را مشاهده خواهید کرد:
public class Program
{
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();
 
        host.Run();
    }
}
در کدهای فوق، سطر UseContentRoot، پوشه‌ی خاصی را به نام content root معرفی می‌کند که در اینجا به همان پوشه‌ی اصلی برنامه اشاره می‌کند و پوشه‌ی wwwroot از مسیر content root/wwwroot خوانده می‌شود که جهت ارائه‌ی تمام فایل‌های عمومی برنامه مورد استفاده قرار می‌گیرد (مانند تصاویر، فایل‌های JS ،CSS و امثال آن). هدف این است که کدهای سمت سرور برنامه (قرار گرفته در content root) از کدهای عمومی آن (قرار گرفته در پوشه‌ی ویژه‌ی content root/wwwroot) جدا شده و به این ترتیب احتمال نشتی اطلاعات سمت سرور به حداقل برسد.

یک مثال: زمانیکه فایل استاتیک images/banner3.svg در پوشه‌ی wwwroot قرار می‌گیرد، با آدرس http://localhost:9189/images/banner3.svg توسط عموم قابل دسترسی خواهد بود.

یک نکته‌ی امنیتی مهم
در برنامه‌های ASP.NET Core، هنوز فایل web.config را نیز مشاهده می‌کنید. این فایل تنها کاربردی که در اینجا دارد، تنظیم ماژول AspNetCoreModule برای IIS است تا IIS static file handler آن، راسا اقدام به توزیع فایل‌های یک برنامه‌ی ASP.NET Core نکند. بنابراین توزیع این فایل را بر روی سرورهای IIS فراموش نکنید. همچنین بهتر است در ویندوزهای سرور، به قسمت Modules feature مراجعه کرده و StaticFileModule را از لیست ویژگی‌های موجود حذف کرد.


نصب Middleware مخصوص پردازش فایل‌های استاتیک

در قسمت قبل با نحوه‌ی نصب و فعال سازی middleware مخصوص WelcomePage آشنا شدیم. روال کار در اینجا نیز دقیقا به همان صورت است:
الف) نصب بسته‌ی نیوگت Microsoft.AspNetCore.StaticFiles
برای اینکار می‌توان بر روی گره‌ی references کلیک راست کرده و سپس از منوی ظاهر شده،‌گزینه‌ی manage nuget packages را انتخاب کرد. سپس ابتدا برگه‌ی browse را انتخاب کنید و در اینجا نام Microsoft.AspNetCore.StaticFiles را جستجو کرده و سپس نصب کنید.


انجام این کارها معادل افزودن یک سطر ذیل به فایل project.json است و سپس ذخیره‌ی آن که کار بازیابی بسته‌ها را به صورت خودکار آغاز می‌کند:
 "dependencies": {
   // same as before
    "Microsoft.AspNetCore.StaticFiles": "1.0.0"
},
ب) معرفی Middleware پردازش فایل‌های استاتیک
برای اینکار به فایل Startup.cs مراجعه کرده و سطر UseStaticFiles را به متد Configure اضافه کنید (به UseWelcomePage هم دیگر نیازی نداریم):
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }
 
    public void Configure(IApplicationBuilder app)
    {
        app.UseStaticFiles();
        //app.UseWelcomePage();
 
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello DNT!");
        });
    }
}

یک مثال: بر روی پوشه‌ی wwwroot کلیک راست کرده و گزینه‌ی add->new item را انتخاب کنید. سپس یک HTML page جدید را به نام index.html به این پوشه اضافه کنید.
با این محتوا:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Hello World</title>
</head>
<body>
    Hello World!
</body>
</html>
در این حالت برنامه را اجرا کنید. خروجی ذیل را مشاهده خواهید کرد:


که این خروجی دقیقا خروجی app.Run برنامه است و نه محتوای فایل index.html ایی که اضافه کردیم.
در ادامه اگر مسیر کامل این فایل را (http://localhost:7742/index.html) درخواست دهیم، آنگاه می‌توان خروجی این فایل استاتیک را مشاهده کرد:


این رفتار اندکی متفاوت است نسبت به نگارش‌های قبلی ASP.NET که فایل index.html را به عنوان فایل پیش فرض، درنظر می‌گرفت و محتوای آن‌را نمایش می‌داد. منظور از فایل پیش فرض، فایلی است که با درخواست ریشه‌ی یک مسیر، به کاربر ارائه داده می‌شود و index.html یکی از آن‌ها است.
برای رفع این مشکل، نیاز است Middleware مخصوص آن‌را به نام Default Files نیز به برنامه معرفی کرد:
public void Configure(IApplicationBuilder app)
{
   app.UseDefaultFiles();
   app.UseStaticFiles();
در این حالت است که با درخواست ریشه‌ی سایت، فایل پیش فرض آن نمایش داده خواهد شد:


فعال سازی Default Files، سبب جستجوی یکی از 4 فایل ذیل به صورت پیش فرض می‌شود (اگر تنها ریشه‌ی پوشه‌ای درخواست شود):
default.htm
default.html
index.htm
index.html

اگر خواستید فایل سفارشی خاص دیگری را معرفی کنید، نیاز است پارامتر DefaultFilesOptions آن‌را مقدار دهی نمائید:
 // Serve my app-specific default file, if present.
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);


ترتیب معرفی Middlewares مهم است

در قسمت قبل، در حین معرفی تفاوت‌های Middlewareها با HTTP Modules، عنوان شد که اینبار برنامه نویس می‌تواند بر روی ترتیب اجرای Middlewareها کنترل کاملی داشته باشد و این ترتیب معادل است با ترتیب معرفی آن‌ها در متد Configure، به نحوی که مشاهده می‌کنید. برای آزمایش این مطلب، متد معرفی middleware فایل‌های پیش فرض را پس از متد معرفی فایل‌های استاتیک قرار دهید:
public void Configure(IApplicationBuilder app)
{
  app.UseStaticFiles();
  app.UseDefaultFiles();
در این حالت اگر برنامه را اجرا کنید، به این خروجی خواهید رسید:


بله. اینبار تعریف فایل‌های پیش فرض، هیچ تاثیری نداشته و درخواست ریشه‌ی سایت، بدون ذکر صریح نام فایلی، مجددا به app.Run ختم شده‌است.


توزیع فایل‌های استاتیک خارج از wwwroot

همانطور که در ابتدای بحث عنوان شد، با فعال سازی UseStaticFiles به صورت پیش فرض مسیر content root/wwwroot در معرض دید دنیای خارج قرار می‌گیرد و توسط وب سرور قابل توزیع خواهد شد:
○ wwwroot
   § css
   § images
   § ...
○ MyStaticFiles
   § test.png
اما اگر قصد داشته باشیم تا تصویر test.png موجود در پوشه‌ی MyStaticFiles خارج از wwwroot را نیز عمومی کنیم چه باید کرد؟
برای این منظور می‌توان از پارامتر StaticFileOptions متد UseStaticFiles به نحو ذیل جهت معرفی پوشه‌ی MyStaticFiles استفاده کرد:
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")),
    RequestPath = new PathString("/StaticFiles")
});
در این حالت، مسیر دسترسی عمومی به این فایل، به صورت  http://<app>/StaticFiles/test.png خواهد بود (بر مبنای RequestPath تنظیم شده).


فعال سازی مشاهده‌ی مرور فایل‌های استاتیک بر روی سرور


فرض کنید پوشه‌ی تصاویر را به پوشه‌ی عمومی wwwroot اضافه کرده‌ایم. برای فعال سازی مرور محتوای این پوشه می‌توان از Middleware دیگری به نام DirectoryBrowser استفاده کرد:
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
    FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")),
    RequestPath = new PathString("/MyImages")
});
بعد از انجام اینکار به خطای ذیل خواهید رسید:
 Unable to resolve service for type 'System.Text.Encodings.Web.HtmlEncoder' while attempting to activate 'Microsoft.AspNetCore.StaticFiles.DirectoryBrowserMiddleware'.
برای رفع آن، سرویس آن نیز باید به متد ConfigureServices اضافه شود:
public void ConfigureServices(IServiceCollection services)
{
   services.AddDirectoryBrowser();
}
در این حالت پس از اجرای برنامه، اگر مسیر http://localhost:7742/myimages را درخواست دهید (MyImages از RequestPath تنظیم شده، گرفته می‌شود)، به تصویر ذیل خواهید رسید:


مشکل! در این حالت که DirectoryBrowser را فعال کرده‌ایم، اگر بر روی لینک فایل تصویر نمایش داده شده کلیک کنیم، باز پیام Hello DNT یا اجرای app.Run را شاهد خواهیم بود.
به این دلیل که UseStaticFiles پیش فرض، مسیر درخواستی MyImages را که بر روی file system وجود ندارد، نمی‌شناسد. برای رفع این مشکل تنها کافی است مسیریابی این Request Path خاص را نیز فعال کنیم:
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")),
    RequestPath = new PathString("/MyImages")
});


بررسی خلاصه‌ی تنظیماتی که به فایل آغازین برنامه اضافه شدند

تا اینجا اگر توضیحات را قدم به قدم دنبال و اجرا کرده باشید، یک چنین تنظیماتی را خواهید داشت:
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
 
namespace Core1RtmEmptyTest
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDirectoryBrowser();
        }
 
        public void Configure(IApplicationBuilder app)
        {
            app.UseDefaultFiles();
 
            app.UseStaticFiles(); // For the wwwroot folder
 
            // For the files outside of the wwwroot
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")),
                RequestPath = new PathString("/StaticFiles")
            });
 
            // For DirectoryBrowser
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")),
                RequestPath = new PathString("/MyImages")
            });
 
            app.UseDirectoryBrowser(new DirectoryBrowserOptions
            {
                FileProvider = new PhysicalFileProvider(root: Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\images")),
                RequestPath = new PathString("/MyImages")
            });
 
            //app.UseWelcomePage();
 
            app.Run(async context =>
            {
                await context.Response.WriteAsync("Hello DNT!");
            });
        }
    }
}
services.AddDirectoryBrowser برای فعال سازی مرور پوشه‌ها اضافه شده‌است.
UseDefaultFiles کار فعال سازی شناسایی فایل‌های پیش فرضی مانند index.html را در صورت ذکر نام ریشه‌ی یک پوشه، انجام می‌دهد.
اولین UseStaticFiles تعریف شده، تمام مسیرهای فیزیکی ذیل wwwroot را عمومی می‌کند.
دومین UseStaticFiles تعریف شده، پوشه‌ی MyStaticFiles واقع در خارج از wwwroot را عمومی می‌کند.
سومین UseStaticFiles تعریف شده، پوشه‌ی فیزیکی wwwroot\images را به مسیر درخواست‌های MyImages نگاشت می‌کند (http://localhost:7742/myimages) تا توسط DirectoryBrowser تعریف شده، قابل استفاده شود.
در آخر هم DirectoryBrowser تعریف شده‌است.


یک نکته‌ی امنیتی مهم
یک چنین قابلیتی (مرور فایل‌های درون یک پوشه) به صورت پیش فرض بر روی تمام IIS‌ها به دلایل امنیتی غیرفعال است. به همین جهت بهتر است Middleware فوق را هیچگاه استفاده نکنید و به این قسمت صرفا از دیدگاه اطلاعات عمومی نگاه کنید.


ساده سازی تعاریف توزیع فایل‌های استاتیک

Middleware دیگری به نام FileServer کار تعریف توزیع فایل‌های استاتیک را ساده می‌کند. اگر آن‌را تعریف کنید:
 app.UseFileServer();
اینکار به معنای تعریف یکباره‌ی UseStaticFiles و UseDefaultFiles، با ترتیب صحیح آن‌ها است.
اگر خواستید DirectoryBrowsing آن‌را نیز فعال کنید، پارامتر ورودی آن‌را به true مقدار دهی کنید (که به صورت پیش فرض غیرفعال است):
 app.UseFileServer(enableDirectoryBrowsing: true);
همچنین در اینجا می‌توانید مسیر پوشه‌ی MyStaticFiles خارج از wwwroot را نیز با مقدار دهی پارامتر FileServerOptions آن، مشخص کنید:
app.UseFileServer(new FileServerOptions
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")),
        RequestPath = new PathString("/StaticFiles"),
        EnableDirectoryBrowsing = false
    });


توزیع فایل‌های ناشناخته

اگر به سورس ASP.NET Core 1.0 دقت کنید، کلاسی را به نام FileExtensionContentTypeProvider خواهید یافت. این‌ها پسوندها و mime typeهای متناظری هستند که توسط ASP.NET Core شناخته شده و توزیع می‌شوند. برای مثال اگر فایلی را به نام test.xyz به پوشه‌ی wwwroot اضافه کنید، درخواست آن توسط کاربر، به Hello DNT ختم می‌شود؛ چون در این کلاس پایه، پسوند xyz تعریف نشده‌است.
برای رفع این مشکل و تکمیل این لیست می‌توان به نحو ذیل عمل کرد:
 // Set up custom content types -associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".xyz"] = "text/html";
 
app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider
}) ; // For the wwwroot folder
در اینجا ابتدا همان کلاس پایه FileExtensionContentTypeProvider را نمونه سازی می‌کنیم و سپس به دیکشنری آن، پسوند و mime type ویژه‌ی خود را اضافه می‌کنیم. سپس این provider را می‌توان به خاصیت ContentTypeProvider پارامتر StaticFileOptions آن نسبت داد. اکنون این فایل با پسوند xyz، قابل شناسایی می‌شود:


و یا اگر خواستید کمی تمیزتر کار کنید، بهتر است از کلاس پایه FileExtensionContentTypeProvider ارث بری کرده و سپس در سازنده‌ی این کلاس، خاصیت Mappings را ویرایش نمود:
public class XyzContentTypeProvider : FileExtensionContentTypeProvider
{
    public XyzContentTypeProvider()
    {
        this.Mappings.Add(".xyz", "text/html");
    }
}
و برای استفاده‌ی از آن خواهیم داشت:
app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = new XyzContentTypeProvider()
}) ; // For the wwwroot folder

روش دیگر مدیریت این مساله، تنظیم مقدار خاصیت ServeUnknownFileTypes به true است:
app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = true,
    DefaultContentType = "image/png"
});
در اینجا هر پسوند شناخته نشده‌ای با mime type تصویر png، توزیع خواهد شد. البته از لحاظ امنیتی توصیه شده‌است که چنین کاری را انجام ندهید و از این تنظیم عمومی نیز صرفنظر کنید.
مطالب
پیاده سازی یک تامین کننده MySQL برای ASP.NET Identity
در این مقاله جایگزینی پیاده سازی پیش فرض ASP.NET Identity را بررسی می‌کنیم. در ادامه خواهید خواند:

  • جزئیات نحوه پیاده سازی یک Storage Provider برای ASP.NET Identity
  • تشریح اینترفیس هایی که باید پیاده سازی شوند، و نحوه استفاده از آنها در ASP.NET Identity
  • ایجاد یک دیتابیس MySQL روی Windows Azure
  • نحوه استفاده از یک ابزار کلاینت (MySQL Workbench) برای مدیریت دیتابیس مذکور
  • نحوه جایگزینی پیاده سازی سفارشی با نسخه پیش فرض در یک اپلیکیشن ASP.NET MVC
در انتهای این مقاله یک اپلیکیشن ASP.NET MVC خواهیم داشت که از ASP.NET Identity و تامین کننده سفارشی جدید استفاده می‌کند. دیتابیس اپلیکیشن MySQL خواهد بود و روی Windows Azure میزبانی می‌شود. سورس کد کامل این مثال را هم می‌توانید از این لینک دریافت کنید.


پیاده سازی یک Storage Provider سفارشی برای ASP.NET Identity

ASP.NET Identity سیستم توسعه پذیری است که می‌توانید بخش‌های مختلف آن را جایگزین کنید.در این سیستم بناهای سطح بالایی مانند Managers و Stores وجود دارند.
Managers کلاس‌های سطح بالایی هستند که توسعه دهندگان از آنها برای اجرای عملیات مختلف روی ASP.NET Identity استفاده می‌کنند. مدیریت کننده‌های موجود عبارتند از UserManager و RoleManager. کلاس UserManager برای اجرای عملیات مختلف روی کاربران استفاده می‌شود، مثلا ایجاد کاربر جدید یا حذف آنها. کلاس RoleManager هم برای اجرای عملیات مختلف روی نقش‌ها استفاده می‌شود.

Stores کلاس‌های سطح پایین‌تری هستند که جزئیات پیاده سازی را در بر می‌گیرند، مثلا اینکه موجودیت‌های کاربران و نقش‌ها چگونه باید ذخیره و بازیابی شوند. این کلاس‌ها با مکانیزم ذخیره و بازیابی تلفیق شده اند. مثلا Microsoft.AspNet.Identity.EntityFramework کلاسی با نام UserStore دارد که برای ذخیره و بازیابی User‌ها و داده‌های مربوطه توسط EntityFramework استفاده می‌شود.

Managers از Stores تفکیک شده اند و هیچ وابستگی ای به یکدیگر ندارند. این تفکیک بدین منظور انجام شده که بتوانید مکانیزم ذخیره و بازیابی را جایگزین کنید، بدون اینکه اپلیکیشن شما از کار بیافتد یا نیاز به توسعه بیشتر داشته باشد. کلاس‌های Manager می‌توانند با هر Store ای ارتباط برقرار کنند. از آنجا که شما از API‌های سطح بالای UserManager برای انجام عملیات CRUD روی کاربران استفاده می‌کنید، اگر UserStore را با پیاده سازی دیگری جایگزین کنید، مثلا AzureTable Storage یا MySql، نیازی به بازنویسی اپلیکیشن نیست.

در مثال جاری پیاده سازی پیش فرض Entity Framework را با یک  تامین کننده MySQL جایگزین می‌کنیم.

پیاده سازی کلاس‌های Storage
برای پیاده سازی تامین کننده‌های سفارشی، باید کلاس هایی را پیاده سازی کنید که همتای آنها در Microsoft.AspNet.Identity.EntityFramework وجود دارند:
  • <UserStore<TUser
  • IdentityUser
  • <RoleStore<TRole
  • IdentityRole
پیاده سازی پیش فرض Entity Framework را در تصاویر زیر مشاهده می‌کنید.
Users

Roles

در مخزن پیش فرض ASP.NET Identity EntityFramework کلاس‌های بیشتری برای موجودیت‌ها مشاهده می‌کنید.

  • IdentityUserClaim
  • IdentityUserLogin
  • IdentityUserRole
همانطور که از نام این کلاس‌ها مشخص است، اختیارات، نقش‌ها و اطلاعات ورود کاربران توسط این کلاس‌ها معرفی می‌شوند. در مثال جاری این کلاس‌ها را پیاده سازی نخواهیم کرد، چرا که بارگذاری اینگونه رکوردها از دیتابیس به حافظه برای انجام عملیات پایه (مانند افزودن و حذف اختیارات کاربران) سنگین است. در عوض کلاس‌های backend store اینگونه عملیات را بصورت مستقیم روی دیتابیس اجرا خواهند کرد. بعنوان نمونه متد ()UserStore.GetClaimsAsync را در نظر بگیرید. این متد به نوبه خود متد (userClaimTable.FindByUserId(user.Id را فراخوانی می‌کند که یک کوئری روی جدول مربوطه اجرا می‌کند و لیستی از اختیارات کاربر را بر می‌گرداند.
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
{
    ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
    return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
}
برای پیاده سازی یک تامین کننده سفارشی MySQL مراحل زیر را دنبال کنید.
1. کلاس کاربر را ایجاد کنید، که اینترفیس IUser را پیاده سازی می‌کند.
public class IdentityUser : IUser
{
    public IdentityUser(){...}

    public IdentityUser(string userName) (){...}

    public string Id { get; set; }

    public string UserName { get; set; }

    public string PasswordHash { get; set; }

    public string SecurityStamp { get; set; }
}
2. کلاس User Store را ایجاد کنید، که اینترفیس‌های IUserStore, IUserClaimStore, IUserLoginStore, IUserRoleStore و IUserPasswordStore را پیاده سازی می‌کند. توجه کنید که تنها اینترفیس IUserStore را باید پیاده سازی کنید، مگر آنکه بخواهید از امکاناتی که دیگر اینترفیس‌ها ارائه می‌کنند هم استفاده کنید.
public class UserStore : IUserStore<IdentityUser>,
                         IUserClaimStore<IdentityUser>,
                         IUserLoginStore<IdentityUser>,
                         IUserRoleStore<IdentityUser>,
                         IUserPasswordStore<IdentityUser>
{
    public UserStore(){...}

    public Task CreateAsync(IdentityUser user){...}

    public Task<IdentityUser> FindByIdAsync(string userId){...}   
...
}
3. کلاس Role را ایجاد کنید که اینترفیس IRole را پیاده سازی می‌کند.
public class IdentityRole : IRole
{
    public IdentityRole(){...}

    public IdentityRole(string roleName) (){...}

    public string Id { get; set; }

    public string Name { get; set; }
}
4. کلاس Role Store را ایجاد کنید که اینترفیس IRoleStore را پیاده سازی می‌کند. توجه داشته باشید که پیاده سازی این مخزن اختیاری است و در صورتی لازم است که بخواهید از نقش‌ها در سیستم خود استفاده کنید.
public class RoleStore : IRoleStore<IdentityRole>                        
{
    public RoleStore(){...}

    public Task CreateAsync(IdentityRole role){...}

    public Task<IdentityRole> FindByIdAsync(string roleId){...}   
....
}
کلاس‌های بیشتری هم وجود دارند که مختص پیاده سازی مثال جاری هستند.
  • MySQLDatabase: این کلاس اتصال دیتابیس MySql و کوئری‌ها را کپسوله می‌کند. کلاس‌های UserStore و RoleStore توسط نمونه ای از این کلاس وهله سازی می‌شوند.
  • RoleTable: این کلاس جدول Roles و عملیات CRUD مربوط به آن را کپسوله می‌کند.
  • UserClaimsTable: این کلاس جدول UserClaims و عملیات CRUD مربوط به آن را کپسوله می‌کند.
  • UserLoginsTable: این کلاس جدول UserLogins و عملیات CRUD مربوط به آن را کپسوله می‌کند.
  • UserRolesTable: این کلاس جدول UserRoles و عملیات CRUD مربوطه به آن را کپسوله می‌کند.
  • UserTable: این کلاس جدول Users و عملیات CRUD مربوط به آن را کپسوله می‌کند.

ایجاد یک دیتابیس MySQL روی Windows Azure

1. به پورتال مدیریتی Windows Azure وارد شوید.
2. در پایین صفحه روی NEW+ کلیک کنید و گزینه STORE را انتخاب نمایید.

در ویزارد Choose Add-on به سمت پایین اسکرول کنید و گزینه ClearDB MySQL Database را انتخاب کنید. سپس به مرحله بعد بروید.

4. راهکار Free بصورت پیش فرض انتخاب شده، همین گزینه را انتخاب کنید و نام دیتابیس را به IdentityMySQLDatabase تغییر دهید. نزدیک‌ترین ناحیه (region) به خود را انتخاب کنید و به مرحله بعد بروید.

5. روی علامت checkmark کلیک کنید تا دیتابیس شما ایجاد شود. پس از آنکه دیتابیس شما ساخته شد می‌توانید از قسمت ADD-ONS آن را مدیریت کنید.

6. همانطور که در تصویر بالا می‌بینید، می‌توانید اطلاعات اتصال دیتابیس (connection info) را از پایین صفحه دریافت کنید.

7. اطلاعات اتصال را با کلیک کردن روی دکمه مجاور کپی کنید تا بعدا در اپلیکیشن MVC خود از آن استفاده کنیم.


ایجاد جداول ASP.NET Identity در یک دیتابیس MySQL

ابتدا ابزار MySQL Workbench را نصب کنید.
1. ابزار مذکور را از اینجا دانلود کنید.
2. هنگام نصب، گزینه Setup Type: Custom را انتخاب کنید.
3. در قسمت انتخاب قابلیت ها، گزینه‌های Applications و MySQLWorkbench را انتخاب کنید و مراحل نصب را به اتمام برسانید.
4. اپلیکیشن را اجرا کرده و روی MySQLConnection کلیک کنید تا رشته اتصال جدیدی تعریف کنید. رشته اتصالی که در مراحل قبل از Azure MySQL Database کپی کردید را اینجا استفاده کنید. بعنوان مثال:
 Connection Name: AzureDB; Host Name: us-cdbr-azure-west-b.cleardb.com; Username: <username>; Password: <password>; Default Schema: IdentityMySQLDatabase 
5. پس از برقراری ارتباط با دیتابیس، یک برگ Query جدید باز کنید. فرامین زیر را برای ایجاد جداول مورد نیاز کپی کنید.
CREATE TABLE `IdentityMySQLDatabase`.`users` (
  `Id` VARCHAR(45) NOT NULL,
  `UserName` VARCHAR(45) NULL,
  `PasswordHash` VARCHAR(100) NULL,
  `SecurityStamp` VARCHAR(45) NULL,
  PRIMARY KEY (`id`));

CREATE TABLE `IdentityMySQLDatabase`.`roles` (
  `Id` VARCHAR(45) NOT NULL,
  `Name` VARCHAR(45) NULL,
  PRIMARY KEY (`Id`));

CREATE TABLE `IdentityMySQLDatabase`.`userclaims` (
  `Id` INT NOT NULL AUTO_INCREMENT,
  `UserId` VARCHAR(45) NULL,
  `ClaimType` VARCHAR(100) NULL,
  `ClaimValue` VARCHAR(100) NULL,
  PRIMARY KEY (`Id`),
  FOREIGN KEY (`UserId`)
    REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade);

CREATE TABLE `IdentityMySQLDatabase`.`userlogins` (
  `UserId` VARCHAR(45) NOT NULL,
  `ProviderKey` VARCHAR(100) NULL,
  `LoginProvider` VARCHAR(100) NULL,
  FOREIGN KEY (`UserId`)
    REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade);

CREATE TABLE `IdentityMySQLDatabase`.`userroles` (
  `UserId` VARCHAR(45) NOT NULL,
  `RoleId` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`UserId`, `RoleId`),
  FOREIGN KEY (`UserId`)
    REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) 
on delete cascade
on update cascade,
  FOREIGN KEY (`RoleId`)
    REFERENCES `IdentityMySQLDatabase`.`roles` (`Id`)
on delete cascade
on update cascade);
6. حالا تمام جداول لازم برای ASP.NET Identity را در اختیار دارید، دیتابیس ما MySQL است و روی Windows Azure میزبانی شده.


ایجاد یک اپلیکیشن ASP.NET MVC و پیکربندی آن برای استفاده از MySQL Provider

2. در گوشه سمت راست پایین صفحه روی دکمه Download Zip کلیک کنید تا کل پروژه را دریافت کنید.
3. محتوای فایل دریافتی را در یک پوشه محلی استخراج کنید.
4. پروژه AspNet.Identity.MySQL را باز کرده و آن را کامپایل (build) کنید.
5. روی نام پروژه کلیک راست کنید و گزینه Add, New Project را انتخاب نمایید. پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را به IdentityMySQLDemo تغییر دهید.

6. در پنجره New ASP.NET Project قالب MVC را انتخاب کنید و تنظیمات پیش فرض را بپذیرید.

7. در پنجره Solution Explorer روی پروژه IdentityMySQLDemo کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.EntityFramework" را وارد کنید. در لیست نتایج این پکیج را انتخاب کرده و آن را حذف (Uninstall) کنید. پیغامی مبنی بر حذف وابستگی‌ها باید دریافت کنید که مربوط به پکیج EntityFramework است، گزینه Yes را انتخاب کنید. از آنجا که کاری با پیاده سازی فرض نخواهیم داشت، این پکیج‌ها را حذف می‌کنیم.

8. روی پروژه IdentityMySQLDemo کلیک راست کرده و Add, Reference, Solution, Projects را انتخاب کنید. در دیالوگ باز شده پروژه AspNet.Identity.MySQL را انتخاب کرده و OK کنید.

9. در پروژه IdentityMySQLDemo پوشه Models را پیدا کرده و کلاس IdentityModels.cs را حذف کنید.

10. در پروژه IdentityMySQLDemo تمام ارجاعات ";using Microsoft.AspNet.Identity.EntityFramework" را با ";using AspNet.Identity.MySQL" جایگزین کنید.

11. در پروژه IdentityMySQLDemo تمام ارجاعات به کلاس "ApplicationUser" را با "IdentityUser" جایگزین کنید.

12. کنترلر Account را باز کنید و متد سازنده آنرا مطابق لیست زیر تغییر دهید.

public AccountController() : this(new UserManager<IdentityUser>(new UserStore(new MySQLDatabase())))
{

}

13. فایل web.config را باز کنید و رشته اتصال DefaultConnection را مطابق لیست زیر تغییر دهید.

<add name="DefaultConnection" connectionString="Database=IdentityMySQLDatabase;Data Source=<DataSource>;User Id=<UserID>;Password=<Password>" providerName="MySql.Data.MySqlClient" />

مقادیر <DataSource>, <UserId> و <Password> را با اطلاعات دیتابیس خود جایگزین کنید.


اجرای اپلیکیشن و اتصال به دیتابیس MySQL

1. روی پروژه IdentityMySQLDemo کلیک راست کرده و Set as Startup Project را انتخاب کنید.
2. اپلیکیشن را با Ctrl + F5 کامپایل و اجرا کنید.
3. در بالای صفحه روی Register کلیک کنید.
4. حساب کاربری جدیدی بسازید.

5. در این مرحله کاربر جدید باید ایجاد شده و وارد سایت شود.

6. به ابزار MySQL Workbench بروید و محتوای جداول IdentityMySQLDatabase را بررسی کنید. جدول users را باز کنید و اطلاعات کاربر جدید را بررسی نمایید.

برای ساده نگاه داشتن این مقاله از بررسی تمام کدهای لازم خودداری شده، اما اگر مراحل را دنبال کنید و سورس کد نمونه را دریافت و بررسی کنید خواهید دید که پیاده سازی تامین کنندگان سفارشی برای ASP.NET Identity کار نسبتا ساده ای است.

نظرات مطالب
Bundling and Minifying Inline Css and Js
سلام ، مرسی از مطالب خوبتون
بحث Optimization در لینک زیر کاملتر بررسی شده، البته مطلب شما برایم تازگی داشت.
http://go.microsoft.com/fwlink/?LinkId=254725

نکاتی که میشه گفت
در وب فرم هم قابل استفاده است
برای single download کردن فایل‌های css و js دو روش وجود دارد:
1. تنظیم debug=false در بخش compilation در فایل Web.config
2. نوشتن کد زیر در کلاسی که باندل‌های خود را در bundleTable درج می‌کنید.
BundleTable.EnableOptimizations = true
برای نصب آن با NuGet Package، در کنسول عبارت زیر را وارد کنید
 install-package Microsoft.AspNet.Web.Optimization
که با نصب آن، 4 کتابخانه به reference‌های پروژه اضافه می‌شود.
تفاوتی که در script‌ها ایجاد می‌کند می‌توان به حذف کردن description ها، تغییر در variable‌ها ، و min کردن js‌های شما اشاره کرد.
موفق باشید. 
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
به روز رسانی: ارتقاء به نگارش «2.0.0rc.0 »

برای ارتقاء به نگارش RC0، این مراحل را باید طی کنید:
1) پیش از هر کاری، پوشه‌ی node_modules قدیمی خود را حذف کنید (با تمام محتوای آن).
2) به روز رسانی فایل package.json به صورت ذیل:
{
    "name": "asp-net-mvc5x-angular2x",
    "version": "1.0.0",
    "author": "DNT",
    "description": "",
    "scripts": {
        "postinstall": "typings install"
    },
    "license": "Apache-2.0",
    "dependencies": {
        "@angular/common": "^2.0.0-rc.0",
        "@angular/compiler": "^2.0.0-rc.0",
        "@angular/core": "^2.0.0-rc.0",
        "@angular/http": "2.0.0-rc.0",
        "@angular/router": "2.0.0-rc.0",
        "@angular/router-deprecated": "^2.0.0-rc.0",
        "@angular/platform-browser": "^2.0.0-rc.0",
        "@angular/platform-browser-dynamic": "^2.0.0-rc.0",
        "bootstrap": "^3.3.6",
        "es6-promise": "^3.1.2",
        "es6-shim": "^0.35.0",
        "jquery": "^2.2.3",
        "reflect-metadata": "^0.1.3",
        "rxjs": "^5.0.0-beta.6",
        "systemjs": "^0.19.27",
        "zone.js": "^0.6.12"
    },
    "devDependencies": {
        "typescript": "^1.8.9",
        "typings": "^0.8.1"
    },
    "repository": { }
}
به روز شده‌ی محتوای فوق، همیشه در آدرس مستندات npm packages موجود است.
3) افزودن فایلی به نام typings.json در ریشه‌ی پروژه؛ با این محتوا:
{
    "ambientDependencies": {
        "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd"
    }
}
این فایل توسط قسمت «postinstall» اسکریپت package.json نصب می‌شود. اما چون مسیر https://raw.githubusercontent.com قابل دسترسی نیست (از این طرف البته!)، موفق به دریافت آن نخواهید شد. بنابراین یک پوشه را به نام typings به ریشه‌ی سایت اضافه کنید و سپس فایل ذیل را به آن اضافه نمائید:
es6-shim.d.ts
بدون این فایل، کامپایلر TypeScript تعاریف ES 6 را مانند Map و Promise و امثال آن‌، نمی‌شناسد و پروژه را کامپایل نخواهد کرد.

اکنون یکبار فایل package.json را ذخیره کنید تا کار به روز رسانی بسته‌ها انجام شود. البته اگر بر روی این فایل کلیک راست کنید، در منوی ظاهر شده، گزینه‌ی restore packages هم موجود است.

4) پس از آن، چند تغییر جزئی ذیل باید در کدهای این سری، اعمال شوند:
هر جایی angular2 تعریف شده، اینبار می‌شود angular@. مثلا:
import { PipeTransform, Pipe } from '@angular/core';
فایل مسیریابی آن قرار است تغییرات کلی داشته باشد. این مورد به صورت ذیل تغییر نام یافته است:
import { RouteParams, Router } from '@angular/router-deprecated';
5) فایل main.ts (قسمت دوم) به صورت ذیل تغییر کرده‌است:
/// <reference path="../typings/es6-shim.d.ts" />
import { bootstrap } from '@angular/platform-browser-dynamic';

// Our main component
import { AppComponent } from "./app.component";

bootstrap(AppComponent);
6) تعاریف اسکریپت‌های Index.html سایت، اینبار به نحو ذیل تغییر کرده‌اند:
یک نکته: اگر می‌خواهید این تعاریف را در یک فایل razor، وارد کنید، چون @ به ابتدای پوشه‌ی angular2 اضافه شده (node_modules\@angular)، مشکل پردازشی razor را ایجاد خواهد کرد و باید escape شود. به همین جهت بجای @ بهتر است معادل آن را یعنی ("@")Html.Raw@   وارد کنید.
سپس ابتدا فایل systemjs.config.js را از اینجا دریافت کنید.
در ادامه مداخل جدید را هم در فایل index.html مثال رسمی آغازین آن بررسی کنید.

بنابراین، فایل systemjs.config.js را  به ریشه‌ی سایت اضافه کنید (از این جهت که مسیر بسته‌های node_modules را از ریشه‌ی سایت می‌خواند). سپس فایل Views\Shared\_Layout.cshtml را به نحو ذیل تغییر دهید:
<!DOCTYPE html>
<html>
<head>
    <base href="/">
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>

    <link href="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet"/>
    <link href="~/app/app.component.css" rel="stylesheet"/>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css"/>

    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="~/node_modules/es6-shim/es6-shim.min.js"></script>

    <script src="~/node_modules/zone.js/dist/zone.js"></script>
    <script src="~/node_modules/reflect-metadata/Reflect.js"></script>
    <script src="~/node_modules/systemjs/dist/system.src.js"></script>

    <script src="~/systemjs.config.js"></script>

    <!-- 2. Configure SystemJS -->
    <script>
        System.import('app/main').then(null, console.error.bind(console));
    </script>
</head>
<body>
    <div>
        @RenderBody()
        <pm-app>Loading App...</pm-app>
    </div>

    @RenderSection("Scripts", required: false)
</body>
</html>

خلاصه‌ی سریع این موارد
الف) تغییرات آخرین بسته‌های npm را از مستندات آن پیگیری و اعمال کنید. آخرین نگارش آن همیشه در اینجا قابل دسترسی است.
ب) تغییرات index.html، فایل main.ts و مداخل آغازین آن‌را از مثال آغازین رسمی آن پیگیری و اعمال کنید.
مطالب
فقط به خاطر یک نیم فاصله!
نسخه‌ی 64 بیتی ویندوز 7 را نصب کرده‌ام و اولین مشکل، نبود صفحه کلید مطابق استاندارد 2091 برای نسخه‌های 64 بیتی ویندوز است. پروژه وب فارسی دانشگاه صنعتی شریف، سال‌ها قبل فقط یک نسخه‌ی 32 بیتی از آن را تهیه کرده و نسخه‌های 64 بیتی موجود، مطابق استاندارد 9147 هستند و من به دلایل ذیل حاضر به استفاده از آن نیستم!
  1. در استاندارد 9147 ، جای ژ و پ مطابق صفحه کلیدی که در بازار ایران فروخته می‌شود نیست (و باید به همه پاسخگو باشید که چرا اینطوری است).
  2. ی و ک در آن اصلاح شده و دیگر عربی نیست که چقدر هم خوب، اما من تعداد زیادی برنامه و همچنین تعداد قابل توجهی فایل word دارم که مطابق استاندارد 2091 تهیه شده‌اند. مشکلات ی و ک فارسی و عربی را هم در اینجا ذکر کرده‌ام و به دنبال باگ‌های جدید در برنامه‌ها نمی‌گردم.
  3. با ی و ک فارسی اگر در گوگل جستجو کنید تعداد پاسخ‌های مرتبط یافت شده بسیار بسیار کمتر از حالتی خواهد بود که با ی و ک عربی جستجو کنید.
  4. در یک سازمان باید رویه‌ا‌ی واحد در این مورد برقرار باشد. یا همه باید از 2091 استفاده کنند و یا همه از 9147.

نسخه‌ی کامل استاندارد 9147 را از سایت آقای اخگری می‌توانید دریافت نمائید:

به همین دلایل خصوصا قسمت دوم (هر چند ممکن است با آن موافق نباشید)، نیاز به صفحه کلید مطابق استاندارد 2091 نسخه‌ی 64 بیتی داشتم.

برای تهیه صفحه کلید فارسی از برنامه Microsoft Keyboard Layout Creator استفاده می‌شود که نسخه‌ی 1.4 آن‌را از آدرس زیر می‌توانید دریافت نمائید. این نسخه قابلیت تولید فایل‌های dll مرتبط 64 بیتی را هم دارد:


یکی از قابلیت‌های این برنامه بارگذاری صفحه کلید فارسی جاری سیستم است:



صفحه کلید استاندارد 2091 پروژه وب فارسی را اگر با آن باز کرده و سپس از منوی Project گزینه‌ی Build DLL and setup package را انتخاب نمائید، با خطای زیر متوقف خواهید شد:
ERROR: 'VK_SPACE' in Shift State 'Shift' must be made up of white space character(s), but is defined as '‌' (U+200c) instead.
که دقیقا مربوطه به این سطر در تعاریف صفحه کلید است (صفحه کلید را می‌شود به صورت فایلی با پسوند klc هم save as کرد):
39 SPACE  0 0020 200c 0020 -1  // SPACE, ZERO WIDTH NON-JOINER, SPACE, <none>
مفاهیم این ستون‌ها هم به شرح زیر هستند:
0 //Column 4
1 //Column 5 : Shft
2 //Column 6 :       Ctrl
3 //Column 7 : Shft  Ctrl
یعنی جهت نمایش نیم فاصله حاصل از ترکیب shift space مطابق استاندارد تایپ ایران، بجای 0x0020 یا همان فاصله، از 0x200C استفاده شده است (مطابق استاندارد تایپ ایران باید نوشت "می‌روم" و نه "می روم". به نیم فاصله و فاصله دقت بفرمائید).
اما این برنامه‌ی محترم دقیقا همین مورد را غلط گرفته و فایل dll نهایی را تولید نمی‌کند (مطابق خطایی که ذکر شد).
برنامه‌ی Microsoft Keyboard Layout Creator هم با دات نت فریم ورک نوشته شده است. اگر کمی با reflector به آنالیز آن بپردازیم به کلاس Accept و متد زیر خواهیم رسید:
private bool VerifySpaceBarIntegrity(ShiftState ss, bool fMustBeDefined)
در این متد جایی که خطای ذکر شده صادر می‌شود، مربوط به چند سطر زیر است:
if (!Utilities.IsSpacing(ss.Characters))
{
    this.WriteErrorToLogFile("SpaceKeyNeedsWhiteSpaceCharacters", new string[] { "VK_" + Utilities.VkStringOfIvk(ss.VK), this.m_stStateLabel[ss.State], ss.Characters, Utilities.FromCharacterToUPlusForm(ss.Characters) });
    flag = false; /*اینجا باید اصلاح شود!*/
}
خوب! به نظر شما ایرادی دارد که این flag همیشه true باشد؟! برای همین منظور، فایل MSKLC.exe را پچ کرده‌ام که از آدرس زیر قابل دریافت است:

  دریافت پچ شده نگارش 1.4.6000.2 : MSKLC.Patched.7z  

با استفاده از این نسخه (ابتدا برنامه اصلی را نصب کرده و سپس فایل exe را جایگزین نمائید)، به راحتی می‌توان نسخه‌ی استاندارد و اصلی 2091 را بارگذاری و مجددا کامپایل کرد (بدون توقف کامپایل به خاطر فقط یک نیم فاصله‌ی غیرمعتبر از دیدگاه نویسندگان اصلی برنامه). به این صورت فایل‌های 64 بیتی لازم هم تولید می‌شوند ( ممکن است در حین کار با برنامه‌ی patch شده، نمایش خطای کار با نیم فاصله را مجددا دریافت کنید؛ اما مهم نیست . نمونه‌ی وصله شده، بدون مشکل حاصل را کامپایل می‌کند که در نمونه‌ی اصلی اینطور نیست. یعنی پس از overwrite کردن فایل exe موجود، با نمونه patch شده، برنامه کار کامپایل را کامل می‌کند) که حاصل نهایی را از آدرس زیر می‌توانید دریافت نمائید:


این فایل‌ها دقیقا بر مبنای همان فایل‌های پروژه وب فارسی استاندارد 2091 هستند؛ با این تفاوت که نسخه‌‌های غیر 32 بیتی هم در آن لحاظ شده‌اند.

نصب این dll‌ها هم از ویندوز ویستا به بعد داستان خودش را پیدا کرده؛ ابتدا باید take ownership شود، سپس می‌توان فایل اصلی را به سادگی جایگزین کرد و سپس reboot .
برای این منظور ابتدا به پوشه‌ی system32 ویندوز مراجعه کرده و فایل KBDFA.DLL را پیدا کنید.
در ادامه به پنجره خواص آن (کلیک راست و انتخاب properties) مراجعه و برگه‌ی Security آن‌را انتخاب کنید.
در این قسمت بر روی دکمه‌ی Advanced کلیک نمائید.
در صفحه‌ی باز شده به برگه‌ی Owner مراجعه کنید.
در این قسمت بر روی دکمه‌ی Edit کلیک نموده و کاربر خودتان را اضافه نمائید.
پس از طی این مرحله (یا همان take ownership) به برگه security مراجعه کرده و به کاربر خودتان دسترسی full control بدهید.
اکنون مجوز تغییر این فایل را بدون هیچ مشکلی خواهید یافت.
در پایان reboot را فراموش نکنید.

راه دوم:
یا برنامه نصاب حاصل از برنامه‌ی Microsoft Keyboard Layout Creator، مدخل جدیدی را به صفحه کلیدهای قابل انتخاب ویندوز در کنترل پنل اضافه می‌کند که نیازی به reboot ندارد.
بازخوردهای دوره
معرفی پروژه NotifyPropertyWeaver
- فید NuGet در VS.NET به Https تنظیم شده است. اگر دسترسی به Https برای شما به کندی صورت می‌گیرد فقط کافی است مسیر فید آن‌را در منوی Tools، گزینه‌ی Options، ذیل قسمت Package manager یافته و به http://nuget.org/api/v2 تغییر دهید؛ یعنی به Http خالی، بجای Https؛ تا سرعت دریافت بسته‌های NuGet مورد نظر افزایش یابند.
- این بسته از طریق آدرس ذیل نیز قابل دریافت است:
https://az320820.vo.msecnd.net/packages/propertychanged.fody.1.42.0.nupkg 
همین آدرس را در IE‌ وارد کنید. اگر کار نکرد احتمالا تنظیمات IE شما به هم ریخته است؛ چون تنظیمات آن به صورت مستقیم روی تنظیمات اتصالی برنامه‌های دات نت تاثیر دارند.
مطالب
آموزش WAF (بررسی ساختار همراه با پیاده سازی یک مثال)
در این پست با مفاهیم اولیه این کتابخانه آشنا شدید. برای بررسی و پیاده سازی مثال، ابتدا یک Blank Solution را ایجاد نمایید. فرض کنید قصد پیاده سازی یک پروژه بزرگ ماژولار را داریم. برای این کار لازم است مراحل زیر را برای طراحی ساختار مناسب پروژه دنبال نمایید.
نکته: آشنایی اولیه با مفاهیم MEF از ملزومات این بخش است.
»ابتدا یک Class Library به نام Views ایجاد نمایید و اینترفیس زیر را به صورت زیر در آن تعریف نمایید. این اینترفیس رابط بین کنترلر و View از طریق ViewModel خواهد بود.
 public interface IBookView : IView
    {
        void Show();
        void Close();
    }
اینترفیس IView در مسیر System.Waf.Applications قرار دارد. در نتیجه از طریق nuget اقدام به نصب Package  زیر نمایید:
Install-Package WAF
»حال در Solution ساخته شده  یک پروژه از نوع WPF Application به نام Shell ایجاد کنید. با استفاده از نیوگت، Waf Package را نصب نمایید؛ سپس ارجاعی از اسمبلی Views را به آن ایجاد کنید. output type اسمبلی Shell را به نوع ClassLibrary تغییر داده، همچنین فایل‌های موجود در آن را حذف نمایید. یک فایل Xaml جدید را به نام BookShell ایجاد نمایید و کد‌های زیر را در آن کپی نمایید:
<Window x:Class="Shell.BookShell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Book View" Height="350" Width="525">
    <Grid>
        <DataGrid ItemsSource="{Binding Books}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="400" Height="200">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Code" Binding="{Binding Code}" Width="100"></DataGridTextColumn>
                <DataGridTextColumn Header="Title" Binding="{Binding Title}" Width="300"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>

    </Grid>
</Window>
این فرم فقط شامل یک دیتاگرید برای نمایش اطلاعات کتاب‌هاست. دیتای آن از طریق ViewModel تامین خواهد شد، در نتیجه ItemsSource آن به خاصیتی به نام Books بایند شده است. حال ارجاعی به اسمبلی System.ComponentModel.Composition دهید. سپس در Code behind این فرم کد‌های زیر را کپی کنید:
[Export(typeof(IBookView))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public partial class BookShell : Window, IBookView
    {
        public BookShell()
        {
            InitializeComponent();
        }
    }
کاملا واضح است که این فرم اینترفیس IBookView را پیاده سازی کرده است. از آنجاکه کلاس Window به صورت پیش فرض دارای متد‌های Show و Close است در نتیجه نیازی به پیاده سازی مجدد متدهای IBookView نیست. دستور Export باعث می‌شود که این کلاس به عنوان وابستگی به Composition Container اضافه شود تا در جای مناسب بتوان از آن وهله سازی کرد. نکته‌ی مهم این است که به دلیل آنکه این کلاس، اینترفیس IBookView را پیاده سازی کرده است در نتیجه نوع Export این کلاس حتما باید به صورت صریح از نوع IBookView باشد.

»یک Class Library به نام Models بسازید و بعد از ایجاد آن، کلاس زیر را به عنوان مدل Book در آن کپی کنید:
 public class Book
    {
        public int Code { get; set; }

        public string Title { get; set; }
    }
»یک  Class Library دیگر به نام ViewModels ایجاد کنید و همانند مراحل قبلی، Package مربوط به WAF را نصب کنید. سپس کلاسی به نام BookViewModel ایجاد نمایید و کدهای زیر را در آن کپی کنید (ارجاع به اسمبلی‌های Views و Models را فراموش نکنید):
[Export]
    [Export(typeof(ViewModel<IBookView>))]
    public class BookViewModel : ViewModel<IBookView>
    {
        [ImportingConstructor]
        public BookViewModel(IBookView view)
            : base(view)
        {
        }
       
        public ObservableCollection<Book> Books { get; set; }
        
    }
ViewModel مورد نظر از کلاس ViewModel of T ارث برده است. نوع این کلاس معادل نوع View مورد نظر ماست که در اینجا مقصود IBookView است. این کلاس شامل خاصیتی به نام ViewCore است که امکان فراخوانی متد‌ها و خاصیت‌های View را فراهم می‌نماید. وظیفه اصلی کلاس پایه ViewModel، وهله سازی از View سپس ست کردن خاصیت DataContext در View مورد نظر به نمونه وهله سازی شده از ViewModel است. در نتیجه عملیات مقید سازی در Shell به درستی انجام خواهدشد.
به دلیل اینکه سازنده پیش فرض در  این کلاس وجود ندارد حتما باید از ImportingConstructor استفاده نماییم تا CompositionContainer در هنگام عملیات وهله سازی Exception صادر نکند.

»بخش بعدی ساخت یک Class Library دیگر به نام Controllers است. در این Library نیز بعد از ارجاع به اسمبلی‌های زیر کتابخانه WAF را نصب نمایید. 
  • Views
  • Models
  • ViewModels
  • System.ComponentModel.Composition
کلاسی به نام BookController بسازید و کد‌های زیر را در آن کپی نمایید:
[Export]
    public class BookController
    {
        [ImportingConstructor]
        public BookController(BookViewModel viewModel)
        {
            ViewModelCore = viewModel;
        }

        public BookViewModel ViewModelCore
        {
            get;
            private set;
        }

        public void Run()
        {
            var result = new List<Book>();
            result.Add(new Book { Code = 1, Title = "Book1" });
            result.Add(new Book { Code = 2, Title = "Book2" });
            result.Add(new Book { Code = 3, Title = "Book3" });

            ViewModelCore.Books = new ObservableCollection<Models.Book>(result);

            (ViewModelCore.View as IBookView).Show();
        }
    }
نکته مهم این کلاس این است که BookViewModel به عنوان وابستگی این کنترلر تعریف شده است. در نتیجه در هنگام وهله سازی از این کنترلر Container مورد نظر یک وهله از BookViewModel را در اختیار آن قرار خواهد داد. در متد Run نیز ابتدا مقدار Book که به ItemsSource دیتا گرید در BookShell مقید شده است مقدار خواهد گرفت. سپس با فراخوانی متد Show از اینترفیس IBookView، متد Show در BookShell فراخوانی خواهد شد که نتیجه آن نمایش فرم مورد نظر است.

طراحی Bootstrapper

در پروژه‌های ماژولار  Bootstrapper از ملزومات جدانشدنی این گونه پروژه هاست. برای این کار ابتدا یک WPF Application دیگر به نام Bootstrapper ایجاد نماید. سپس ارجاعی به اسمبلی‌های زیر را در آن قرار دهید:
»Controllers
»Views
»ViewModels
»Shell
»System.ComponentModel.Composition
»نصب بسته WAF با استفاده از nuget

حال یک کلاس به نام  AppBootstrapper ایجاد نمایید و کد‌های زیر را در آن کپی نمایید:
public class AppBootstrapper
    {
        public CompositionContainer Container
        {
            get;
            private set;
        }

        public AggregateCatalog Catalog
        {
            get;
            private set;
        }

        public void Run()
        {
            Catalog = new AggregateCatalog();
            Catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            
            Catalog.Catalogs.Add(new AssemblyCatalog(String.Format("{0}\\{1}", Environment.CurrentDirectory, "Shell.dll")));
            Catalog.Catalogs.Add(new AssemblyCatalog(String.Format("{0}\\{1}", Environment.CurrentDirectory, "ViewModels.dll")));
            Catalog.Catalogs.Add(new AssemblyCatalog(String.Format("{0}\\{1}", Environment.CurrentDirectory, "Controllers.dll")));

            Container = new CompositionContainer(Catalog);

            var batch = new CompositionBatch();
            batch.AddExportedValue(Container);
            Container.Compose(batch);

            var bookController = Container.GetExportedValue<BookController>();
            bookController.Run();


        }
    }
اگر با MEF آشنا باشید کد‌های بالا نیز برای شما مفهوم مشخصی دارند. در متد Run این کلاس ابتدا Catalog ساخته می‌شود. سپس با اسکن اسمبلی‌های مورد نظر تمام Export‌ها و Import‌های لازم واکشی شده و به Conrtainer مورد نظر رجیستر می‌شوند. در انتها نیز با وهله سازی از BookController و فراخوانی متد Run آن خروجی زیر نمایان خواهد شد.

نکته بخش Startup  را از فایل App.Xaml خذف نمایید و در متد Startup این فایل کد زیر را کپی کنید:

public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            new Bootstrapper.AppBootstrapper().Run();
        }
    }
در پایان، ساختار پروژه به صورت زیر خواهد شد:

نکته: می‌توان بخش اسکن اسمبلی‌ها را توسط یک DirecotryCatalog به صورت زیر خلاصه کرد:
Catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory));
در این صورت تمام اسمبلی‌های موجود در این مسیر اسکن خواهند شد.
نکته: می‌توان به جای جداسازی فیزیکی لایه‌ها آن‌ها را از طریق Directory‌ها به صورت منطقی در قالب یک اسمبلی نیز مدیریت کرد.
نکته: بهتر است به جای رفرنس مستقیم اسمبلی‌ها به Bootstrapper با استفاده از Pre post build در قسمت  Build Event، اسمبلی‌های مورد نظر را در یک مسیر Build کپی نمایید که روش آن به تفصیل در این پست و این پست شرح داده شده است.
دانلود سورس پروژه
مطالب
نکته‌ای تکمیلی در مورد مجوز استفاده از iTextSharp

یکی از سؤ برداشت‌های متداول از کارهای سورس باز موجود این است:
«من مجازم از این کتابخانه‌ی سورس باز هرجایی و هر طوری که دوست دارم استفاده کنم.»

در کل این یک «توهم» بزرگ است. بسته به مجوز پروژه (^)، جمله‌ی فوق می‌تواند صحیح یا کاملا نادرست باشد.
برای نمونه من خیلی‌ها رو می‌بینم که می‌گن: «از MySQL استفاده کن که رایگانه». نه دوست عزیز؛ اشتباه می‌کنید! فقط برای کارهای سورس باز رایگان است. مجوز نگارش Community و رایگان آن در رده‌ی مجوز‌های GPL است (^). به این معنا که اگر روزی مطابق قوانین کپی رایت قرار شد رفتار شود، به سراغ کار سورس بسته شما که دارد از MySQL رایگان استفاده می‌کند، خواهند آمد. جهت اطلاع!
به همین جهت کسانی که کار تجاری سورس بسته انجام می‌دهند از طرف کتابخانه‌های دارای مجوز GPL حتی رد هم نمی‌شوند؛ چه برسد به اینکه بخواهند آزادانه از آن استفاده کنند.

در مورد مجوز کتابخانه‌ی iTextSharp پیشتر مطلبی را در این سایت خوانده‌اید:
مجوز این کتابخانه، GNU Affero General Public License است. به این معنا که شما موظفید، تغییری در قسمت تهیه کننده خواص فایل PDF تولیدی که به صورت خودکار به نام کتابخانه تنظیم می‌شود، ندهید. اگر می‌خواهید این قسمت را تغییر دهید باید هزینه کنید. همچنین با توجه به اینکه این مجوز، GPL است یعنی زمانیکه از آن استفاده کردید باید کار خود را به صورت سورس باز ارائه دهید (^).

و ... نکته تکمیلی مهم اینکه:
این کتابخانه تا نگارش 4.1.7 تحت مجوز MPL/LGPL ارائه شده و «بدون مشکل» در کارهای تجاری سورس بسته قابل استفاده است. از نگارش 5 به بعد، AGPL شده و برای کارهای تجاری سورس بسته «رایگان نیست» (^).
برای نمونه سورس نسخه 4.1.7 از این آدرس قابل دریافت است.
این سورس را از پروژه "FDFToolkit .NET" اینجا نقل کردم چون تهیه کننده این پروژه دقیقا به این مطلب اشاره کرده و کار خود را به نگارش 4.1.7 کتابخانه iTextSharp عمدا محدود کرده است.

نظرات اشتراک‌ها
ایجاد، اتصال و انتشار مخزن کد در Visual Studio 2012 با استفاده از Git
ممنون بابت معرفی، برای استفاده از قابلیت باید آپدیت  Visual Studio 2012 Update 2 CTP 2   به حجم یک مگ رو نصب کنید و بعد افزونه Microsoft.TeamFoundation.Git.Provider  که کمتر از دو مگ حجم دارد رو نصب کنید ، راحتتر از اون چیزی که فکر می‌کنید یک سورس کنترل حرفه ای در دسترس شما قرار می‌گیره .