نظرات مطالب
ایجاد ServiceLocator با استفاده از Ninject
سلام؛ وقتی دستور
 Install-Package CommonServiceLocator.NinjectAdapter
را وارد میکنم خطای زیر ظاهر می‌شود:
 Install-Package : Unable to find package 'CommonServiceLocator.NinjectAdapter'.
At line:1 char:16
+ Install-Package <<<<  CommonServiceLocator.NinjectAdapter
+ CategoryInfo : NotSpecified: (:) [Install-Package], InvalidOperationException
+ FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.PowerShell.Commands.InstallPackageCommand
نمی‌تونم اسمبلی NinjetAdapter را نصب کنم. میشه لطفا در این خصوص بیشتر توضیح بدید و بفرمایید این dll رو از کجا باید دانلود کنم!
مطالب
EF Code First #2

در قسمت قبل با تنظیمات و قراردادهای ابتدایی EF Code first آشنا شدیم، هرچند این تنظیمات حجم کدنویسی ابتدایی راه اندازی سیستم را به شدت کاهش می‌دهند، اما کافی نیستند. در این قسمت نگاهی سطحی و مقدماتی خواهیم داشت بر امکانات مهیا جهت تنظیم ویژگی‌های مدل‌های برنامه در EF Code first.

تنظیمات EF Code first توسط اعمال متادیتای خواص

اغلب متادیتای مورد نیاز جهت اعمال تنظیمات EF Code first در اسمبلی System.ComponentModel.DataAnnotations.dll قرار دارند. بنابراین اگر مدل‌های خود را در اسمبلی و پروژه class library جداگانه‌ای تعریف و نگهداری می‌کنید (مثلا به نام DomainClasses)، نیاز است ابتدا ارجاعی را به این اسمبلی به پروژه جاری اضافه نمائیم. همچنین تعدادی دیگر از متادیتای قابل استفاده در خود اسمبلی EntityFramework.dll قرار دارند. بنابراین در صورت نیاز باید ارجاعی را به این اسمبلی نیز اضافه نمود.
همان مثال قبل را در اینجا ادامه می‌دهیم. دو کلاس Blog و Post در آن تعریف شده (به این نوع کلاس‌ها POCO – the Plain Old CLR Objects نیز گفته می‌شود)، به همراه کلاس Context که از کلاس DbContext مشتق شده است. ابتدا دیتابیس قبلی را دستی drop کنید. سپس در کلاس Blog، خاصیت public int Id را مثلا به public int MyTableKey تغییر دهید و پروژه را اجرا کنید. برنامه بلافاصله با خطای زیر متوقف می‌شود:

One or more validation errors were detected during model generation:
\tSystem.Data.Entity.Edm.EdmEntityType: : EntityType 'Blog' has no key defined.

