مطالب
آشنایی با LibMan در پروژه‌های ASP.NET Core
مدیریت پکیج‌های سمت کلاینت
پروژه‌های جدیدی که با استفاده از قالب پیش‌فرض ایجاد می‌شوند، شامل یک پوشه با نام lib، در شاخه‌ی wwwroot هستند که حاوی این فایل‌ها می‌باشند:


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

  • دانلود مستقیم فایل موردنیاز و جایگزین کردن آن با نسخه‌ی فعلی
  • حذف پوشه‌ی wwwroot/lib و نصب مجدد پوشه‌ها از طریق NPM یا Yarn
  • استفاده از bower و سفارشی‌سازی مسیر قرار گرفتن فایل‌ها توسط browerrc.

روش اول، به نسبت ساده‌تر است؛ اما مشکل اینجاست که همه چیز به صورت دستی انجام خواهد گرفت. یعنی برای نسخه‌های بعدی همین روال باید مجدداً انجام شود. در روش دوم نیز مشکل اینجاست که مسیر قرار گرفتن پکیج‌های پروژه، از این به بعد node_modules خواهند بود و نیاز به یک تنظیم اضافی در فایل Startup.cs و مطلع کردن ASP.NET Core برای serve کردن فایل‌های سمت کلاینت است همچنین نیاز خواهد بود که تمامی ارجاعات داخل فایل Layout.cshtmlـ نیز از lib به node_modules تغییر پیدا کنند. در نهایت روش آخر نیز به دلیل منسوخ شدن bower پیشنهاد نمی‌شود.

استفاده از LibMan 
LibMan یک قابلیت جدید است که از نسخه‌ی Visual Studio 2017 برای مدیریت پکیج‌های سمت کلاینت اضافه شده است. این ابزار به صورت cross platform تهیه شده است؛ یعنی در خارج از Visual Studio نیز قابل استفاده می‌باشد. در واقع این ابزار را می‌توانیم به صورت Globally روی سیستم خود نصب کنیم و از طریق خط فرمان همانند NPM از آن استفاده کنیم.  

نصب و استفاده از LibMan
این ابزار ابتدا توسط Mads Kristensen با عنوان Client-Side Library Installer به صورت یک افزونه برای Visual Studio 2015 توسعه داده شد که در نهایت تیم ASP.NET این ابزار را  به صورت توکار به Visual Studio 2017 اضافه کرد. در نتیجه اگر از نسخه‌ی 2017 ویژوال استودیو استفاده می‌کنید، برای استفاده از این ابزار نیاز به نصب پکیج خاصی نخواهید داشت. برای استفاده از این ابزار در سیستم‌عامل‌های به غیر از ویندوز نیز یک CLI تهیه شده است که از طریق خط فرمان قابل استفاده می‌باشد. برای نصب این ابزار کافی است ابتدا آن را به صورت سراسری نصب کنید:
dotnet tool install -g Microsoft.Web.LibraryManager.Cli

قدم بعدی اضافه کردن فایلی با عنوان libman.json درون پروژه است. برای افزودن این فایل کافی است دستور زیر را در خطر فرمان وارد کنید:
libman init

با صدور فرمان فوق فایل مربوطه درون پروژه ایجاد می‌شود که در واقع یک فایل json ساده برای مدیریت پکیج‌های سمت کلاینت است:
{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": []
}

همانطور که مشاهده می‌کنید، می‌توانیم منبع دریافت پکیج‌‌ها را نیز تعیین کنیم که در حالت پیش‌فرض بر روی cdnjs تنظیم شده‌است و در واقع یک Content Delivery Network برای تعداد بیشماری پکیج سمت کلاینت است. درون آرایه‌ی libraries نیز می‌توانیم پکیج‌های موردنیاز خود را وارد کنیم. حتی برای هر پکیج هم می‌توانیم provider را override نمائیم. فرض کنید می‌خواهیم کتابخانه‌ی bootstrap را به پروژه اضافه کنیم. در اینحالت کافی است دستور زیر را برای نصب پکیج مربوطه صادر نمائیم:
libman install bootstrap@4.3.1 --provider unpkg --destination wwwroot/lib/bootstrap

در نهایت فایل libman.json چنین ساختاری پیدا خواهد کرد:
{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "provider": "unpkg",
      "library": "bootstrap@4.3.1",
      "destination": "wwwroot/lib/bootstrap"
    }
  ]
}

برای نصب مجدد پکیج هم می‌توانید از دستور libman restore استفاده کنید؛ در این حالت دقیقا مانند Nuget، پکیج‌ها از روی فایل libman.json از provider مربوطه دانلود و به پروژه اضافه خواهند شد. در ادامه لیست دستورات موجود را مشاهده می‌کنید:

مطالب
فشرده سازی اطلاعات توسط الگوریتم Zstandard فیسبوک
Zstandard یا به اختصار Zstd، یک الگوریتم فشرده سازی سریع و بدون افت کیفیت است که از سرعت و قدرت خوبی برخوردار است. این الگوریتم توسط Facebook توسعه داده شده و استفاده می‌شود.
الگوریتم‌های دیگری مانند  Snappy (الگوریتم فشرده سازی گوگل)،  LZ4 (جز سریع‌ترین الگوریتم ها) و LZMA (جز قوی‌ترین الگوریتم‌ها) نیز وجود دارند ولی Zstd تعادل (trade-off) خوبی بین سرعت و میزان فشرده سازی را فراهم می‌کند.
در این مقاله برای فشرده سازی اطلاعات با الگوریتم Zstd، از کتابخانه دات نتی  ZstdNet استفاده خواهیم کرد. علاوه بر این، کتابخانه دیگری نیز به نام  Zstandard.Net  با پشتیبانی از Streaming نیز وجود دارد.
ابتدا لازم است کتابخانه مذکور از Nuget نصب شود.
Install-Package ZstdNet
این کتابخانه شامل کلاسی به نام Compessor برای فشرده سازی است که متد Wrap آن آرایه‌ای byte‌ها را دریافت کرده و پس از فشرده سازی، آرایه‌ای از byte‌ها را باز می‌گرداند. به عنوان مثال :
byte[] sourceData = GetData(); //for example : File.ReadAllBytes(filePath);
byte[] compressedData;
using (var compressor = new Compressor())
{
    compressedData = compressor.Wrap(sourceData);
}  
برای تنظیمات فشرده سازی، از جمله میزان فشرده سازی می‌توان وهله‌ای از CompressionOptions را به سازنده کلاس Compressor ارسال کرد.
کلاس  CompressionOptions در سازنده خود، پارامتری را به نام  compressionLevel از نوع int، دریافت می‌کند که میزان فشرده سازی را مشخص می‌کند. مقدار پیش‌فرض آن 3 است و می‌تواند بین 1 تا 22 ( MaxCompressionLevel) باشد. افزایش این مقدار به معنی افزایش میزان فشرده سازی و طبیعتا افزایش زمان فشرده سازی است. 
var compressor = new Compressor(new CompressionOptions(compressionLevel: 5))
برای خارج کردن از حالت فشرده نیز از کلاس Decompressor و متد Unwrap آن استفاده می‌کنیم که آرایه بایتی فشرده شده را دریافت و پس از Decompress کردن، آرایه بایتی اصلی آن (حالت عادی و غیر فشرده شده) را باز می‌گرداند.
using (var decompressor = new Decompressor())
{
    byte[] sourceData = decompressor.Unwrap(compressedData);
}  
با استفاده از روش فوق می‌توانید قبل از ذخیره سازی فایل‌هایتان آنها را فشرده سازی کنید؛ مخصوصا اگر تصمیم به ذخیره سازی درون دیتابیس را دارید، این امکان می‌تواند به کاهش حجم دیتابیس شما کمک کند.
برای فشرده سازی و ذخیره فایل روی هارد نیز می‌توانید از کلاس کمکی زیر استفاده کنید.
public static class ZstdCompression
{
    public static (decimal savedPercent, long savedSizeKB) Compress(string filePath, string savePath, int compressionLevel = 5)
    {
        var sourceData = File.ReadAllBytes(filePath);
        byte[] compressedData;

        using (var compressor = new Compressor(new CompressionOptions(compressionLevel)))
            compressedData = compressor.Wrap(sourceData);

        File.WriteAllBytes(savePath, compressedData);

        var diff = sourceData.LongLength - compressedData.LongLength;
        var savedpercent = (decimal)diff * 100 / sourceData.LongLength;
        var savedSizeKB = diff / 1024;

        return (savedpercent, savedSizeKB);
    }

    public static void Decompress(string filePath, string savePath)
    {
        var compressedData = File.ReadAllBytes(filePath);
        byte[] decompressedData;

        using (var decompressor = new Decompressor())
            decompressedData = decompressor.Unwrap(compressedData);

        File.WriteAllBytes(savePath, decompressedData);
    }
}  
و برای استفاده :
var compressInfo = ZstdCompression.Compress(@"c:\video.mp4", @"c:\compressedFile");
Console.Write($"Saved : {compressInfo.savedPercent}% ({compressInfo.savedSizeKB} KB)");
//Output => Saved : 57% (7530 KB)

مطالب
راهنمای تغییر بخش احراز هویت و اعتبارسنجی کاربران سیستم مدیریت محتوای IRIS به ASP.NET Identity – بخش دوم
در بخش اول، کارهایی که انجام دادیم به طور خلاصه عبارت بودند از:
1-  حذف کاربرانی که نام کاربری و ایمیل تکراری داشتند
2-  تغییر نام فیلد Password به PasswordHash در جدول User
 
سیستم مدیریت محتوای IRIS، برای استفاده از Entity Framework، از الگوی واحد کار (Unit Of Work) و تزریق وابستگی استفاده کرده است و اگر با نحوه‌ی پیاده سازی این الگو‌ها آشنا نیستید، خواندن مقاله EF Code First #12  را به شما توصیه می‌کنم.
برای استفاده از ASP.NET Identity نیز باید از الگوی واحد کار استفاده کرد و برای این کار، ما از مقاله اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity استفاده خواهیم کرد.
نکته مهم: در ادامه اساس کار ما بر پایه‌ی مقاله اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity است و چیزی که بیشتر برای ما اهمیت دارد کدهای نهایی آن هست؛ پس حتما به مخزن کد آن مراجعه کرده و کدهای آن را دریافت کنید.
 
تغییر نام کلاس User به ApplicationUser

اگر به کدهای مثال رسمی ASP.NET Identity نگاهی بیندازید، می‌بینید که کلاس مربوط به جدول کاربران ApplicationUser نام دارد، ولی در سیستم IRIS نام آن User است. بهتر است که ما هم نام کلاس خود را از User به ApplicationUser تغییر دهیم چرا که مزایای زیر را به دنبال دارد:

1- به راحتی می‌توان کدهای مورد نیاز را از مثال Identity کپی کرد.
2- در سیستم Iris، بین کلاس User متعلق به پروژه خودمان و User مربوط به HttpContext تداخل رخ می‌داد که با تغییر نام کلاس User دیگر این مشکل را نخواهیم داشت.
 
برای این کار وارد پروژه Iris.DomainClasses شده و نام کلاس User را به ApplicationUser تغییر دهید. دقت کنید که این تغییر نام را از طریق Solution Explorer انجام دهید و نه از طریق کدهای آن. پس از این تغییر ویژوال استودیو می‌پرسد که آیا نام این کلاس را هم در کل پروژه تغییر دهد که شما آن را تایید کنید.

برای آن که نام جدول Users در دیتابیس تغییری نکند، وارد پوشه‌ی Entity Configuration شده و کلاس UserConfig را گشوده و در سازنده‌ی آن کد زیر را اضافه کنید:
ToTable("Users");

نصب ASP.NET Identity

برای نصب ASP.NET Identity دستور زیر را در کنسول Nuget وارد کنید:
Get-Project Iris.DomainClasses, Iris.Datalayer, Iris.Servicelayer, Iris.Web | Install-Package Microsoft.AspNet.Identity.EntityFramework
از پروژه AspNetIdentityDependencyInjectionSample.DomainClasses کلاس‌های CustomUserRole، CustomUserLogin، CustomUserClaim و CustomRole را به پروژه Iris.DomainClasses منتقل کنید. تنها تغییری که در این کلاس‌ها باید انجام دهید، اصلاح namespace آنهاست.
همچنین بهتر است که به کلاس CustomRole، یک property به نام Description اضافه کنید تا توضیحات فارسی نقش مورد نظر را هم بتوان ذخیره کرد:

 
    public class CustomRole : IdentityRole<int, CustomUserRole>
    {
        public CustomRole() { }
        public CustomRole(string name) { Name = name; }

        public string Description { get; set; }

    }

نکته: پیشنهاد می‌کنم که اگر می‌خواهید مثلا نام CustomRole را به IrisRole تغییر دهید، این کار را از طریق find and replace انجام ندهید. با همین نام‌های پیش فرض کار را تکمیل کنید و سپس از طریق خود ویژوال استودیو نام کلاس را تغییر دهید تا ویژوال استودیو به نحو بهتری این نام‌ها را در سرتاسر پروژه تغییر دهد.
 
سپس کلاس ApplicationUser پروژه IRIS را باز کرده و تعریف آن را به شکل زیر تغییر دهید:
public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>

اکنون می‌توانید property‌های Id،  UserName، PasswordHash و Email را حذف کنید؛ چرا که در کلاس پایه IdentityUser تعریف شده اند.
 
تغییرات DataLayer

وارد Iris.DataLayer شده و کلاس IrisDbContext را به شکل زیر ویرایش کنید:
public class IrisDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>,
        IUnitOfWork

اکنون می‌توانید property زیر را نیز حذف کنید چرا که در کلاس پایه تعریف شده است: 
 public DbSet<ApplicationUser> Users { get; set; }

 

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

        public IrisDbContext()
            : base("IrisDbContext")
        {
        }