زیرا EF Code first در این کلاس خاصیتی به نام Id یا BlogId را نیافته‌است و امکان تشکیل Primary key جدول را ندارد. برای رفع این مشکل تنها کافی است ویژگی Key را به این خاصیت اعمال کنیم:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace EF_Sample01.Models
{
public class Blog
{
[Key]
public int MyTableKey { set; get; }

همچنین تعدادی ویژگی دیگر مانند MaxLength و Required را نیز می‌توان بر روی خواص کلاس اعمال کرد:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace EF_Sample01.Models
{
public class Blog
{
[Key]
public int MyTableKey { set; get; }

[MaxLength(100)]
public string Title { set; get; }

[Required]
public string AuthorName { set; get; }

public IList<Post> Posts { set; get; }
}
}

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

CREATE TABLE [dbo].[Blogs](
[MyTableKey] [int] IDENTITY(1,1) NOT NULL,
[Title] [nvarchar](100) NULL,
[AuthorName] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_Blogs] PRIMARY KEY CLUSTERED
(
[MyTableKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

همانطور که ملاحظه می‌کنید در اینجا طول فیلد Title به 100 تنظیم شده است و همچنین فیلد AuthorName اینبار NOT NULL است. به علاوه primary key نیز بر اساس ویژگی Key اعمالی تعیین شده است.
البته برای اجرای کدهای تغییر کرده مدل، فعلا بانک اطلاعاتی قبلی را دستی می‌توان حذف کرد تا بتوان به ساختار جدید رسید. در مورد جزئیات مبحث DB Migration در قسمت‌های بعدی مفصلا بحث خواهد شد.

ب) اعتبار سنجی اطلاعات پیش از ارسال کوئری به بانک اطلاعاتی
برای مثال اگر در حین تعریف وهله‌ای از کلاس Blog، خاصیت AuthorName مقدار دهی نگردد، پیش از اینکه رفت و برگشتی به بانک اطلاعاتی صورت گیرد، یک validation error را دریافت خواهیم کرد. یا برای مثال اگر طول اطلاعات خاصیت Title بیش از 100 حرف باشد نیز مجددا در حین ثبت اطلاعات، یک استثنای اعتبار سنجی را مشاهده خواهیم کرد. البته امکان تعریف پیغام‌های خطای سفارشی نیز وجود دارد. برای این حالت تنها کافی است پارامتر ErrorMessage این ویژگی‌ها را مقدار دهی کرد. برای مثال:
[Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")]
public string AuthorName { set; get; }

نکته‌ی مهمی که در اینجا وجود دارد، وجود یک اکوسیستم هماهنگ و سازگار است. این نوع اعتبار سنجی هم با EF Code first هماهنگ است و هم برای مثال در ASP.NET MVC به صورت خودکار جهت اعتبار سنجی سمت سرور و کلاینت یک مدل می‌تواند مورد استفاده قرار گیرد و مفاهیم و روش‌های مورد استفاده در آن نیز یکی است.


تنظیمات EF Code first به کمک Fluent API

اگر علاقمند به استفاده از متادیتا، جهت تعریف قیود و ویژگی‌های خواص کلاس‌های مدل خود نیستید، روش دیگری نیز در EF Code first به نام Fluent API تدارک دیده شده است. در اینجا امکان تعریف همان ویژگی‌ها توسط کدنویسی نیز وجود دارد، به علاوه اعمال قیود دیگری که توسط متادیتای مهیا قابل تعریف نیستند.
محل تعریف این قیود، کلاس Context که از کلاس DbContext مشتق شده است، می‌باشد و در اینجا، کار با تحریف متد OnModelCreating شروع می‌شود:

using System.Data.Entity;
using EF_Sample01.Models;

namespace EF_Sample01
{
public class Context : DbContext
{
public DbSet<Blog> Blogs { set; get; }
public DbSet<Post> Posts { set; get; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasKey(x => x.MyTableKey);
modelBuilder.Entity<Blog>().Property(x => x.Title).HasMaxLength(100);
modelBuilder.Entity<Blog>().Property(x => x.AuthorName).IsRequired();

base.OnModelCreating(modelBuilder);
}
}
}

به کمک پارامتر modelBuilder، امکان دسترسی به متدهای تنظیم کننده ویژگی‌های خواص یک مدل یا موجودیت وجود دارد. در اینجا چون می‌توان متدها را به صورت یک زنجیره به هم متصل کرد و همچنین حاصل نهایی شبیه به جمله بندی انگلیسی است، به آن Fluent API یا API روان نیز گفته می‌شود.
البته در این حالت امکان تعریف ErrorMessage وجود ندارد و برای این منظور باید از همان data annotations استفاده کرد.


نحوه مدیریت صحیح تعاریف نگاشت‌ها به کمک Fluent API

OnModelCreating محل مناسبی جهت تعریف حجم انبوهی از تنظیمات کلاس‌های مختلف مدل‌های برنامه نیست. در حد سه چهار سطر مشکلی ندارد اما اگر بیشتر شد بهتر است از روش زیر استفاده شود:

using System.Data.Entity;
using EF_Sample01.Models;
using System.Data.Entity.ModelConfiguration;

namespace EF_Sample01
{
public class BlogConfig : EntityTypeConfiguration<Blog>
{
public BlogConfig()
{
this.Property(x => x.Id).HasColumnName("MyTableKey");
this.Property(x => x.RowVersion).HasColumnType("Timestamp");
}
}


با ارث بری از کلاس EntityTypeConfiguration،‌ می‌توان به ازای هر کلاس مدل، تنظیمات را جداگانه انجام داد. به این ترتیب اصل SRP یا Single responsibility principle نقض نخواهد شد. سپس برای استفاده از این کلاس‌های Config تک مسئولیتی به نحو زیر می‌توان اقدام کرد:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new BlogConfig());




نحوه تنظیمات ابتدایی نگاشت کلاس‌ها به بانک اطلاعاتی در EF Code first

الزامی ندارد که EF Code first حتما با یک بانک اطلاعاتی از نو تهیه شده بر اساس پیش فرض‌های آن کار کند. در اینجا می‌توان از بانک‌های اطلاعاتی موجود نیز استفاده کرد. اما در این حالت نیاز خواهد بود تا مثلا نام جدولی خاص با کلاسی مفروض در برنامه، یا نام فیلدی خاص که مطابق استانداردهای نامگذاری خواص در سی شارپ تعریف نشده، با خاصیتی در یک کلاس تطابق داده شوند. برای مثال اینبار تعاریف کلاس Blog را به نحو زیر تغییر دهید:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace EF_Sample01.Models
{
[Table("tblBlogs")]
public class Blog
{
[Column("MyTableKey")]
public int Id { set; get; }

[MaxLength(100)]
public string Title { set; get; }

[Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")]
public string AuthorName { set; get; }

public IList<Post> Posts { set; get; }

[Timestamp]
public byte[] RowVersion { set; get; }
}
}

در اینجا فرض بر این است که نام جدول متناظر با کلاس Blog در بانک اطلاعاتی مثلا tblBlogs است و نام خاصیت Id در بانک اطلاعاتی مساوی فیلدی است به نام MyTableKey. چون نام خاصیت را مجددا به Id تغییر داده‌ایم، دیگر ضرورتی به ذکر ویژگی Key وجود نداشته است. برای تعریف این دو از ویژگی‌های Table و Column جهت سفارشی سازی نام‌های خواص و کلاس استفاده شده است.
یا اگر در کلاس خود خاصیتی محاسبه شده بر اساس سایر خواص، تعریف شده است و قصد نداریم آن‌را به فیلدی در بانک اطلاعاتی نگاشت کنیم، می‌توان از ویژگی NotMapped برای مزین سازی و تعریف آن کمک گرفت.
به علاوه اگر از نام پیش فرض کلید خارجی تشکیل شده خرسند نیستید می‌توان به کمک ویژگی ForeignKey، نسبت به تعریف مقداری جدید مطابق تعاریف یک بانک اطلاعاتی موجود، اقدام کرد.
همچنین خاصیت دیگری به نام RowVersion در اینجا اضافه شده که با ویژگی TimeStamp مزین گردیده است. از این خاصیت ویژه برای بررسی مسایل همزمانی ثبت اطلاعات در EF استفاده می‌شود. به علاوه بانک اطلاعاتی می‌تواند به صورت خودکار آن‌را در حین ثبت مقدار دهی کند.
تمام این تغییرات را به کمک Fluent API نیز می‌توان انجام داد:

modelBuilder.Entity<Blog>().ToTable("tblBlogs");
modelBuilder.Entity<Blog>().Property(x => x.Id).HasColumnName("MyTableKey");
modelBuilder.Entity<Blog>().Property(x => x.RowVersion).HasColumnType("Timestamp");



تبدیل پروژه‌های قدیمی EF به کلاس‌های EF Code first به صورت خودکار

روش متداول کار با EF از روز اول آن، مهندسی معکوس خودکار اطلاعات یک بانک اطلاعاتی و تبدیل آن به یک فایل EDMX بوده است. هنوز هم می‌توان از این روش در اینجا نیز بهره جست. برای مثال اگر قصد دارید یک پروژه قدیمی را تبدیل به نمونه جدید Code first کنید، یا یک بانک اطلاعاتی موجود را مهندسی معکوس کنید، بر روی پروژه در Solution explorer کلیک راست کرده و گزینه Add|New Item را انتخاب کنید. سپس از صفحه ظاهر شده، ADO.NET Entity data model را انتخاب کرده و در ادامه گزینه «Generate from database» را انتخاب کنید. این روال مرسوم کار با EF Database first است.
پس از اتمام کار به entity data model designer مراجعه کرده و بر روی صفحه کلیک راست نمائید. از منوی ظاهر شده گزینه «Add code generation item» را انتخاب کنید. سپس در صفحه باز شده از لیست قالب‌های موجود، گزینه «ADO.NET DbContext Generator» را انتخاب نمائید. این گزینه به صورت خودکار اطلاعات فایل EDMX قدیمی یا موجود شما را تبدیل به کلاس‌های مدل Code first معادل به همراه کلاس DbContext معرف آن‌ها خواهد کرد.

روش دیگری نیز برای انجام اینکار وجود دارد. نیاز است افزونه‌ی به نام Entity Framework Power Tools را دریافت کنید. پس از نصب، از منوی Entity Framework آن گزینه‌ی «Reverse Engineer Code First» را انتخاب نمائید. در اینجا می‌توان مشخصات اتصال به بانک اطلاعاتی را تعریف و سپس نسبت به تولید خودکار کدهای مدل‌ها و DbContext مرتبط اقدام کرد.



استراتژی‌های مقدماتی تشکیل بانک اطلاعاتی در EF Code first

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

System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseIfModelChanges<Context>());
// or
System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseAlways<Context>());

می‌توان بانک اطلاعاتی را در صورت تغییر اطلاعات یک مدل به صورت خودکار drop کرده و نسبت به ایجاد نمونه‌ای جدید اقدام کرد (DropCreateDatabaseIfModelChanges)؛ یا در حین آزمایش برنامه همیشه (DropCreateDatabaseAlways) با شروع برنامه، ابتدا باید بانک اطلاعاتی drop شده و سپس نمونه جدیدی تولید گردد.
محل فراخوانی این دستور هم باید در نقطه آغازین برنامه، پیش از وهله سازی اولین DbContext باشد. مثلا در برنامه‌های وب در متد Application_Start فایل global.asax.cs یا در برنامه‌های WPF در متد سازنده کلاس App می‌توان بانک اطلاعاتی را آغاز نمود.
البته الزامی به استفاده از کلاس‌های DropCreateDatabaseIfModelChanges یا DropCreateDatabaseAlways وجود ندارد. می‌توان با پیاده سازی اینترفیس IDatabaseInitializer از نوع کلاس Context تعریف شده در برنامه، همان عملیات را شبیه سازی کرد یا سفارشی نمود:

public class MyInitializer : IDatabaseInitializer<Context>
{
public void InitializeDatabase(Context context)
{
if (context.Database.Exists() ||
context.Database.CompatibleWithModel(throwIfNoMetadata: false))
context.Database.Delete();

context.Database.Create();
}
}

سپس برای استفاده از این کلاس در ابتدای برنامه، خواهیم داشت:

System.Data.Entity.Database.SetInitializer(new MyInitializer());


نکته:
اگر از یک بانک اطلاعاتی موجود استفاده می‌کنید (محیط کاری) و نیازی به پیش فرض‌های EF Code first ندارید و همچنین این بانک اطلاعاتی نیز نباید drop شود یا تغییر کند، می‌توانید تمام این پیش فرض‌ها را با دستور زیر غیرفعال کنید:

Database.SetInitializer<Context>(null);

بدیهی است این دستور نیز باید پیش از ایجاد اولین وهله از شیء DbContext فراخوانی شود.


همچنین باید درنظر داشت که در آخرین نگارش‌های پایدار EF Code first، این موارد بهبود یافته‌اند و مبحثی تحت عنوان DB Migration ایجاد شده است تا نیازی نباشد هربار بانک اطلاعاتی drop شود و تمام اطلاعات از دست برود. می‌توان صرفا تغییرات کلاس‌ها را به بانک اطلاعاتی اعمال کرد که به صورت جداگانه، در قسمتی مجزا بررسی خواهد شد. به این ترتیب دیگر نیازی به drop بانک اطلاعاتی نخواهد بود. به صورت پیش فرض در صورت از دست رفتن اطلاعات یک استثناء را سبب خواهد شد (که توسط برنامه نویس قابل تنظیم است) و در حالت خودکار یا دستی با تنظیمات ویژه قابل اعمال است.



تنظیم استراتژی‌های آغاز بانک اطلاعاتی در فایل کانفیگ برنامه

الزامی ندارد که حتما متد Database.SetInitializer را دستی فراخوانی کنیم. با اندکی تنظیم فایل‌های app.config و یا web.config نیز می‌توان نوع استراتژی مورد استفاده را تعیین کرد:

<appSettings>
<add key="DatabaseInitializerForType MyNamespace.MyDbContextClass, MyAssembly"
value="MyNamespace.MyInitializerClass, MyAssembly" />
</appSettings>

<appSettings>
<add key="DatabaseInitializerForType MyNamespace.MyDbContextClass, MyAssembly"
value="Disabled" />
</appSettings>

یکی از دو حالت فوق باید در قسمت appSettings فایل کانفیگ برنامه تنظیم شود. حالت دوم برای غیرفعال کردن پروسه آغاز بانک اطلاعاتی و اعمال تغییرات به آن، بکار می‌رود.
برای نمونه در مثال جاری، جهت استفاده از کلاس MyInitializer فوق، می‌توان از تنظیم زیر نیز استفاده کرد:

<appSettings>
<add key="DatabaseInitializerForType EF_Sample01.Context, EF_Sample01"
value="EF_Sample01.MyInitializer, EF_Sample01" />
</appSettings>



اجرای کدهای ویژه در حین تشکیل یک بانک اطلاعاتی جدید

امکان سفارشی سازی این آغاز کننده‌های پیش فرض نیز وجود دارد. برای مثال:

public class MyCustomInitializer : DropCreateDatabaseIfModelChanges<Context>
{
protected override void Seed(Context context)
{
context.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
context.Database.ExecuteSqlCommand("CREATE INDEX IX_title ON tblBlogs (title)");
base.Seed(context);
}
}

در اینجا با ارث بری از کلاس DropCreateDatabaseIfModelChanges یک آغاز کننده سفارشی را تعریف کرده‌ایم. سپس با تحریف متد Seed آن می‌توان در حین آغاز یک بانک اطلاعاتی، تعدادی رکورد پیش فرض را به آن افزود. کار ذخیره سازی نهایی در متد base.Seed انجام می‌شود.
برای استفاده از آن اینبار در حین فراخوانی متد System.Data.Entity.Database.SetInitializer، از کلاس MyCustomInitializer استفاده خواهیم کرد.
و یا توسط متد context.Database.ExecuteSqlCommand می‌توان دستورات SQL را مستقیما در اینجا اجرا کرد. عموما دستوراتی در اینجا مدنظر هستند که توسط ORMها پشتیبانی نمی‌شوند. برای مثال تغییر collation یک ستون یا افزودن یک ایندکس و مواردی از این دست.


سطح دسترسی مورد نیاز جهت فراخوانی متد Database.SetInitializer

استفاده از متدهای آغاز کننده بانک اطلاعاتی نیاز به سطح دسترسی بر روی بانک اطلاعاتی master را در SQL Server دارند (زیرا با انجام کوئری بر روی این بانک اطلاعاتی مشخص می‌شود، آیا بانک اطلاعاتی مورد نظر پیشتر تعریف شده است یا خیر). البته این مورد حین کار با SQL Server CE شاید اهمیتی نداشته باشد. بنابراین اگر کاربری که با آن به بانک اطلاعاتی متصل می‌شویم سطح دسترسی پایینی دارد نیاز است Persist Security Info=True را به رشته اتصالی اضافه کرد. البته این مورد را پس از انجام تغییرات بر روی بانک اطلاعاتی جهت امنیت بیشتر حذف کنید (یا به عبارتی در محیط کاری Persist Security Info=False باید باشد).

Server=(local);Database=yourDatabase;User ID=yourDBUser;Password=yourDBPassword;Trusted_Connection=False;Persist Security Info=True


تعیین Schema و کاربر فراخوان دستورات SQL

در EF Code first به صورت پیش فرض همه چیز بر مبنای کاربری با دسترسی مدیریتی یا dbo schema در اس کیوال سرور تنظیم شده است. اما اگر کاربر خاصی برای کار با دیتابیس تعریف گردد که در هاست‌های اشتراکی بسیار مرسوم است، دیگر از دسترسی مدیریتی dbo خبری نخواهد بود. اینبار نام جداول ما بجای dbo.tableName مثلا someUser.tableName می‌باشند و عدم دقت به این نکته، اجرای برنامه را غیرممکن می‌سازد.
برای تغییر و تعیین صریح کاربر متصل شده به بانک اطلاعاتی اگر از متادیتا استفاده می‌کنید، روش زیر باید بکارگرفته شود:

[Table("tblBlogs", Schema="someUser")]    
public class Blog

و یا در حالت بکارگیری Fluent API به نحو زیر قابل تنظیم است:

modelBuilder.Entity<Blog>().ToTable("tblBlogs", schemaName:"someUser");






نظرات مطالب
بررسی تصویر امنیتی (Captcha) سایت - قسمت دوم
در تمام قسمت‌های رمزنگاری شده سایت جاری از یک salt با طول عمر یک روز استفاده می‌شود. این salt به کلید رمزنگاری اطلاعات اضافه می‌شود. بنابراین اطلاعات رمزنگاری شده توسط آن کلید، فقط در طی روز جاری قابل رمزگشایی خواهند بود.
برای مثال لینک یک پیغام خصوصی سایت را در جایی ذخیره کنید. تا پایان روز کار می‌کند. روز بعد به صورت خودکار منقضی شده (چون salt فردا چیز دیگری است) و اطلاعات روز قبل توسط کلید جدید قابل رمزگشایی نیست. به این ترتیب شخص نیاز خواهد داشت تا لینک جدیدی را در صفحه مداخل مرتبط دریافت کند.
مطالب
EF Code First #15

EF Code first و بانک‌های اطلاعاتی متفاوت

در آخرین قسمت از سری EF Code first بد نیست نحوه استفاده از بانک‌های اطلاعاتی دیگری را بجز SQL Server نیز بررسی کنیم. در اینجا کلاس‌های مدل و کدهای مورد استفاده نیز همانند قسمت 14 است و تنها به ذکر تفاوت‌ها و نکات مرتبط اکتفاء خواهد شد.


حالت کلی پشتیبانی از بانک‌های اطلاعاتی مختلف توسط EF Code first

EF Code first با کلیه پروایدرهای تهیه شده برای ADO.NET 3.5 که پشتیبانی از EF را لحاظ کرده باشند،‌ به خوبی کار می‌کند. پروایدرهای مخصوص ADO.NET 4.0، تنها سه گزینه DeleteDatabase/CreateDatabase/DatabaseExists را نسبت به نگارش قبلی بیشتر دارند و EF Code first ویژگی‌های بیشتری را طلب نمی‌کند.
بنابراین اگر حین استفاده از پروایدر ADO.NET مخصوص بانک اطلاعاتی خاصی با پیغام «CreateDatabase is not supported by the provider» مواجه شدید، به این معنا است که این پروایدر برای دات نت 4 به روز نشده است. اما به این معنا نیست که با EF Code first کار نمی‌کند. فقط باید یک دیتابیس خالی از پیش تهیه شده را به برنامه معرفی کنید تا مباحث Database Migrations به خوبی کار کنند؛ یا اینکه کلا می‌توانید Database Migrations را خاموش کرده (متد Database.SetInitializer را با پارامتر نال فراخوانی کنید) و فیلدها و جداول را دستی ایجاد کنید.


استفاده از EF Code first با SQLite

برای استفاده از SQLite در دات نت ابتدا نیاز به پروایدر ADO.NET آن است: «مکان دریافت درایور‌های جدید SQLite مخصوص دات نت»
ضمن اینکه به نکته «استفاده از اسمبلی‌های دات نت 2 در یک پروژه دات نت 4» نیز باید دقت داشت.
و یکی از بهترین management studio هایی که برای آن تهیه شده: «SQLite Manager»
پس از دریافت پروایدر آن، ارجاعی را به اسمبلی System.Data.SQLite.dll به برنامه اضافه کنید.
سپس فایل کانفیگ برنامه را به نحو زیر تغییر دهید:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>

<connectionStrings>
<clear/>
<add name="Sample09Context"
connectionString="Data Source=CodeFirst.db"
providerName="System.Data.SQLite"/>
</connectionStrings>
</configuration>

همانطور که ملاحظه می‌کنید، تفاوت آن با قبل، تغییر connectionString و providerName است.
اکنون اگر همان برنامه قسمت قبل را اجرا کنیم به خطای زیر برخواهیم خورد:
«The given key was not present in the dictionary»
در این مورد هم توضیح داده شد. سه گزینه DeleteDatabase/CreateDatabase/DatabaseExists در پروایدر جاری SQLite برای دات نت وجود ندارد. به همین جهت نیاز است فایل «CodeFirst.db» ذکر شده در کانکشن استرینگ را ابتدا دستی درست کرد.
برای مثال از افزونه SQLite Manager استفاده کنید. ابتدا یک بانک اطلاعاتی خالی را درست کرده و سپس دستورات زیر را بر روی بانک اطلاعاتی اجرا کنید تا دو جدول خالی را ایجاد کند (در برگه Execute sql افزونه SQLite Manager):

CREATE TABLE [Payees](
[Id] [integer] PRIMARY KEY AUTOINCREMENT NOT NULL,
[Name] [text] NULL,
[CreatedOn] [datetime] NOT NULL,
[CreatedBy] [text] NULL,
[ModifiedOn] [datetime] NOT NULL,
[ModifiedBy] [text] NULL
);

CREATE TABLE [Bills](
[Id] [integer] PRIMARY KEY AUTOINCREMENT NOT NULL,
[Amount] [float](18, 2) NOT NULL,
[Description] [text] NULL,
[CreatedOn] [datetime] NOT NULL,
[CreatedBy] [text] NULL,
[ModifiedOn] [datetime] NOT NULL,
[ModifiedBy] [text] NULL,
[Payee_Id] [integer] NULL
);

سپس سطر زیر را نیز به ابتدای برنامه اضافه کنید:

Database.SetInitializer<Sample09Context>(null);

به این ترتیب database migrations خاموش می‌شود و اکنون برنامه بدون مشکل کار خواهد کرد.
فقط باید به یک سری نکات مانند نوع داده‌ها در بانک‌های اطلاعاتی مختلف دقت داشت. برای مثال integer در اینجا از نوع Int64 است؛ بنابراین در برنامه نیز باید به همین ترتیب تعریف شود تا نگاشت‌ها به درستی انجام شوند.

در کل تنها مشکل پروایدر فعلی SQLite عدم پشتیبانی از مباحث database migrations است. این مورد را خاموش کرده و تغییرات ساختار بانک اطلاعاتی را به صورت دستی به بانک اطلاعاتی اعمال کنید. بدون مشکل کار خواهد کرد.

البته اگر به دنبال پروایدری تجاری با پشتیبانی از آخرین نگارش EF Code first هستید، گزینه زیر نیز مهیا است:
http://devart.com/dotconnect/sqlite/
برای مثال اگر علاقمند به استفاده از حالت تشکیل بانک اطلاعاتی SQLite در حافظه هستید (با رشته اتصالی ویژه Data Source=:memory:;Version=3;New=True;)،‌ فعلا تنها گزینه مهیا استفاده از پروایدر تجاری فوق است؛ زیرا مبحث Database Migrations را به خوبی پشتیبانی می‌کند.



استفاده از EF Code first با SQL Server CE

قبلا در مورد «استفاده از SQL-CE به کمک NHibernate» مطلبی را در این سایت مطالعه کرده‌اید. سه مورد اول آن با EF Code first یکی است و تفاوتی نمی‌کند (یک سری بحث عمومی مشترک است). البته با یک تفاوت؛ در اینجا EF Code first قادر است یک بانک اطلاعاتی خالی SQL Server CE را به صورت خودکار ایجاد کند و نیازی نیست تا آن‌را دستی ایجاد کرد. مباحث database migrations و به روز رسانی خودکار ساختار بانک اطلاعاتی نیز در اینجا پشتیبانی می‌شود.
برای استفاده از آن ابتدا ارجاعی را به اسمبلی System.Data.SqlServerCe.dll قرار گرفته در مسیر Program Files\Microsoft SQL Server Compact Edition\v4.0\Desktop اضافه کنید.
سپس رشته اتصالی به بانک اطلاعاتی و providerName را به نحو زیر تغییر دهید:

<connectionStrings>
<clear/>
<add name="Sample09Context"
connectionString="Data Source=mydb.sdf;Password=1234;Encrypt Database=True"
providerName="System.Data.SqlServerCE.4.0"/>
</connectionStrings>


بدون نیاز به هیچگونه تغییری در کدهای برنامه، همین مقدار تغییر در تنظیمات ابتدایی برنامه برای کار با SQL Server CE کافی است.
ضمنا مشکلی هم با فیلد Identity در آخرین نگارش EF Code first وجود ندارد؛ برخلاف حالت database first آن که پیشتر این اجازه را نمی‌داد و خطای «Server-generated keys and server-generated values are not supported by SQL Server Compact» را ظاهر می‌کرد.



استفاده از EF Code first با MySQL

برای استفاده از EF Code first با MySQL (نگارش 5 به بعد البته) ابتدا نیاز است پروایدر مخصوص ADO.NET آن‌را دریافت کرد: (^)
که از EF نیز پشتیبانی می‌کند. پس از نصب آن، ارجاعی را به اسمبلی MySql.Data.dll قرار گرفته در مسیر Program Files\MySQL\MySQL Connector Net 6.5.4\Assemblies\v4.0 به پروژه اضافه نمائید.
سپس رشته اتصالی و providerName را به نحو زیر تغییر دهید:

<connectionStrings>
<clear/>
<add name="Sample09Context"
connectionString="Datasource=localhost; Database=testdb2; Uid=root; Pwd=123;"
providerName="MySql.Data.MySqlClient"/>
</connectionStrings>

<system.data>
<DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient"/>
<add name="MySQL Data Provider"
invariant="MySql.Data.MySqlClient"
description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
</DbProviderFactories>
</system.data>

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

با این تغییرات پس از اجرای برنامه قسمت قبل، به خطای زیر برخواهیم خورد:
The given key was not present in the dictionary

توضیحات این مورد با قسمت SQLite یکی است؛ به عبارتی نیاز است بانک اطلاعاتی testdb را دستی درست کرد. همچنین جداول و فیلدها را نیز باید دستی ایجاد کرد و database migrations را نیز باید خاموش کرد (پارامتر Database.SetInitializer را به نال مقدار دهی کنید).
برای این منظور یک دیتابیس خالی را ایجاد کرده و سپس دو جدول زیر را به آن اضافه کنید:

CREATE TABLE IF NOT EXISTS `bills` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Amount` float DEFAULT NULL,
`Description` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`CreatedOn` datetime NOT NULL,
`CreatedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`ModifiedOn` datetime NOT NULL,
`ModifiedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`Payee_Id` int(11) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `payees` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`CreatedOn` datetime NOT NULL,
`CreatedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
`ModifiedOn` datetime NOT NULL,
`ModifiedBy` varchar(400) CHARACTER SET utf8 COLLATE utf8_persian_ci NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci AUTO_INCREMENT=1 ;

پس از این تغییرات، برنامه بدون مشکل اجرا خواهد شد (ایجاد بانک اطلاعاتی خالی به همراه ایجاد ساختار جداول و خاموش کردن database migrations که توسط این پروایدر پشتیبانی نمی‌شود).

به علاوه پروایدر تجاری دیگری هم در سایت devart.com برای MySQL و EF Code first مهیا است که مباحث database migrations را به خوبی مدیریت می‌کند.


مشکل!
اگر به همین نحو برنامه را اجرا کنیم، فیلدهای یونیکد فارسی ثبت شده در MySQL با «??????? ?? ????» مقدار دهی خواهند شد و تنظیم CHARACTER SET utf8 COLLATE utf8_persian_ci نیز کافی نبوده است (این مورد با SQLite یا نگارش‌های مختلف SQL Server بدون مشکل کار می‌کند و نیاز به تنظیم اضافه‌تری ندارد):

ALTER TABLE `bills` DEFAULT CHARACTER SET utf8 COLLATE utf8_persian_ci

برای رفع این مشکل توصیه شده است که CharSet=UTF8 را به رشته اتصالی به بانک اطلاعاتی اضافه کنیم. اما در این حالت خطای زیر ظاهر می‌شود:
The provider did not return a ProviderManifestToken string
این مورد فقط به اشتباه بودن تعاریف رشته اتصالی بر می‌گردد؛‌ یا عدم پشتیبانی از تنظیم اضافه‌ای که در رشته اتصالی ذکر شده است.
مقدار صحیح آن دقیقا مساوی CHARSET=utf8 است (با همین نگارش و رعایت کوچکی و بزرگی حروف؛ مهم!):

<connectionStrings>
<clear/>
<add name="Sample09Context"
connectionString="Datasource=localhost; Database=testdb; Uid=root; Pwd=123;CHARSET=utf8"
providerName="MySql.Data.MySqlClient"/>
</connectionStrings>


به این ترتیب، مشکل ثبت عبارات یونیکد فارسی برطرف می‌شود (البته جدول هم بهتر است به DEFAULT CHARACTER SET utf8 COLLATE utf8_persian_ci تغییر پیدا کند؛ مطابق دستور Alter ایی که در بالا ذکر شد).

نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 2 - به روز رسانی ساختار بانک اطلاعاتی
یک نکته‌ی تکمیلی
ممکن است در حین اجرای عملیات تولید کلاس‌های Migrations در NET Core 3.0. به خطای «ارجاعی به اسمبلی Microsoft.EntityFrameworkCore.Design در پروژه‌ی آغازین برنامه وجود ندارد» بخورید. برای رفع آن نیازی نیست تا این ارجاع را به پروژه‌ی اصلی اضافه کنید. فقط سطر قبلی زیر را
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" 
                  Version="3.0.0-preview7.19362.6"
                  PrivateAssets="all" />
در فایل csproj مربوط به پروژه‌ی مجزای DataLayer که Context برنامه در آن قرار دارد یافته و خاصیت PrivateAssets آن‌را حذف کنید.
مطالب
امکان اجرای خودکار کدها در زمان بارگذاری اولیه‌ی یک اسمبلی در C# 9.0
C# 9.0 به همراه قابلیت جدیدی است به نام «module initializer» که در اصل متدی است که در زمان بارگذاری اولیه‌ی یک اسمبلی، به صورت خودکار اجرا می‌شود. عملکرد آن شبیه به سازنده‌های static کلاس‌ها است؛ اما بجای اعمال به یک کلاس، اینبار به کل اسمبلی اعمال می‌شود. این قابلیت از روزهای ابتدایی طراحی CLR وجود خارجی داشته‌، اما در C# 9.0، امکان استفاده‌ی عمومی از آن فراهم شده‌است.


روش تعریف یک module initializer

در مثال زیر، قالب ابتدایی یک ModuleInitializer را مشاهده می‌کنید:
namespace CS9Features
{
    using System.Runtime.CompilerServices;

    internal static class TestModuleInitializer
    {
        [ModuleInitializer]
        public static void MyModuleInitializer()
        {
            // put your module initializer here
        }
    }
}
متدی که قرار است به عنوان module initializer معرفی شود، باید مزین به ویژگی [ModuleInitializer] باشد و همچنین این متد باید دارای ویژگی‌های زیر نیز باشد:
- باید استاتیک باشد.
- باید بدون پارامتر باشد.
- باید خروجی آن void باشد.
- نباید به صورت جنریک تعریف شود.
- این متد باید در همان اسمبلی، قابل دسترسی باشد؛ یعنی سطح دسترسی آن باید یا public و یا internal باشد.
- نباید local function باشد.


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

به مثال زیر دقت کنید:
namespace CS9Features
{
    using System.Runtime.CompilerServices;

    internal static class TestModuleInitializer
    {
        [ModuleInitializer]
        public static void MyModuleInitializer1()
        {
            // put your module initializer here
        }

        [ModuleInitializer]
        public static void MyModuleInitializer2()
        {
            // put your module initializer here
        }
    }
}
در اینجا بیش از یک متد ModuleInitializer تعریف شده‌اند. اگر بر روی ابتدای هر کدام از این متدها یک break-point را قرار دهید، مشاهده خواهید کرد که کدهای آن‌ها پیش از شروع متد Main برنامه اجرا می‌شوند. همچنین نحوه‌ی اجرای این متدها همواره مشخص و ترتیبی است؛ از بالا به پایین.
این مورد یکی از مهم‌ترین تفاوت‌های module initializer‌ها با سازنده‌های static است. ترتیب اجرای سازنده‌های static مشخص نیست و بر اساس کدهای کلاینت و زمان دسترسی به کلاس‌های مختلف، سازنده‌ی استاتیک کلاس A می‌تواند پس از سازنده‌ی استاتیک کلاس B اجرا شود و یا برعکس. اما همواره نحوه‌ی اجرای module initializer‌ها مشخص و ترتیبی است و همچنین نیازی به فراخوانی آن‌ها توسط هیچ کلاینتی نیست.


موارد کاربرد module initializer‌ها

نمونه‌ی بسیار پرکاربرد module initializer ها، اجرای کدهایی پیش از شروع به اجرای آزمون‌های خودکار یک برنامه‌است؛ مانند کدهایی که یک بانک اطلاعاتی را ایجاد و مقدار دهی اولیه می‌کنند و پس از آن قرار است آزمایش‌های برنامه بر روی این بانک اطلاعاتی مشخص، اجرا شوند.
نظرات مطالب
Implementing second level caching in EF code first
اشکال کار را پیدا کردم
چون سیستم کش لایه دوم بر اساس پارامتر آخر عمل میکنه باعث میشه که دیتا به ازای پارامترهای مختلف یک داده خاص فقط از کش بازیابی کنه چون باعث میشه کلید یکسان برای همه ثبت بشه مثلا در دستور زیر
public Article GetById(int id)
{
   return articles
         .Where(e => e.Id == id)
         .Include(x => x.Category)
         .Include(x => x.Tags) //پارامتر در نظر گرفته شده برای تولید کلید کش 
         .Cacheable(x => x.FirstOrDefault());
}
قسمت Tags به عنوان پارامتر در نظر گرفته میشه که برای همه مقالات یکسان از آب در میاد
در صورتی که دستور به شکل زیر اصلاح بشه این مشکل رفع میشه چون در اون صورت Id مقاله به عنوان پارامتر در نظر گرفته میشه

public Article GetById(int id)
{
   return articles
         .Include(x => x.Category)
         .Include(x => x.Tags)
         .Where(e => e.Id == id) //پارامتر در نظر گرفته شده برای تولید کلید کش
         .Cacheable(x => x.FirstOrDefault());
}
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
یک نکته‌ی تکمیلی: چگونه پس از نصب SDK 3x جدید، بتوانیم همان پروژه‌ی قبلی را بدون به روز رسانی یا هیچگونه تغییری در آن، باز هم استفاده کنیم؟

با توجه به اینکه امضای یکسری از اینترفیس‌های نگارش 3x با 2x یکی نیست (مانند ILookupNormalizer)، پس از نصب SDK 3x، دیگر قادر به اجرای برنامه‌های 2x خود نخواهید شد؛ چون پروژه‌های NET Core. همواره از آخرین نگارش SDK نصب شده استفاده می‌کنند. برای قفل کردن شماره SDK یک Solution به نگارش 2x به صورت زیر عمل کنید:
dotnet --list-sdks
dotnet new globaljson --sdk-version 2.2.106
دستور اول لیست SDKهای نصب شده را نمایش می‌دهد و دستور دوم یک فایل global.json جدید را بر اساس شماره‌ای که از طریق اجرای دستور اول یافته‌اید، تولید می‌کند. این فایل باید در ریشه‌ی Solution قرار گیرد.
مطالب
افزودن خودکار کلاس های WebAPI و SignalR Hub به برنامه در حالت SelfHost
یکی از گزینه‌های میزبانی WebAPI و SignalR حالت SelfHost می‌باشد که روش آن قبلا در مطلب «نگاهی به گزینه‌های مختلف مهیای جهت میزبانی SignalR» توضیح داده شده است.

ابتدا نگاه کوچکی به یک مثال داشته باشیم:
هاب زیر را در نظر بگیرید.
public class MessageHub : Hub
    {
        public void NotifyAllClients()
        {
            Clients.All.Notify();
        } 
}
برای selfHsot کردن از یک برنامه‌ی کنسول استفاده می‌کنیم:
static void Main(string[] args)
        {
            const string baseAddress = "http://localhost:9000/"; // "http://*:9000/";
 
            using (var webapp = WebApp.Start<Startup>(baseAddress))
            {
                Console.WriteLine("Start app...");
 
                var hubConnection = new HubConnection(baseAddress);
                IHubProxy messageHubProxy = hubConnection.CreateHubProxy("messageHub");
 
                messageHubProxy.On("notify", () =>
                {
                    Console.WriteLine();
                    Console.WriteLine("Notified!");
                });
 
                hubConnection.Start().Wait();
 
                Console.WriteLine("Start signalr...");
 
                bool dontExit = true;
                while (dontExit)
                {
                    var key = Console.ReadKey();
                    if (key.Key == ConsoleKey.Escape) dontExit = false;
 
                    messageHubProxy.Invoke("NotifyAllClients");
                }
 
            }
        }
با کلاس start-up ذیل:
public partial class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var hubConfiguration = new HubConfiguration()
            {
                EnableDetailedErrors = true
            };
 
            appBuilder.MapSignalR(hubConfiguration);
 
            appBuilder.UseCors(CorsOptions.AllowAll);
 
 
        }
    }
اکنون اگر برنامه را اجرا کنیم، با زدن هر کلید در کنسول، یک پیغام چاپ می‌شود که نشان دهنده صحت کارکرد هاب پیام می‌باشد.
خوب؛ تا الان همه چیز درست کار میکند.

صورت مساله:
معمولا برای منظم کردن و مدیریت بهتر کدهای نرم افزار، آن‌ها را در پروژه‌های مجزا یا در واقع همان class library‌های مجزا نگاه داری میکنیم.
اکنون در برنامه‌ی فوق ، اگر کلاس messageHub را به یک class library دیگر منتقل کنیم و آن را به برنامه‌ی کنسول ارجاع دهیم و برنامه را مجدد اجرا کنیم، با خطای زیر مواجه می‌شویم:
{"StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:\r\n{\r\n  Date: Mon, 27 Oct 2014 09:36:48 GMT\r\n  Server: Microsoft-HTTPAPI/2.0\r\n  Content-Length: 0\r\n}"}
مشکل چیست؟
همانطور که در مطلب «نگاهی به گزینه‌های مختلف مهیای جهت میزبانی SignalR» عنوان شده‌است، «در حالت SelfHost بر خلاف روش asp.net hosting ، اسمبلی‌های ارجاعی برنامه اسکن نمی‌شوند» و طبیعتا مشکل رخ داده شده در بالا از اینجا ناشی می‌شود.

راه حل:
- این کار باید به صورت دستی انجام پذیرد. با افزودن کد زیر به ابتدای برنامه (قبل از شروع هر کدی) اسمبلی‌های مورد نظر افزوده می‌شوند:
AppDomain.CurrentDomain.Load(typeof(MessageHub).Assembly.FullName);
طبیعتا افزودن دستی هر اسمبلی مشکل و در خیلی مواقع ممکن است با خطای انسانی فراموش کردن مواجه شود!
کد خودکار زیر، میتواند تکمیل کننده‌ی راه حل بالا باشد:
class LoadAssemblyHelper
    {
        public static void Load(string searchPattern)
        {
            var path = Assembly.GetExecutingAssembly().Location;
            var entityAssemblies = Directory.GetFiles(Path.GetDirectoryName(path), searchPattern: searchPattern);
            var assemblyNames = entityAssemblies.Select(e => AssemblyName.GetAssemblyName(e)).ToList();
            assemblyNames.ToList().ForEach(e => AppDomain.CurrentDomain.Load(e));
        }
    }
و برای فراخوانی آن در ابتدای برنامه می‌نویسیم:
static void Main(string[] args)
        {
            //AppDomain.CurrentDomain.Load(typeof(MessageHub).Assembly.FullName);
            //AppDomain.CurrentDomain.Load(typeof(MessageController).Assembly.FullName);

            LoadAssemblyHelper.Load("myFramework.*.dll");

            const string baseAddress = "http://*:9000/";
            using (var webapp = WebApp.Start<Startup>(baseAddress))
            {
                ...
            }
        }

نکته‌ی مهم
 این خطا و راه حل آن، در مورد hub‌های signalr و هم controller‌های webapi صادق می‌باشد.
مطالب
رمزنگاری JWT و افزایش امنیت آن در ASP.NET Core
آموزش JSON Web Token (به اختصار JWT) و پیاده سازی آن در برنامه‌های ASP.NET Core درسایت موجود است.
توکن JWT در حالت عادی به صورت Base64 رمزنگاری می‌شود که این نوع رمزنگاری به راحتی قابل رمزگشایی و خواندن است. سایت‌های آنلاین زیادی برای رمزگشایی base64 موجود است؛ برای مثال کافی است توکن خود را در سایت jwt.io کپی کنید و به راحتی محتوای بدنه توکن (Payload) را مشاهده کنید.

پس توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد.
ساده‌ترین راه حل، رمزنگاری دستی بدنه توکن می‌باشد که مثلا بر اساس کلیدی (که فقط سمت سرور نگهداری و مراقبت می‌شود) توکن را رمزنگاری کرده و به هنگام خواندن، آن را با همان کلید رمزگشایی کنیم. ولی این روش ضمن استاندارد نبودن، مشکلات خاص خودش را دارد و نیاز به سفارشی سازی زیادی، هم به هنگام تولید توکن و هم به هنگام خواندن توکن دارد.
 اصولی‌ترین راه، استفاده از رمزنگاری توکن به روش JSON Web Encryption (یا به اختصار JWE) است که در آن مشابه روش بالا ولی به صورت استاندارد تعریف شده (و قابل فهم برای همه استفاده کنندگانی که با این استاندارد آشنایی دارند) است.
نکته :
  1. اگر از JWE استفاده نمی‌کنید، بهتر است اطلاعات حساسی مانند شماره تلفن کاربر (و شاید در مواردی حتی آیدی کاربر) را در بدنه توکن قرار ندهیم چرا که قابل خوانده شدن است (که در این صورت استفاده از Guid برای آیدی کاربر می تواند کمی مفید باشد چرا که حداقل آیدی بقیه کاربران قابل پیش بینی نمی‌باشد).
  2. توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد؛ ولی به لطف امضای (signature) آن، در برابر تغییر محتوا، ایمن است؛ چرا که در صورت تغییر محتوای آن، دیگر مقدار hash محتوا با امضای آن همخوانی نداشته و عملا از اعتبار ساقط می‌گردد.
برای رمزنگاری JWT باید در هر دو مرحله‌ی "تولید توکن" و "اعتبارسنجی توکن" کلید و الگوریتم لازم برای رمزنگاری را مشخص کنیم. بدین منظور در جایی که توکن را تولید می‌کنیم، خواهیم داشت :
var secretKey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey"); // must be 16 character or longer 
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature);