همچنین درون متد OnModelCreating کدهای زیر را پس از فراخوانی متد (base.OnModelCreating(modelBuilder  جهت تعیین نام جداول دیتابیس بنویسید:
            modelBuilder.Entity<CustomRole>().ToTable("AspRoles");
            modelBuilder.Entity<CustomUserClaim>().ToTable("UserClaims");
            modelBuilder.Entity<CustomUserRole>().ToTable("UserRoles");
            modelBuilder.Entity<CustomUserLogin>().ToTable("UserLogins");
از این جهت نام جدول CustomRole را در دیتابیس AspRoles انتخاب کردم تا با نام جدول Roles نقش‌های کنونی سیستم Iris تداخلی پیش نیاید.
اکنون دستور زیر را در کنسول Nuget وارد کنید تا کدهای مورد نیاز برای مهاجرت تولید شوند:
Add-Migration UpdateDatabaseToAspIdentity
public partial class UpdateDatabaseToAspIdentity : DbMigration
{
        public override void Up()
        {
            CreateTable(
                "dbo.UserClaims",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        UserId = c.Int(nullable: false),
                        ClaimType = c.String(),
                        ClaimValue = c.String(),
                        ApplicationUser_Id = c.Int(),
                    })
                .PrimaryKey(t => t.Id)
                .ForeignKey("dbo.Users", t => t.ApplicationUser_Id)
                .Index(t => t.ApplicationUser_Id);
            
            CreateTable(
                "dbo.UserLogins",
                c => new
                    {
                        LoginProvider = c.String(nullable: false, maxLength: 128),
                        ProviderKey = c.String(nullable: false, maxLength: 128),
                        UserId = c.Int(nullable: false),
                        ApplicationUser_Id = c.Int(),
                    })
                .PrimaryKey(t => new { t.LoginProvider, t.ProviderKey, t.UserId })
                .ForeignKey("dbo.Users", t => t.ApplicationUser_Id)
                .Index(t => t.ApplicationUser_Id);
            
            CreateTable(
                "dbo.UserRoles",
                c => new
                    {
                        UserId = c.Int(nullable: false),
                        RoleId = c.Int(nullable: false),
                        ApplicationUser_Id = c.Int(),
                    })
                .PrimaryKey(t => new { t.UserId, t.RoleId })
                .ForeignKey("dbo.Users", t => t.ApplicationUser_Id)
                .ForeignKey("dbo.AspRoles", t => t.RoleId, cascadeDelete: true)
                .Index(t => t.RoleId)
                .Index(t => t.ApplicationUser_Id);
            
            CreateTable(
                "dbo.AspRoles",
                c => new
                    {
                        Id = c.Int(nullable: false, identity: true),
                        Description = c.String(),
                        Name = c.String(nullable: false, maxLength: 256),
                    })
                .PrimaryKey(t => t.Id)
                .Index(t => t.Name, unique: true, name: "RoleNameIndex");
            
            AddColumn("dbo.Users", "EmailConfirmed", c => c.Boolean(nullable: false));
            AddColumn("dbo.Users", "SecurityStamp", c => c.String());
            AddColumn("dbo.Users", "PhoneNumber", c => c.String());
            AddColumn("dbo.Users", "PhoneNumberConfirmed", c => c.Boolean(nullable: false));
            AddColumn("dbo.Users", "TwoFactorEnabled", c => c.Boolean(nullable: false));
            AddColumn("dbo.Users", "LockoutEndDateUtc", c => c.DateTime());
            AddColumn("dbo.Users", "LockoutEnabled", c => c.Boolean(nullable: false));
            AddColumn("dbo.Users", "AccessFailedCount", c => c.Int(nullable: false));
        }
        
        public override void Down()
        {
            DropForeignKey("dbo.UserRoles", "RoleId", "dbo.AspRoles");
            DropForeignKey("dbo.UserRoles", "ApplicationUser_Id", "dbo.Users");
            DropForeignKey("dbo.UserLogins", "ApplicationUser_Id", "dbo.Users");
            DropForeignKey("dbo.UserClaims", "ApplicationUser_Id", "dbo.Users");
            DropIndex("dbo.AspRoles", "RoleNameIndex");
            DropIndex("dbo.UserRoles", new[] { "ApplicationUser_Id" });
            DropIndex("dbo.UserRoles", new[] { "RoleId" });
            DropIndex("dbo.UserLogins", new[] { "ApplicationUser_Id" });
            DropIndex("dbo.UserClaims", new[] { "ApplicationUser_Id" });
            DropColumn("dbo.Users", "AccessFailedCount");
            DropColumn("dbo.Users", "LockoutEnabled");
            DropColumn("dbo.Users", "LockoutEndDateUtc");
            DropColumn("dbo.Users", "TwoFactorEnabled");
            DropColumn("dbo.Users", "PhoneNumberConfirmed");
            DropColumn("dbo.Users", "PhoneNumber");
            DropColumn("dbo.Users", "SecurityStamp");
            DropColumn("dbo.Users", "EmailConfirmed");
            DropTable("dbo.AspRoles");
            DropTable("dbo.UserRoles");
            DropTable("dbo.UserLogins");
            DropTable("dbo.UserClaims");
        }
}

بهتر است که در کدهای تولیدی فوق، اندکی متد Up را با کد زیر تغییر دهید: 
AddColumn("dbo.Users", "EmailConfirmed", c => c.Boolean(nullable: false, defaultValue:true));

چون در سیستم جدید احتیاج به تایید ایمیل به هنگام ثبت نام است، بهتر است که ایمیل‌های قبلی موجود در سیستم نیز به طور پیش فرض تایید شده باشند.
در نهایت برای اعمال تغییرات بر روی دیتابیس دستور زیر را در کنسول Nuget وارد کنید:
Update-Database
 
تغییرات ServiceLayer

ابتدا دستور زیر را در کنسول Nuget  وارد کنید: 
Get-Project Iris.Servicelayer, Iris.Web | Install-Package Microsoft.AspNet.Identity.Owin
سپس از فولدر Contracts پروژه AspNetIdentityDependencyInjectionSample.ServiceLayer فایل‌های IApplicationRoleManager، IApplicationSignInManager، IApplicationUserManager، ICustomRoleStore و ICustomUserStore را در فولدر Interfaces پروژه Iris.ServiceLayer کپی کنید. تنها کاری هم که نیاز هست انجام بدهید اصلاح namespace هاست.

باز از پروژه AspNetIdentityDependencyInjectionSample.ServiceLayer کلاس‌های ApplicationRoleManager، ApplicationSignInManager،  ApplicationUserManager، CustomRoleStore، CustomUserStore، EmailService و SmsService را به پوشه EFServcies پروژه‌ی Iris.ServiceLayer کپی کنید.
نکته: پیشنهاد می‌کنم که EmailService را به IdentityEmailService تغییر نام دهید چرا که در حال حاضر سیستم Iris دارای کلاسی به نامی EmailService هست.
 
تنظیمات StructureMap برای تزریق وابستگی ها
پروژه Iris.Web  را باز کرده، به فولدر DependencyResolution بروید و به کلاس IoC کدهای زیر را اضافه کنید:
                x.For<IIdentity>().Use(() => (HttpContext.Current != null && HttpContext.Current.User != null) ? HttpContext.Current.User.Identity : null);

                x.For<IUnitOfWork>()
                    .HybridHttpOrThreadLocalScoped()
                    .Use<IrisDbContext>();

                x.For<IrisDbContext>().HybridHttpOrThreadLocalScoped()
                   .Use(context => (IrisDbContext)context.GetInstance<IUnitOfWork>());
                x.For<DbContext>().HybridHttpOrThreadLocalScoped()
                   .Use(context => (IrisDbContext)context.GetInstance<IUnitOfWork>());

                x.For<IUserStore<ApplicationUser, int>>()
                    .HybridHttpOrThreadLocalScoped()
                    .Use<CustomUserStore>();

                x.For<IRoleStore<CustomRole, int>>()
                    .HybridHttpOrThreadLocalScoped()
                    .Use<RoleStore<CustomRole, int, CustomUserRole>>();

                x.For<IAuthenticationManager>()
                      .Use(() => HttpContext.Current.GetOwinContext().Authentication);

                x.For<IApplicationSignInManager>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use<ApplicationSignInManager>();

                x.For<IApplicationRoleManager>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use<ApplicationRoleManager>();

                // map same interface to different concrete classes
                x.For<IIdentityMessageService>().Use<SmsService>();
                x.For<IIdentityMessageService>().Use<IdentityEmailService>();

                x.For<IApplicationUserManager>().HybridHttpOrThreadLocalScoped()
                   .Use<ApplicationUserManager>()
                   .Ctor<IIdentityMessageService>("smsService").Is<SmsService>()
                   .Ctor<IIdentityMessageService>("emailService").Is<IdentityEmailService>()
                   .Setter<IIdentityMessageService>(userManager => userManager.SmsService).Is<SmsService>()
                   .Setter<IIdentityMessageService>(userManager => userManager.EmailService).Is<IdentityEmailService>();

                x.For<ApplicationUserManager>().HybridHttpOrThreadLocalScoped()
                   .Use(context => (ApplicationUserManager)context.GetInstance<IApplicationUserManager>());

                x.For<ICustomRoleStore>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use<CustomRoleStore>();

                x.For<ICustomUserStore>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use<CustomUserStore>();

اگر ()HttpContext.Current.GetOwinContext شناسایی نمی‌شود دلیلش این است که متد GetOwinContext یک متد الحاقی است که برای استفاده از آن باید پکیج نیوگت زیر را نصب کنید:
Install-Package Microsoft.Owin.Host.SystemWeb

تغییرات Iris.Web
در ریشه پروژه‌ی Iris.Web  یک کلاس به نام Startup  بسازید و کدهای زیر را در آن بنویسید:
using System;
using Iris.Servicelayer.Interfaces;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.DataProtection;
using Owin;
using StructureMap;

namespace Iris.Web
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            configureAuth(app);
        }

        private static void configureAuth(IAppBuilder app)
        {
            ObjectFactory.Container.Configure(config =>
            {
                config.For<IDataProtectionProvider>()
                      .HybridHttpOrThreadLocalScoped()
                      .Use(() => app.GetDataProtectionProvider());
            });

            //ObjectFactory.Container.GetInstance<IApplicationUserManager>().SeedDatabase();

            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            // Configure the sign in cookie
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account.
                    OnValidateIdentity = ObjectFactory.Container.GetInstance<IApplicationUserManager>().OnValidateIdentity()
                }
            });
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
            app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

            // Enables the application to remember the second login verification factor such as phone or email.
            // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
            // This is similar to the RememberMe option when you log in.
            app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

            app.CreatePerOwinContext(
               () => ObjectFactory.Container.GetInstance<IApplicationUserManager>());

            // Uncomment the following lines to enable logging in with third party login providers
            //app.UseMicrosoftAccountAuthentication(
            //    clientId: "",
            //    clientSecret: "");

            //app.UseTwitterAuthentication(
            //   consumerKey: "",
            //   consumerSecret: "");

            //app.UseFacebookAuthentication(
            //   appId: "",
            //   appSecret: "");

            //app.UseGoogleAuthentication(
            //    clientId: "",
            //    clientSecret: "");

        }
    }
}

تا به این جای کار اگر پروژه را اجرا کنید نباید هیچ مشکلی مشاهده کنید. در بخش بعدی کدهای مربوط به کنترلر‌های ورود، ثبت نام، فراموشی کلمه عبور و ... را با سیستم Identity پیاده سازی می‌کنیم.
مطالب
افزونه مدیریت فایل های رایگان Roxy FileMan برای TinyMce و CkEditor
در این مقاله سعی در پیاده سازی Roxy FileMan  در Asp.net mvc داریم. البته خود وب سایت آن (اینجا) برای .net Framework 4.5 پروژه‌ایی را آماده کرده است که بنده بنا دارم همان پروژه را توضیح دهم.
قبل از شروع به کار، ابتدا نمایی از این ابزار مدیریت فایل (File Manager) را ملاحظه فرمایید:


1- ابتدا یک پروژه‌ی جدید MVC می‌سازیم
2- جهت بخش سمت سرور، کد هندلری به نام main.ashx نوشته شده است که در فولدر asp_net قرار دارد و در ادامه به توضیح آن خواهم پرداخت. با هندلر بیشتر آشنا شوید.
3- پیشنهاد می‌شود جهت راحتی کار و انتقال بین پروژه‌های خود در فولدر scripts فولدری را به نام  RoxyFileManager بسازید. این کار سبب می‌شود جهت پروژه‌های مختلف فقط فولدر RoxyFileManager را بین پروژه‌های خود کپی و پیست کنید.
4- از داخل پروژه دانلود شده، فولدر های asp_net ،css، Images، js، lang را به پروژه‌ی جدید کپی می‌کنیم.

 
5- اکنون به سراغ web.config می‌رویم. موارد قرمز ضروری هستند که به web,config اضافه شوند.


6- کل مدیریت فایل در فایل index.html می‌باشد و اگر تمایل به تغییر عنوان، درج لوگوی خودتان و ... را دارید به این فایل مراجعه کنید.
7- فایل conf.json، همانطور که از نام این فایل برداشت می‌شود، تنظیمات را می‌توان به فرمت json در این فایل جستجو کرد. مهمترین تنظیمات، بحث تغییر زبان می‌باشد که می‌توان با تعویض قسمت LANG از en به هر زبان دلخواهی که داخل پوشه‌ی lang می‌باشد، آن‌را تغییر داد که البته بنده آن‌را به زبان فارسی هم ترجمه کرده‌ام که در فولدر lang جهت استفاده موجود می‌باشد.
8- اکنون به فولدر asp_net می‌رویم و با handler بیشتر آشنا می‌شیم. در خط‌های اول، به متغییر confFile بر می‌خوریم که آدرس فایل conf.json را داده است.
9- آدرس دایرکتوری اصلی را در متد GetFilesRoot جستجو کنید. (من معمولا در پروژه‌های خود، فولدری دارم به نام uploads که داخل آن تمام فولدرهام که مربوط به ماژول‌های مختلف است، نگه داری می‌شوند و خود کاربر آپلود می‌کند. اکنون برای این پروژه می‌توان یک فولدر جدید باز کرد و به آن آدرس داد تا در روند پروژه‌ی شما اختلالی ایجاد نشود. من نام آن فولدر را web گذاشته‌ام)
نکته جالب همین جاست که شما به کمک این آدرس دهی از source پروژه می‌توانید خیلی کارها را انجام دهید. مثلا در یک پروژه می‌خواهید به فولدرها طبق نقشی که کاربران  در سیستم دارند دسترسی بدهید تا فایل‌ها را آپلود کنند و ... به راحتی می‌توانید با کمی کد نویسی این تغییرات را در متد GetFilesRoot اعمال کنید.
اگر بخواهید مدیریت فایل را بر اساس زبان سیستم تغییر دهید کافیست، یک فایل conf.json دیگر ایجاد کنید و در سورس به ازای هر زبان، آن فایل conf.json را لود کنید. البته در هر فایل شما باید زبان را تغییر دهید.
راست چین کردن این ادیتور را در اسرع وقت انجام می‌دهم و برای شما آپلود می‌کنم. تا کاملا نسخه‌ی بومی سازی آن در اختیار شما قرار گیرد.
10- اکنون به سراغ تنظیمات Tiny mce می‌رویم.
خوب، ابتدا با دستور Install-Package TinyMCE در Package Manager Console و انتخاب سورس Nuget آخرین نسخه را نصب می‌کنیم. در حال حاضر آخرین نسخه نگارنده 4.2.5 می باشد.
کل این تنظیمات در فایل Tinymce.html می‌باشد (این فایل در پروژه شما می‌تواند همان view‌ها و partial view‌های شما باشد که تنظیمات اولیه tinymce را می‌نویسید). حال بررسی مواردی که به tinymce اضافه می‌شوند تا با مدیریت فایل ادغام شود:


و اسکریپتی جهت افزونه‌ی مدیریت فایل Roxy FileMan را در پایین متد اضافه می‌کنیم.