var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey"); //must be 16 character
var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encryptionkey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256);

var claims = new List<Claim>
{
   new Claim(ClaimTypes.Name, "UserName"), //user.UserName
   new Claim(ClaimTypes.NameIdentifier, "123"), //user.Id
};

var descriptor = new SecurityTokenDescriptor
{
   Issuer = _siteSetting.JwtSettings.Issuer,
   Audience = _siteSetting.JwtSettings.Audience,
   IssuedAt = DateTime.Now,
   NotBefore = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.NotBeforeMinutes),
   Expires = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.ExpirationMinutes),
   SigningCredentials = signingCredentials,
   EncryptingCredentials = encryptingCredentials,
   Subject = new ClaimsIdentity(claims)
};

var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(descriptor);
string encryptedJwt = tokenHandler.WriteToken(securityToken);
کد بالا، مانند کد تولید یک توکن jwt معمولی است؛ تنها تفاوت آن، ایجاد و معرفی شیء encryptingCredentials است.
در خط چهارم، آرایه بایتی کلید لازم برای رمزنگاری (encryptionkey) گرفته شده و از روی آن encryptingCredentials ایجاد شده‌است. این کلید باید 16 کاراکتر باشد؛ در غیر اینصورت به هنگام تولید توکن، خطا دریافت خواهید کرد. رمزنگاری توکن، توسط این کلید و الگوریتم مشخص شده انجام خواهد شد.
سپس شیء تولید شده، به خاصیت EncryptingCredentials کلاس SecurityTokenDescriptor معرفی شده‌است و  نهایتا متد tokenHandler.WriteToken توکن رمزنگاری شده‌ای را تولید می‌کند.
نتیجه کار این است که توکن تولید شده، بدون کلید مربوطه (که سمت سرور نگهداری می‌شود) قابل رمز گشایی نیست و اگر آن را در سایت jwt.io کپی کنید، جوابی دریافت نخواهید کرد.