خوب، اکنون فایل Tinymce.html  را اجرا کنید و نتیجه را ملاحظه بفرمایید.
Roxy FileMan را می‌توان با CKEditor هم سازگار کرد، که جهت اطلاعات بیشتر  اینجا  را مطالعه فرمایید.
فایل پروژه: RoxyFileManager.rar
مطالب
Ef 6 و Ngen : شروعی سریعتر برای برنامه های مبتنی بر Entity Framework
تولید کد Native زمانی اتفاق می‌افتد که کامپایلر JIT، کد اسمبلی‌های MSIL را به کدهای Native در ماشین محلی کامپایل می‌کند و این عمل بلافاصله قبل از اجرای متد برای اولین بار اتفاق می‌افتد. این کد به صورت موقتی بوده و در حافظه‌ای که برای پردازش در نظر گرفته شده ذخیره می‌شود و در پایان هر پردازش توسط سیستم عامل ویرایش می‌شود. کد Native به ازای هر بار شروع یک پردازش تولید می‌شود. ابزار Native Image Generator یا همان Ngen اقدام به تولید کد Native با استفاده از کامپایلر JIT نموده و آن را در هارد دیسک ذخیره می‌نماید. زمانیکه برنامه نیازمند یک اسمبلی CLR است، به جای بارگذاری خود اسمبلی، ایمیج کد Native آن بارگذاری می‌شود. به این نکته نیز توجه داشته باشید که CLR اطلاعاتی در مورد اینکه کدام اسمبلی، ایمیج کد Native است و این ایمیج در کجا و در چه زمانی تهیه شده است، دارد. کد Native باعث بهبود استفاده از حافظه می‌شود، زمانیکه یک اسمبلی بین پروسس‌ها به اشتراک گذاشته شده‌است. تا قبل از EF6 کتابخانه‌های هسته‌ای EF در زمان اجرا جزئی از دات نت فریمورک بودند و تولید کد Native آنها به صورت اتوماتیک انجام می‌شد. اما از نسخه 6، تمامی این کتابخانه‌ها در داخل پکیج Nuget آن ترکیب شده‌اند . پس برای تولید کد Native مربوط به فایل EntityFramework.dll نیازمند ابزار Ngen هستیم.
1- ابتدا یک برنامه‌ی ساده کنسول ویندوز ساخته و از Package Manager Console دستور Install-package entityframework را اجرا نموده تا پکیج Ef به برنامه اضافه گردد.
using System;
using System.Data.Entity;

namespace UsingNgen
{

    public class NgenDbContex : DbContext
    { }

    class Program
    {
        static void Main()
        {
            var nGenCtx = new NgenDbContex();
            Console.WriteLine("Press a key to exit...");
            Console.ReadKey();
        }
    }


}
حال کد ساده بالا را به برنامه اضافه می‌کنیم و برنامه را Build میکنیم.
2- برای ثبت جزئیات اجرای برنامه از ابزار Windows Performance Recorder که جزئی از ویندوز می‌باشد، استفاده می‌کنیم. کافیست عبارت WPR را در نوار جستجوی ویندوز تایپ کنید تا این ابزار در دسترس قرار گیرد. 



برای ضبط جزئیات، روی دکمه‌ی Start کلیک کنید و به محل ذخیره‌ی فایل اجرایی حاصل از Build ویژوال استودیو رفته و آن را اجرا کنید. بعد از اتمام اجرا، جزئیات را ذخیره نمایید.

بعد از ذخیره فایل، در پنجره بالا دکمه‌ای به نام Open in WPA ظاهر می‌شود. WPA مخفف Windows Performance Analyzer می‌باشد. آن را کلیک کنید تا محیط آنالایزر باز شود.

حال در سمت چپ این پنجره انواع آنالایزرها را مشاهده می‌کنید. روی آنالایزر Computation کلیک کنید و از زیرمجموعه‌ی آن، CPU Usage را انتخاب کنید. آمار مربوط به برنامه خودمان را در تصویر بالا مشاهده می‌کنید. کل برنامه 164 میلی ثانیه زمان برده و فایل Clr.dll حدود 47 میلی ثانیه و یک فایل clrjit.dll نیز برای تولید کد JIT وجود دارد. حال برای تسریع در عمل شروع، از تکنیک Ngen به صورت زیر استفاده می‌کنیم.

3- دوباره به نوار جستجوی ویندوز رفته و ابزار Developer Command Prompt for VsXXXX را با امتیاز دسترسی از نوع Admin اجرا کنید. XXXX نسخه‌ی ویژوال استودیو می‌باشد.

حال به محل ذخیره فایل اجرایی برنامه رفته و دستور Ngen Install EntityFramework.dll را تایپ کنید تا یک ایمیج کد Native از entityframework.dll ساخته شود. دوباره ابزار Windows Performance Recorder را لود کرده و روی دکمه Start کلیک کنید و فایل اجرایی برنامه را اجرا نمایید. پس از اتمام عملیات ثبت جزئیات، آن را در Windows Performance Analyzer باز نمایید.

همانطور که مشاهده می‌کنید کل برنامه ما 89 میلی ثانیه زمان برده و Clr.dll 29 ثانیه و به جای clrjit.dll فایل EntityFramework به صورت native تولید شده است.

مطالب
Accord.NET #1
Accord.NET کتابخانه‌ای است متن‌باز و بسیار کارآمد که در آن توابع بسیار زیادی در حوزه‌ی تحلیل آماری (statistical analysis)، یادگیری ماشین (machine learning)، پردازش تصویر (Image processing) و بینایی ماشین (computer vision) قرار گرفته‌اند تا در برنامه‌های NET. ایی مورد استفاده قرار گیرند.


چارچوب Accord.NET توسط آقای سزار سوزا بر پایه کتابخانه‌ی مشهور و محبوب AForge.NET (که توسط آقای اندرو کریلو ایجاد شده بود) بنا شده و البته ابزار‌های جدید زیادی به همراه یک محیط کامل برای محاسبات علمی (scientific computing) در NET. به آن اضافه شده است.

این چارچوب متشکل از چندین کتابخانه است که می‌توان آن را از طریق NuGet دریافت و نصب کرد.

کتابخانه‌های Accord.NET را می‌توان به سه دسته‌ی کلی تقسیم کرد :

1. محاسبات علمی (scientific computing) 


1.1. Accord.Math 
جهت کار با ماتریس‌ها عددی
تجزیه ماتریس‌ها (decomposition matrix)
الگوریتم‌های بهینه سازی عددی برای مسائل محدود و نامحدود
توابع و ابزار‌های خاص جهت استفاده در کاربردهای علمی
1.2.  Accord.Statistics  شامل توابعی جهت توزیع‌های احتمال (probability distributions)
آزمایش فرضیات (hypothesis testing)
مدل‌های آماری (statistical models)
و توابعی شامل : رگرسیون خطی، مدل پنهان مارکوف (Hidden Markov Models)، آنالیز اجزای اساسی   (Principal Component Analysis) و خیلی از تکنیک‌های مرتبط دیگر.

1.3. Accord.MachineLearning
شامل دسته بند‌های معروف از جمله :
ماشین برداری پشتیبان - Support Vector Machines
درخت تصمیم - Decision Trees
مدل نیو بیز - Naive Bayesian models
K-means
مدل ترکیبی گوسین - Gaussian Mixture models
و الگوریتم‌های متدوال دیگری مانند : Ransac, Cross-validation و Grid-Search 
1.4. Accord.Neuro 
شامل الگوریتم‌های معروف در حوزه شبکه‌های عصبی مصنوعی مانند:
لونبرگ مارکوارت - Levenberg-Marquardt
Parallel Resilient Back-propagation
شبکه باور عمیق - Deep Belief Networks
ماشین بولتزمن - Restrictured Boltzmann Machines
و تعدادی از شبکه‌های عصبی دیگر  

2. پردازش تصویر و سیگنال

2.1. Accord.Imaging  شامل آشکارسازهای نقاط از جمله Harris, SURF, FAST و FREAK  
فیلتر‌هایی برای تصاویر
توابعی جهت انطباق (matching) و دوخت (stitching) تصاویر
استخراج ویژگی‌های خوبی مانند - ﻫﯿﺴﺘﻮﮔﺮام ﮔﺮادﯾﺎن‌ﻫﺎی ﺷﯿﺐ‌ﮔﺮا و یا هاگ (Histograms of Oriented Gradients) و ویژگی‌های توصیفی بافتی هارلیک (Haralick’s textural)

2.2. Accord.Vision   تشخیص و ردیابی بی‌درنگ چهره
توابعی برای تشخیص، ردیابی و تبدیل اشیایی که در جریانی(streams) از تصاویر هستند
2.3. Accord.Audio  شامل توابعی جهت پردازش صدا از جمله اسپکتروم آنالایزر 

3. سایر کتابخانه‌های پشتیبانی

3.1. Accord.Controls  شامل نمودار هیستوگرام، پلات‌ها و نمایشگر‌ها و نمودارهایی برای داده‌های جدولی جهت کاربردهای علمی.
3.2. Accord.Controls.Imaging  شامل ابزاری برای نمایش سریع تصاویر برای برنامه‌های Windows Forms 
3.3. Accord.Controls.Audio 
شامل کنترل‌های Windows Forms برای نمایش شکل موج صوت و اطلاعات آن
3.4. Accord.Controls.Vision 
شامل اجزاء و کنترل‌های Windows Forms برای ردیابی حرکات سر، صورت، دست و سایر کارهای مرتبط با بینایی ماشین

اگر با مفاهیم یادگیری ماشین و هوش موصنوعی کمتر آشنا هستید و در این قسمت کمی کلمات تخصصی به کار رفته نگران نباشید؛ در مطالب آتی به صورت کاربردی به استفاده‌ی از آنها خواهیم پرداخت.
مطالب
Content Negotiation در WCF
Content Negotiation ، مکانیزمی است که طی آن مصرف کننده یک سرویس http تعیین می‌کند که خروجی مورد نظر از سرویس به چه فرمتی در اختیار آن قرار گیرد. این قابلیت بسیار زیبا در Asp.Net Web Api فراهم می‌باشد. اما از آن جا که در  WCF به صورت توکار مکانیزمی جهت پیاده سازی این قابلیت در نظر گرفته نشده است می‌توان از طریق یک کتابخانه ثالث به نام WCFRestContrib به این مهم دست یافت.

به صورت معمول برای پیاده سازی Content Negotiation، مصرف کننده باید در Accept هدر درخواست، برای سرویس مورد نظر، نوع Content-Type را نیز تعیین نمایید. از طرفی سرویس دهنده نیز باید معادل Mime Type درخواست شده، یک Formatter جهت سریالایز داده‌ها در اختیار داشته باشد. در WCF از طریق کتابخانه WcfRestContrib می‌توانیم به صورت زیر Content Negotiation را پیاده سازی نماییم:

ابتدا از طریق Nuget کتابخانه زیر را نصب کنید:
install-package WcfRestContrib
حال فرض کنید سرویسی به صورت زیر داریم:
[ServiceContract]
public interface IBooksService
{    
    [OperationContract]
    void AddBook(string isbn, Book book);
}
کد‌های بالا روشی مرسوم برای تعریف Service Contract‌های WCF است. برای اینکه سرویس WCF بالا به صورت Rest طراحی شود و از طرفی قابلیت سریالاز داده‌ها به چندین فرمت را داشته باشد باید به صورت زیر عمل نماییم:
[ServiceContract]
public interface IBooksService
{
    [WebInvoke(UriTemplate = "/{isbn}", Method=Verbs.Put)]
    [WebDispatchFormatter]
    [OperationContract]
    void AddBook(string isbn, Book book);
    ....
}
وظیفه WebDispatchFormatterAttribute تعریف شده برای Operation بالا این است که نوع فرمت مورد نیاز را از Accept هدر درخواست واکشی کرده و با توجه به MimeType‌های تعریف شده در سرویس، داده‌ها را به آن فرمت سریالاز نماید. در صورتی که MimeType درخواست شده از سوی مصرف کننده، سمت سرور تعریف نشده بود، MimeType پیش فرض انتخاب می‌شود.
گام بعدی مشخص کردن انواع MimeType‌ها برای این سرویس است. در WcfRestContrib به صورت پیش فرض چهار Formatter تعبیه شده است:
»Xml : از DataContractSerializer موجود در WCF برای سریالاز و دی سریالایز داده‌ها استفاده می‌کند.
»Json : از طریق DataContactJsonSerializer برای سریالاز و دی سریالایز داده‌ها استفاده می‌کند.
POX : همانند مورد اول از DataContractSerializer استفاده می‌کند با این تفاوت که DataContract‌ها بدون Namesapce و Attribute و DataMember‌ها نیز بدون Order می‌باشند.
»Form Url Encoded

در صورتی که نیاز به formatter دیگری دارید می‌توانید با استفاده از CustomFormatter موجود در این کتابخانه، Formatter دلخواه خود را پیاده سازی نمایید.

همان طور که در بالا ذکر شد، در صورتی که MimeType درخواست شده از سوی مصرف کننده، سمت سرور تعریف نشده باشد، MimeType پیش فرض انتخاب می‌شود. برای تعریف MimeType پیش فرض می‌توان از خاصیت WebDispatchFormatterConfigurationAttribute که در فضای نام  WcfRestContrib.ServiceModel.Description  قرار دارد استفاده کرد. تعاریف سایر MimeType‌ها نیز با استفاده از WebDispatchFormatterMimeTypeAttribute انجام می‌شود. به صورت زیر:

[WebDispatchFormatterConfiguration("application/xml")]
[WebDispatchFormatterMimeType(typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.PoDataContract), "application/xml",  "text/xml")]
[WebDispatchFormatterMimeType( typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.DataContractJson),  "application/json")]
[WebDispatchFormatterMimeType( typeof(WcfRestContrib.ServiceModel.Dispatcher.Formatters.FormUrlEncoded), "application/x-www-form-urlencoded")]
public class Books : IBooksService 
{    
   public void AddBook(string isbn, Book book)
   {
   }
}
همانند سایر تنظیمات WCF می‌توان تمامی این موارد را در فایل Config پروژه سرویس نیز تعریف کرد: برای مثال:
<system.serviceModel>
    <extensions>
        <behaviorExtensions>
            <add name="webFormatter" 
                 type="WcfRestContrib.ServiceModel.Configuration.WebDispatchFormatter.ConfigurationBehaviorElement, WcfRestContrib, 
                       Version=x.x.x.x, Culture=neutral, PublicKeyToken=89183999a8dc93b5"/>
        </behaviorExtensions>
    </extensions>
    <serviceBehaviors>
        <behavior name="Rest">
          <webFormatter>
            <formatters defaultMimeType="application/xml">
              <formatter mimeTypes="application/xml,text/xml" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.PoxDataContract, WcfRestContrib"/>
              <formatter mimeTypes="application/json" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.DataContractJson, WcfRestContrib"/>
              <formatter mimeTypes="application/x-www-form-urlencoded" 
                         type="WcfRestContrib.ServiceModel.Dispatcher.Formatters.FormUrlEncoded, WcfRestContrib"/>
            </formatters>
          </webFormatter>
        </behavior>
    </serviceBehaviors>