در ادامه لازم است در مرحله اعتبار سنجی و رمزگشایی توکن در سمت سرور، کلید و الگوریتم لازم را به آن معرفی کنیم تا middleware مربوطه بتواند توکن دریافتی را رمزگشایی و سپس اعتبار سنجی کند. بدین منظور در متد ConfigureServices کلاس Startup.cs خواهیم داشت:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
   var secretkey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey");
   var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey");

   var validationParameters = new TokenValidationParameters
   {
      ClockSkew = TimeSpan.Zero, // default: 5 min
      RequireSignedTokens = true,

      ValidateIssuerSigningKey = true,
      IssuerSigningKey = new SymmetricSecurityKey(secretkey),

      RequireExpirationTime = true,
      ValidateLifetime = true,

      ValidateAudience = true, //default : false
      ValidAudience = "MyWebsite",

      ValidateIssuer = true, //default : false
      ValidIssuer = "MyWebsite",

      TokenDecryptionKey = new SymmetricSecurityKey(encryptionkey)
   };

   options.RequireHttpsMetadata = false;
   options.SaveToken = true;
   options.TokenValidationParameters = validationParameters;
});

کد بالا مانند کد فعال سازی احراز هویت توسط JWT معمولی در ASP.NET Core است؛ با این تفاوت که:

ابتدا آرایه بایتی همان کلید رمزنگاری (encryptionkey) که قبلا توکن را با آن رمزنگاری کرده بودیم، گرفته شده و سپس توسط مقداردهی خاصیت TokenDecryptionKey کلاس TokenValidationParameters، معرفی شده است. 

ولی شاید این سؤال برایتان پیش آید که چرا الگوریتم رمزنگاری مشخص نشده است؟ پس سرور از کجا می‌فهمد که این توکن بر اساس چه الگوریتمی رمزنگاری شده است؟ 

دلیل آن این است که به هنگام تولید توکن، اسم الگوریتم مربوطه، داخل بخش header توکن نوشته می‌شود. اگر تصویر قبل را مشاهده کنید مقدار header توکن به شرح زیر است.

{
  "alg": "A128KW",
  "enc": "A128CBC-HS256",
  "typ": "JWT"
}

پس سرور بر اساس این قسمت از توکن (header)، که هیچگاه رمزنگاری نمی‌شود، می‌فهمد که توسط چه الگوریتمی باید توکن را رمزگشایی کند که در اینجا A128CBC-HS256 (اختصار AES-128-CBC و HMAC-SHA256) است.

مثال کامل و قابل اجرای این مطلب را می‌توانید از این ریپازیتوری دریافت کنید.