</system.serviceModel>
نکته:
در صورتی که قصد داشته باشیم که باتوجه به direction مورد نظر (نظیر Outgoing یا Incoming) داده‌ها سریالایز/ دی سریالایز شوند، می‌توان این مورد را در هنگام تعریف OperationContract تعیین کرد:
[WebDispatchFormatter(WebDispatchFormatter.FormatterDirection.Outgoing)]  

مطلب تکمیلی:

  مشاهده پیاده سازی Content Negotiation در Asp.Net MVC
مطالب
آموزش WAF
دز طراحی پروژه‌های مقیاس بزرگ و البته به صورت ماژولار همیشه ساختار پروژه اهمیت به سزایی دارد. متاسفانه این مورد خیلی در طراحی پروژه‌ها در نظر گرفته نمی‌شود و اغلب اوقات شاهد آن هستیم که یک پروژه بسیار بزرگ دقیقا به همان صورت پروژهای کوچک و کم اهمیت‌تر مدیریت و پیاده سازی می‌شود که این مورد هم مربوط به پروژه‌های تحت وب و هم پروژه‌های تحت ویندوز و WPF است. برای مدیریت پروژه‌های WPF و Silverlight در این پست به اختصار درباره PRISM بحث شد. مزایا و معایب آن  بررسی و در طی این پست ها(^ و ^) مثال هایی را پیاده سازی کردیم. اما در این پست مفتخرم شما را با یکی دیگر از کتابخانه‌های مربوط به پیاده سازی مدل MVVM آشنا کنم. کتابخانه ای متن باز، بسیار سبک با کارایی بالا.
اما نکته ای که ذکر آن خالی از لطف نیست این است که قبلا از این کتابخانه در یک پروژه بزرگ  و ماژولار WPF  استفاده کردم و نتیجه مطلوب نیز حاصل شد.
معرفی:
WPF Application Framework یا به اختصار WAF کتابخانه کم حجم سبک و البته با کارایی عالی برای طراحی پروژه‌های ماژولار WPF در مقیاس بزرگ طراحی شده است که مدل پیاده سازی ان بر مبنای مدل MVVM و MVC است. شاید برایتان جالب باشد که این کتابخانه دقیقا مدل MVC را با مدل MVVM ترکیب کرده در نتیجه مفاهیم آن بسیار شبیه به پروژه‌های تحت وب MVC است. همانطور که از نام آن پیداست این کتابخانه صرفا برای پروژه‌های WPF طراحی شده، در نتیجه در پروژه‌های Silverlight نمی‌توان از آن استفاده کرد.
ساختار کلی آن به شکل زیر می‌باشد:

همانطور که مشاهده می‌کنید پروژه‌های مبتنی بر این کتابخانه همانند سایر کتابخانه‌های MVVM از سه بخش تشکیل شده اند. بخش اول با عنوان Shell یا Presentation معرف فایل‌های Xaml پروژه است، بخش دوم یا Application معرف ViewModel و Controller و البته IView  می‌باشد. بخش Domain نیز در برگیرنده مدل‌های برنامه است.

معرفی برخی مفاهیم:

»Shell :  این کلاس معادل یک فایل Xaml است که حتما باید یک اینترفیس IView را پیاده سازی نماید.
»IView : معرف یک اینترفیس جهت برقراری ارتباط بین ViewModel و Shell
»ViewModel : در این جا ViewModel با مفهوم ViewModel در سایر کتابخانه‌های MVVM  کمی متفاوت است. در این کتابخانه ViewModel فقط شامل تعاریف است  و هیچ گونه پیاده سازی در اینجا صورت نمی‌گیرد. دقیقا معادل مفهوم ViewModel در پروژه‌های MVC تحت وب.
»Controller : پیاده سازی ViewModel و تعریف رفتارها در این قسمت انجام می‌گیرد.

اما در بسیاری از پروژها نیاز به پیاده سازی الگوی DataModel-View-ViewModel است که این کتابخانه با دراختیار داشتن برخی کلاس‌های پایه این مهم را برایمان میسر کرده است.

همانطور که می‌بینید در این حالت بر خلاف حالت قبلی ViewModel  و کنترلر‌های پروژه به جای ارتباط با مدل با مفهوم DataModel تغذیه می‌شوند که یک پیاده سازی سفارشی از مدل‌های پروژه است. هم چنین این کتابخانه یک سری Converter‌های سفارشی جهت تبدیل Model به DataModel و برعکس را ارائه می‌دهد.
سرویس‌های پیش فرض: که شامل DialogBox جهت نمایش پیغام‌ها و Save|Open File Dialog سفارشی نیز می‌باشد.
 »برای پیاده سازی Modularity از کتابخانه MEF استفاده شده است.
Command‌های سفارشی: پیاده سازی خاص از اینترفیس ICommand
»مفاهیم مربوط به Weak Event Pattern به صورت توکار در این کتابخانه تعبیه شده است.
»به صورت پیش فرض مباحث مربوط به اعتبارسنجی با استفاده از DataAnnotation   و IDataErrorInfo در این کتابخانه تعبیه شده است.
»ارائه Extension‌های مربوط به UnitTest نظیر Exceptions  و CanExecuteChangedEvent و PopertyChanged جهت سهولت در تهیه unit test

دانلود و نصب
با استفاده از nuget  و دستور زیر می‌توانید این کتابخانه را نصب نمایید:
Install-Package waf
هم چنین می‌توانید سورس آن به همراه فایل‌های باینری را از اینجا دریافت کنید. در پست بعدی یک نمونه از پیاده سازی مثال با این کتابخانه را بررسی خواهیم کرد.
مطالب
تعامل با پایگاه داده با استفاده از EntityFramework در پروژه های F# MVC 4
در پست‌های قبلی (^ و^) با  template و ساخت کنترلر و مدل در پروژه‌های F# MVC آشنا شدید. در این پست به طراحی Repository با استفاده از EntityFramework خواهم پرداخت. در ادامه مثال قبل، برای تامین داده‌های مورد نیاز کنترلر‌ها و نمایش آن‌ها در View نیاز به تعامل با پایگاه داده وجود دارد. در نتیجه با استفاده از الگوی Repository، داده‌های مورد نظر را تامین خواهیم کرد. به صورت پیش فرض با نصب Template جاری (F# MVC4) تمامی اسمبلی‌های مورد نیاز برای استفاده از  در EF در پروژه‌های #F نیز نصب می‌شود.

پیاده سازی DbContext مورد نیاز
برای ساخت DbContext می‌توان به صورت زیر عمل نمود:
namespace FsWeb.Repositories

open System.Data.Entity
open FsWeb.Models

type FsMvcAppEntities() = 
    inherit DbContext("FsMvcAppExample")

    do Database.SetInitializer(new CreateDatabaseIfNotExists<FsMvcAppEntities>())

    [<DefaultValue()>] val mutable books: IDbSet<Guitar>
    member x.Books with get() = x.books and set v = x.books <- v
همان طور که ملاحظه می‌کنید  با ارث بری از کلاس DbContext  و پاس دادن ConnectionString یا نام آن در فایل app.config، به راحتی FsMVCAppEntities ساخته می‌شود که معادل DbContext پروژه مورد نظر است. با استفاده از دستور do متد SetInitializer برای عملیات migration فراخوانی می‌شود. در پایان نیز یک DbSet به نام Books ایجاد کردیم. فقط از نظر syntax با حالت #C آن تفاوت دارد اما روش پیاده سازی مشابه است.

اگر syntax زبان #F برایتان نامفهوم است می‌توانید از این دوره کمک بگیرید.

پیاده سازی کلاس BookRepository
ابتدا به کد‌های زیر دقت کنید:
namespace FsWeb.Repositories

type BooksRepository() =
    member x.GetAll () = 
        use context = new FsMvcAppEntities() 
        query { for g in context.Books do
                select g }
        |> Seq.toList
در کد بالا ابتدا تابعی به نام GetAll داریم. در این تابع یک نمونه از DbContext پروژه وهله سازی می‌شود. نکته مهم این است به جای شناسه let از شناسه use استفاده کردم. شناسه use دقیقا معال دستور {}()using در #C است. بعد از اتمام عملیات شی مورد نظر Dispose خواهد شد.
در بخش بعدی بک کوئری از DbSet مورد نظر گرفته می‌شود. این روش Query گرفتن در F# 3.0 مطرح شده است. در نتیجه در نسخه‌های قبلی آن (F# 2.0) اجرای این کوئری باعث خطا می‌شود. اگر قصد دارید با استفاده از F# 2.0 کوئری‌های خود را ایجاد نماید باید به طریق زیر عمل نمایید:
ابتدا از طریق nuget اقدام به نصب package  ذیل نمایید:
FSPowerPack.Linq.Community
سپس در ابتدا Source File خود، فضای نام Microsoft.FSharp.Linq.Query را باز(استفاده از دستور open) کنید. سپس می‌توانید با اندکی تغییر در کوئری قبلی خود، آن را در F# 2.0 اجرا نمایید.
query <@ seq { for g in context.Books -> g } @> |> Seq.toList
حال باید Repository طراحی شده را در کنترلر مورد نظر فراخوانی کرد. اما اگر کمی سلیقه به خرج دهیم به راحتی می‌توان با استفاده از تزریق وابستگی ، BookRepository را در اختیار کنترلر قرار داد. همانند کد ذیل:
[<HandleError>]
type BooksController(repository : BooksRepository) =
    inherit Controller()
    new() = new BooksController(BooksRepository())
    member this.Index () =
        repository.GetAll()
        |> this.View
در کد‌های بالا ابتدا وابستگی به BookRepository در سازنده BookController تعیین شد. سپس با استفاده از سازنده پیش فرض، یک وهله از وابستگی مورد نظر ایجاد و در اختیار سازنده کنترلر قرار گرفت(همانند استفاده از کلمه this در سازنده کلاس‌های #C). با فراخوانی تابع GetAll  داده‌های مورد نظر از database تامین خواهد شد.

نکته : تنظیمات مروط به ConnectionString را فراموش نکنید:
<add name="FsMvcAppExample"
     connectionString="YOUR CONNECTION STRING"
     providerName="System.Data.SqlClient" />
موفق باشید.

مطالب
ارتقاء به Entity framework 6 و استفاده از بانک‌های اطلاعاتی غیر از SQL Server
برای ارتقاء برنامه‌های قدیمی به EF 6 (که با دات نت 4 به بعد سازگار است) دو حالت استفاده از نیوگت را در حین افزودن ارجاعات لازم به کتابخانه‌های مرتبط با EF باید مدنظر داشت:
الف) از نیوگت استفاده کرده‌اید
در این حالت فقط کافی است کنسول پاورشل نیوگت را در VS.NET گشوده و دستور update-package را صادر کنید. (1) به صورت خودکار آخرین نگارش EF دریافت شده و (2) همچنین فایل کانفیگ برنامه برای افزودن و به روز رسانی تعاریف مرتبط با نگارش 6 به روز گردیده و (3) همچنین اسمبلی اضافی و قدیمی System.Data.Entity.dll نیز حذف خواهد شد.

ب) اگر از نیوگت استفاده نکرده‌اید
ابتدا یک فایل متنی ساده را به نام packages.config با محتوای ذیل به پروژه خود اضافه کنید.
 <?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="EntityFramework" version="5.0.0" targetFramework="net40" />
</packages>
سپس بر روی نام Solution در VS.NET کلیک راست کرده و گزینه فعال سازی Restore بسته‌های نیوگت را فعال کنید (انتخاب گزینه Enable NuGet Package Restore). در ادامه یکبار برنامه را Build کنید تا پوشه packages به صورت خودکار از اینترنت دریافت و بازسازی شود. اکنون دستور update-package را در کنسول پاورشل نیوگت صادر کنید. همان مراحل قسمت الف تکرار خواهند شد.

لازم به ذکر است، اگر پروژه شما از چندین زیر پروژه تشکیل شده است که هر کدام نیز ارجاعی را به اسمبلی EF دارند، باید فایل packages.config فوق را به این زیر پروژه‌ها نیز اضافه کنید. دستور update-package، زیر پروژه‌ها را نیز اسکن کرده و تمام ارجاعات لازم را به صورت خودکار به روز می‌کند. همچنین اسمبلی‌های قدیمی اضافی را نیز حذف خواهد کرد. به این ترتیب با تداخل نگارش‌های قدیمی و جدید EF مواجه نخواهید شد.


مشکلاتی که ممکن است با آن‌ها مواجه شوید:

الف) برنامه کامپایل نمی‌شود
تنها تغییری که جهت کامپایل برنامه باید انجام دهید، استفاده از فضاهای نام جدید بجای فضاهای قدیمی موجود در اسمبلی منسوخ و حذف شده System.Data.Entity.dll است. خود VS.NET قابلیت یافتن فضاهای نام مرتبط را دارد و یا اگر از Resharper نیز استفاده می‌کنید، این قابلیت بهبود یافته است. در کل مثلا System.Data.EntityState شده است System.Data.Entity.EntityState و امثال آن که به روز رسانی آن‌ها نکته خاصی ندارد .


ب) پروایدر بانک اطلاعاتی مورد استفاده یافت نمی‌شود
به صورت پیش فرض فقط پروایدر SQL Server به همراه بسته EF 6 است. حتی پروایدر SQL Server CE نیز با آن ارائه نمی‌شود. اگر از SQL Server CE استفاده کرده‌اید، باید دستور ذیل را نیز پس از نصب EF 6 صادر کنید:
 PM> Install-Package EntityFramework.SqlServerCompact
تا با خطای ذیل مواجه نشوید:
 No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SqlServerCe.4.0'.
Make sure the provider is registered in the 'entityFramework' section of the application config file.
See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.
استفاده از نیوگت به روشی که عنوان شد، فایل کانفیگ برنامه شما را جهت افزودن تعاریف پروایدرهای لازم، به روز می‌کند و این مورد در EF 6 الزامی است (حتما باید تعریف پروایدر در فایل کانفیگ موجود باشد).


ج) خطای عدم وجود کلید خارجی جدول Migration را دریافت می‌کنید
 The foreign key constraint does not exist. [ PK_dbo.__MigrationHistory ]
تا EF 5 نام کلید اصلی جدول MigrationHistory به صورت PK___MigrationHistory می‌باشد.
 ALTER TABLE [__MigrationHistory] ADD CONSTRAINT [PK___MigrationHistory] PRIMARY KEY ([MigrationId]);
در EF 6 این نام شده است PK_dbo.__MigrationHistory
برای حل این مشکل تنها کافی است دستورات ذیل را یکبار بر روی بانک اطلاعاتی خود صادر کنید تا نام مورد نظر به عنوان کلید اصلی جدول migration اضافه شود؛ در غیراینصورت اصلا برنامه اجرا نخواهد شد:
 ALTER TABLE [__MigrationHistory] drop CONSTRAINT [PK___MigrationHistory];
ALTER TABLE [__MigrationHistory] ADD CONSTRAINT [PK_dbo.__MigrationHistory] PRIMARY KEY (MigrationId);
البته یکبار برنامه را اجرا کنید. اگر خطای نبود کلید اصلی یاد شده صادر شد، آنگاه دو دستور فوق را اجرا نمائید.