آماده سازی یک مثال Self host
برای اینکه خروجیهای JSON را بهتر و بدون نیاز به ابزار خاصی مشاهده کنیم، میتوان یک پروژهی کنسول جدید را آغاز کرده و سپس آنرا تبدیل به Host مخصوص Web API کرد. برای اینکار تنها کافی است در کنسول پاور شل نیوگت دستور ذیل را صادر کنید:
PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; namespace WebApiSelfHostTests { public class UsersController : ApiController { public IEnumerable<User> GetAllUsers() { return new[] { new User{ Id = 1, Name = "User 1", Type = UserType.Admin }, new User{ Id = 2, Name = "User 2", Type = UserType.User } }; } public async Task<HttpResponseMessage> Post(HttpRequestMessage request) { var jsonContent = await request.Content.ReadAsStringAsync(); Console.WriteLine("JsonContent (Server Side): {0}", jsonContent); return new HttpResponseMessage(HttpStatusCode.Created); } } }
namespace WebApiSelfHostTests { public enum UserType { User, Admin, Writer } public class User { public int Id { set; get; } public string Name { set; get; } public UserType Type { set; get; } } }
using System.Web.Http; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Owin; namespace WebApiSelfHostTests { /// <summary> /// PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost /// </summary> public class Startup { public void Configuration(IAppBuilder appBuilder) { var config = new HttpConfiguration(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); appBuilder.UseWebApi(config); } } }
var server = WebApp.Start<Startup>(url: BaseAddress); Console.WriteLine("Press Enter to quit."); Console.ReadLine(); server.Dispose();
using (var client = new HttpClient()) { var response = client.GetAsync(BaseAddress + "api/users").Result; Console.WriteLine("Response: {0}", response); Console.WriteLine("JsonContent (Client Side): {0}", response.Content.ReadAsStringAsync().Result); }
JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":1},{"Id":2,"Name":"User 2","Type":0}]
تنظیمات JSON سمت سرور Web API
برای تغییر این خروجی، در سمت سرور تنها کافی است به کلاس Startup مراجعه و HttpConfiguration را به صورت ذیل تنظیم کنیم:
public class Startup { public void Configuration(IAppBuilder appBuilder) { var config = new HttpConfiguration(); config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { Converters = { new StringEnumConverter() } };
اینبار اگر برنامه را اجرا کنیم، چنین خروجی حاصل میگردد و در آن دیگر Type مساوی صفر نیست:
JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":"Admin"},{"Id":2,"Name":"User 2","Type":"User"}]
تنظیمات JSON سمت کلاینت Web API
اکنون در سمت کلاینت قصد داریم اطلاعات یک کاربر را با فرمت JSON به سمت سرور ارسال کنیم. روش متداول آن توسط کتابخانهی HttpClient، استفاده از متد PostAsJsonAsync است:
var user = new User { Id = 1, Name = "User 1", Type = UserType.Writer }; var client = new HttpClient(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = client.PostAsJsonAsync(BaseAddress + "api/users", user).Result; Console.WriteLine("Response: {0}", response);
JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":2}
var jsonMediaTypeFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { Converters = { new StringEnumConverter() } } }; var response = client.PostAsync(BaseAddress + "api/users", user, jsonMediaTypeFormatter).Result; Console.WriteLine("Response: {0}", response);
اینبار مقدار دریافتی در سمت سرور به صورت ذیل است و در آن، Type دیگر عددی نیست:
JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":"Writer"}
مثال کامل این بحث را از اینجا میتوانید دریافت کنید:
UsersController.zip
ASP.NET MVC #11
- یک روش متداول دیگر، ذخیره سازی تاریخ شمسی و میلادی با هم هست در یک جدول. این روش سازگاری بهتری با کوئریهای datetime توکار بانکهای اطلاعاتی داره. اما ... این کوئریها یکجا مشکل پیدا میکنند و آن هم گروه بندی بر اساس ماههای شمسی است که تطابقی با ماههای میلادی ندارند و اگر قرار باشد برای آن کوئری ویژهای بر اساس datetime میلادی نوشت، به کوئری بسیار پیچیده و کندی خواهیم رسید. اما گروه بندی بر اساس ماههای مقادیر فیلد شمسی ذخیره شده بسیار سریع است (مثلا تهیه گزارش لیست خلاصه جمع حقوقی تمامی کارکنان در یکسال، گروه بندی شده بر اساس ماههای شمسی؛ ... آنهم فقط با نوشتن یک کوئری ساده و نه بیشتر).
- روش دیگر نوشتن یک TextBoxFor سفارشی است که تاریخ میلادی دریافت کند و شمسی نمایش دهد. بعد از ترکیب Model binder فوق استفاده شود برای دریافت مقدار نهایی آن.
- تنظیمات پیش فرض باید تغییر کنند تا کلمات عبور حداقل 10 کاراکتر باشند
- کلمه عبور حداقل یک عدد و یک کاراکتر ویژه باید داشته باشد
- امکان استفاده از 5 کلمه عبور اخیری که ثبت شده وجود ندارد
ایجاد اپلیکیشن جدید
در پنجره Solution Explorer روی نام پروژه کلیک راست کنید و گزینه Manage NuGet Packages را انتخاب کنید. به قسمت Update بروید و تمام انتشارات جدید را در صورت وجود نصب کنید.
بگذارید تا به روند کلی ایجاد کاربران جدید در اپلیکیشن نگاهی بیاندازیم. این به ما در شناسایی نیازهای جدیدمان کمک میکند. در پوشه Controllers فایلی بنام AccountController.cs وجود دارد که حاوی متدهایی برای مدیریت کاربران است.
- کنترلر Account از کلاس UserManager استفاده میکند که در فریم ورک Identity تعریف شده است. این کلاس به نوبه خود از کلاس دیگری بنام UserStore استفاده میکند که برای دسترسی و مدیریت دادههای کاربران استفاده میشود. در مثال ما این کلاس از Entity Framework استفاده میکند که پیاده سازی پیش فرض است.
- متد Register POST یک کاربر جدید میسازد. متد CreateAsync به طبع متد 'ValidateAsync' را روی خاصیت PasswordValidator فراخوانی میکند تا کلمه عبور دریافتی اعتبارسنجی شود.
var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); }
قانون 1: کلمههای عبور باید حداقل 10 کاراکتر باشند
- مقدار حداقل کاراکترهای کلمه عبور به دو شکل میتواند تعریف شود. راه اول، تغییر کنترلر Account است. در متد سازنده این کنترلر کلاس UserManager وهله سازی میشود، همینجا میتوانید این تغییر را اعمال کنید. راه دوم، ساختن کلاس جدیدی است که از UserManager ارث بری میکند. سپس میتوان این کلاس را در سطح global تعریف کرد. در پوشه IdentityExtensions کلاس جدیدی با نام ApplicationUserManager بسازید.
public class ApplicationUserManager : UserManager<ApplicationUser> { public ApplicationUserManager(): base(new UserStore<ApplicationUser>(new ApplicationDbContext())) { PasswordValidator = new MinimumLengthValidator (10); } }
- حال باید کلاس ApplicationUserManager را در کنترلر Account استفاده کنیم. متد سازنده و خاصیت UserManager را مانند زیر تغییر دهید.
public AccountController() : this(new ApplicationUserManager()) { } public AccountController(ApplicationUserManager userManager) { UserManager = userManager; } public ApplicationUserManager UserManager { get; private set; }
- اپلیکیشن را اجرا کنید و سعی کنید کاربر محلی جدیدی ثبت نمایید. اگر کلمه عبور وارد شده کمتر از 10 کاراکتر باشد پیغام خطای زیر را دریافت میکنید.
قانون 2: کلمههای عبور باید حداقل یک عدد و یک کاراکتر ویژه داشته باشند
- در پوشه IdentityExtensions کلاس جدیدی بنام CustomPasswordValidator بسازید و اینترفیس مذکور را پیاده سازی کنید. از آنجا که نوع کلمه عبور رشته (string) است از <IIdentityValidator<string استفاده میکنیم.
public class CustomPasswordValidator : IIdentityValidator<string> { public int RequiredLength { get; set; } public CustomPasswordValidator(int length) { RequiredLength = length; } public Task<IdentityResult> ValidateAsync(string item) { if (String.IsNullOrEmpty(item) || item.Length < RequiredLength) { return Task.FromResult(IdentityResult.Failed(String.Format("Password should be of length {0}",RequiredLength))); } string pattern = @"^(?=.*[0-9])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*0-9]{10,}$"; if (!Regex.IsMatch(item, pattern)) { return Task.FromResult(IdentityResult.Failed("Password should have one numeral and one special character")); } return Task.FromResult(IdentityResult.Success); }
- قدم بعدی تعریف این اعتبارسنج سفارشی در کلاس UserManager است. باید مقدار خاصیت PasswordValidator را به این کلاس تنظیم کنیم. به کلاس ApplicationUserManager که پیشتر ساختید بروید و مقدار خاصیت PasswordValidator را به CustomPasswordValidator تغییر دهید.
public class ApplicationUserManager : UserManager<ApplicationUser> { public ApplicationUserManager() : base(new UserStore<ApplicationUser(new ApplicationDbContext())) { PasswordValidator = new CustomPasswordValidator(10); } }
قانون 3: امکان استفاده از 5 کلمه عبور اخیر ثبت شده وجود ندارد
public class PreviousPassword { public PreviousPassword() { CreateDate = DateTimeOffset.Now; } [Key, Column(Order = 0)] public string PasswordHash { get; set; } public DateTimeOffset CreateDate { get; set; } [Key, Column(Order = 1)] public string UserId { get; set; } public virtual ApplicationUser User { get; set; } }
- خاصیت جدیدی به کلاس ApplicationUser اضافه کنید تا لیست آخرین کلمات عبور استفاده شده را نگهداری کند.
public class ApplicationUser : IdentityUser { public ApplicationUser() : base() { PreviousUserPasswords = new List<PreviousPassword>(); } public virtual IList<PreviousPassword> PreviousUserPasswords { get; set; } }
public class ApplicationUserStore : UserStore<ApplicationUser> { public ApplicationUserStore(DbContext context) : base(context) { } public override async Task CreateAsync(ApplicationUser user) { await base.CreateAsync(user); await AddToPreviousPasswordsAsync(user, user.PasswordHash); } public Task AddToPreviousPasswordsAsync(ApplicationUser user, string password) { user.PreviousUserPasswords.Add(new PreviousPassword() { UserId = user.Id, PasswordHash = password }); return UpdateAsync(user); } }
public class ApplicationUserManager : UserManager<ApplicationUser> { private const int PASSWORD_HISTORY_LIMIT = 5; public ApplicationUserManager() : base(new ApplicationUserStore(new ApplicationDbContext())) { PasswordValidator = new CustomPasswordValidator(10); } public override async Task<IdentityResult> ChangePasswordAsync(string userId, string currentPassword, string newPassword) { if (await IsPreviousPassword(userId, newPassword)) { return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password")); } var result = await base.ChangePasswordAsync(userId, currentPassword, newPassword); if (result.Succeeded) { var store = Store as ApplicationUserStore; await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword)); } return result; } public override async Task<IdentityResult> ResetPasswordAsync(string userId, string token, string newPassword) { if (await IsPreviousPassword(userId, newPassword)) { return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password")); } var result = await base.ResetPasswordAsync(userId, token, newPassword); if (result.Succeeded) { var store = Store as ApplicationUserStore; await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword)); } return result; } private async Task<bool> IsPreviousPassword(string userId, string newPassword) { var user = await FindByIdAsync(userId); if (user.PreviousUserPasswords.OrderByDescending(x => x.CreateDate). Select(x => x.PasswordHash).Take(PASSWORD_HISTORY_LIMIT) .Where(x => PasswordHasher.VerifyHashedPassword(x, newPassword) != PasswordVerificationResult.Failed).Any()) { return true; } return false; } }
سورس کد این مثال را میتوانید از این لینک دریافت کنید. نام پروژه Identity-PasswordPolicy است، و زیر قسمت Samples/Identity قرار دارد.
EF Code First #6
ادامه بررسی Fluent API جهت تعریف نگاشت کلاسها به بانک اطلاعاتی
در قسمتهای قبل با استفاده از متادیتا و data annotations جهت بررسی نحوه نگاشت اطلاعات کلاسها به جداول بانک اطلاعاتی آشنا شدیم. اما این موارد تنها قسمتی از تواناییهای Fluent API مهیا در EF Code first را ارائه میدهند. یکی از دلایل آن هم به محدود بودن تواناییهای ذاتی Attributes بر میگردد. برای مثال حین کار با Attributes امکان استفاده از متغیرها یا lambda expressions و امثال آن وجود ندارد. به علاوه شاید عدهای علاقمند نباشند تا کلاسهای خود را با data annotations شلوغ کنند.
در قسمت دوم این سری، مروری مقدماتی داشتیم بر Fluent API. در آنجا ذکر شد که امکان تعریف نگاشتها به کمک تواناییهای Fluent API به دو روش زیر میسر است:
الف) میتوان از متد protected override void OnModelCreating در کلاس مشتق شده از DbContext کار را شروع کرد.
ب) و یا اگر بخواهیم کلاس Context برنامه را شلوغ نکنیم بهتر است به ازای هر کلاس مدل برنامه، یک کلاس mapping مشتق شده از EntityTypeConfiguration را تعریف نمائیم. سپس میتوان این کلاسها را در متد OnModelCreating یاد شده، توسط متد modelBuilder.Configurations.Add جهت استفاده و اعمال، معرفی کرد.
کلاسهای مدلی را که در این قسمت بررسی خواهیم کرد، همان کلاسهای User و Project قسمت سوم هستند و هدف این قسمت بیشتر تطابق Fluent API با اطلاعات ارائه شده در قسمت سوم است؛ برای مثال در اینجا چگونه باید از خاصیتی صرفنظر کرد، مسایل همزمانی را اعمال نمود و امثال آن.
بنابراین یک پروژه جدید کنسول را آغاز نمائید. سپس با کمک NuGet ارجاعات لازم را به اسمبلیهای EF اضافه نمائید.
در پوشه Models این پروژه، سه کلاس تکمیل شده زیر، از قسمت سوم وجود دارند:
using System;
using System.Collections.Generic;
namespace EF_Sample03.Models
{
public class User
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Name { set; get; }
public string LastName { set; get; }
public string FullName
{
get { return Name + " " + LastName; }
}
public string Email { set; get; }
public string Description { set; get; }
public byte[] Photo { set; get; }
public IList<Project> Projects { set; get; }
public byte[] RowVersion { set; get; }
public InterestComponent Interests { set; get; }
public User()
{
Interests = new InterestComponent();
}
}
}
using System;
namespace EF_Sample03.Models
{
public class Project
{
public int Id { set; get; }
public DateTime AddDate { set; get; }
public string Title { set; get; }
public string Description { set; get; }
public virtual User User { set; get; }
public byte[] RowVesrion { set; get; }
}
}
namespace EF_Sample03.Models
{
public class InterestComponent
{
public string Interest1 { get; set; }
public string Interest2 { get; set; }
}
}
سپس یک پوشه جدید به نام Mappings را به پروژه اضافه نمائید. به ازای هر کلاس فوق، یک کلاس جدید را جهت تعاریف اطلاعات نگاشتها به کمک Fluent API اضافه خواهیم کرد:
using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;
namespace EF_Sample03.Mappings
{
public class InterestComponentConfig : ComplexTypeConfiguration<InterestComponent>
{
public InterestComponentConfig()
{
this.Property(x => x.Interest1).HasMaxLength(450);
this.Property(x => x.Interest2).HasMaxLength(450);
}
}
}
using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;
namespace EF_Sample03.Mappings
{
public class ProjectConfig : EntityTypeConfiguration<Project>
{
public ProjectConfig()
{
this.Property(x => x.Description).IsMaxLength();
this.Property(x => x.RowVesrion).IsRowVersion();
}
}
}
using System.Data.Entity.ModelConfiguration;
using EF_Sample03.Models;
using System.ComponentModel.DataAnnotations;
namespace EF_Sample03.Mappings
{
public class UserConfig : EntityTypeConfiguration<User>
{
public UserConfig()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.ToTable("tblUser", schemaName: "guest");
this.Property(p => p.AddDate).HasColumnName("CreateDate").HasColumnType("date").IsRequired();
this.Property(x => x.Name).HasMaxLength(450);
this.Property(x => x.LastName).IsMaxLength().IsConcurrencyToken();
this.Property(x => x.Email).IsFixedLength().HasMaxLength(255); //nchar(128)
this.Property(x => x.Photo).IsOptional();
this.Property(x => x.RowVersion).IsRowVersion();
this.Ignore(x => x.FullName);
}
}
}
توضیحاتی در مورد کلاسهای تنظیمات نگاشتهای خواص به جداول و فیلدهای بانک اطلاعاتی
نظم بخشیدن به تعاریف نگاشتها
همانطور که ملاحظه میکنید، جهت نظم بیشتر پروژه و شلوغ نشدن متد OnModelCreating کلاس Context برنامه، که در ادامه کدهای آن معرفی خواهد شد، به ازای هر کلاس مدل، یک کلاس تنظیمات نگاشتها را اضافه کردهایم.
کلاسهای معمولی نگاشتها ازکلاس EntityTypeConfiguration مشتق خواهند شد و جهت تعریف کلاس InterestComponent به عنوان Complex Type، اینبار از کلاس ComplexTypeConfiguration ارث بری شده است.
تعیین طول فیلدها
در کلاس InterestComponentConfig، به کمک متد HasMaxLength، همان کار ویژگی MaxLength را میتوان شبیه سازی کرد که در نهایت، طول فیلد nvarchar تشکیل شده در بانک اطلاعاتی را مشخص میکند. اگر نیاز است این فیلد nvarchar از نوع max باشد، نیازی به تنظیم خاصی نداشته و حالت پیش فرض است یا اینکه میتوان صریحا از متد IsMaxLength نیز برای معرفی nvarchar max استفاده کرد.
تعیین مسایل همزمانی
در قسمت سوم با ویژگیهای ConcurrencyCheck و Timestamp آشنا شدیم. در اینجا اگر نوع خاصیت byte array بود و نیاز به تعریف آن به صورت timestamp وجود داشت، میتوان از متد IsRowVersion استفاده کرد. معادل ویژگی ConcurrencyCheck در اینجا، متد IsConcurrencyToken است.
تعیین کلید اصلی جدول
اگر پیش فرضهای EF Code first مانند وجود خاصیتی به نام Id یا ClassName+Id رعایت شود، نیازی به کار خاصی نخواهد بود. اما اگر این قراردادها رعایت نشوند، میتوان از متد HasKey (که نمونهای از آنرا در کلاس UserConfig فوق مشاهده میکنید)، استفاده کرد.
تعیین فیلدهای تولید شده توسط بانک اطلاعاتی
به کمک متد HasDatabaseGeneratedOption، میتوان مشخص کرد که آیا یک فیلد Identity است و یا یک فیلد محاسباتی ویژه و یا هیچکدام.
تعیین نام جدول و schema آن
اگر نیاز است از قراردادهای نامگذاری خاصی پیروی شود، میتوان از متد ToTable جهت تعریف نام جدول متناظر با کلاس جاری استفاده کرد. همچنین در اینجا امکان تعریف schema نیز وجود دارد.
تعیین نام و نوع سفارشی فیلدها
همچنین اگر نام فیلدها نیز باید از قراردادهای دیگری پیروی کنند، میتوان آنها را به صورت صریح توسط متد HasColumnName معرفی کرد. اگر نیاز است این خاصیت به نوع خاصی در بانک اطلاعاتی نگاشت شود، باید از متد HasColumnType کمک گرفت. برای مثال در اینجا بجای نوع datetime، از نوع ویژه date استفاده شده است.
معرفی فیلدها به صورت nchar بجای nvarchar
برای نمونه اگر قرار است هش کلمه عبور در بانک اطلاعاتی ذخیره شود، چون طول آن ثابت میباشد، توصیه شدهاست که بجای nvarchar از nchar برای تعریف آن استفاده شود. برای این منظور تنها کافی است از متد IsFixedLength استفاده شود. در این حالت طول پیش فرض 128 برای فیلد درنظر گرفته خواهد شد. بنابراین اگر نیاز است از طول دیگری استفاده شود، میتوان همانند سابق از متد HasMaxLength کمک گرفت.
ضمنا این فیلدها همگی یونیکد هستند و با n شروع شدهاند. اگر میخواهید از varchar یا char استفاده کنید، میتوان از متد IsUnicode با پارامتر false استفاده کرد.
معرفی یک فیلد به صورت null پذیر در سمت بانک اطلاعاتی
استفاده از متد IsOptional، فیلد را در سمت بانک اطلاعاتی به صورت فیلدی با امکان پذیرش مقادیر null معرفی میکند.
البته در اینجا به صورت پیش فرض byte arrayها به همین نحو معرفی میشوند و تنظیم فوق صرفا جهت ارائه توضیحات بیشتر در نظر گرفته شد.
صرفنظر کردن از خواص محاسباتی در تعاریف نگاشتها
با توجه به اینکه خاصیت FullName به صورت یک خاصیت محاسباتی فقط خواندنی، در کدهای برنامه تعریف شده است، با استفاده از متد Ignore، از نگاشت آن به بانک اطلاعاتی جلوگیری خواهیم کرد.
معرفی کلاسهای تعاریف نگاشتها به برنامه
استفاده از کلاسهای Config فوق خودکار نیست و نیاز است توسط متد modelBuilder.Configurations.Add معرفی شوند:
using System.Data.Entity;
using System.Data.Entity.Migrations;
using EF_Sample03.Mappings;
using EF_Sample03.Models;
namespace EF_Sample03.DataLayer
{
public class Sample03Context : DbContext
{
public DbSet<User> Users { set; get; }
public DbSet<Project> Projects { set; get; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new InterestComponentConfig());
modelBuilder.Configurations.Add(new ProjectConfig());
modelBuilder.Configurations.Add(new UserConfig());
//modelBuilder.ComplexType<InterestComponent>();
//modelBuilder.Ignore<InterestComponent>();
base.OnModelCreating(modelBuilder);
}
}
public class Configuration : DbMigrationsConfiguration<Sample03Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(Sample03Context context)
{
base.Seed(context);
}
}
}
در اینجا کلاس Context برنامه مثال جاری را ملاحظه میکنید؛ به همراه کلاس Configuration مهاجرت خودکار که در قسمتهای قبل بررسی شد.
در متد OnModelCreating نیز میتوان یک کلاس را از نوع Complex معرفی کرد تا برای آن در بانک اطلاعاتی جدول جداگانهای تعریف نشود. اما باید دقت داشت که اینکار را فقط یکبار میتوان انجام داد؛ یا توسط کلاس InterestComponentConfig و یا توسط متد modelBuilder.ComplexType. اگر هر دو با هم فراخوانی شوند، EF یک استثناء را صادر خواهد کرد.
و در نهایت، قسمت آغازین برنامه اینبار به شکل زیر خواهد بود که از آغاز کننده MigrateDatabaseToLatestVersion (قسمت چهارم این سری) نیز استفاده کرده است:
using System;
using System.Data.Entity;
using EF_Sample03.DataLayer;
namespace EF_Sample03
{
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample03Context, Configuration>());
using (var db = new Sample03Context())
{
var project1 = db.Projects.Find(1);
if (project1 != null)
{
Console.WriteLine(project1.Title);
}
}
}
}
}
ضمنا رشته اتصالی مورد استفاده تعریف شده در فایل کانفیک برنامه نیز به صورت زیر تعریف شده است:
<connectionStrings>
<clear/>
<add
name="Sample03Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true"
providerName="System.Data.SqlClient"
/>
/connectionStrings>
در قسمتهای بعد مباحث پیشرفتهتری از تنظیمات نگاشتها را به کمک Fluent API، بررسی خواهیم کرد. برای مثال روابط ارث بری، many-to-many و ... چگونه تعریف میشوند.
برای اغلب توسعه دهندههای دات نت (برنامههای وب و دسکتاپ) تنها یک دات نت فریم ورک شناخته شده وجود دارد: The `Full` .NET Framework
که تنها بر روی ویندوز قابل اجرا است و آخرین نگارش پایدار آن در زمان نگارش این مطلب، 4.6.1 است. این فریم ورک بزرگ، از اجزایی تشکیل شدهاست که در تصویر ذیل قابل مشاهدهاند:
مهمترین قسمتهای این فریم ورک «بزرگ» شامل مواردی مانند CLR که کار تبدیل کدهای IL را به کدهای ماشین انجام میدهد، BCL که کلاسهای پایهای را جهت کار با IO، Text و غیره، فراهم میکنند، هستند؛ به علاوه کتابخانههایی مانند Windows Forms، WPF و ASP.NET که برفراز BCL و CLR کار میکنند.
هرچند تعدادی از توسعه دهندههای دات نت تنها با Full framework کار میکنند، اما در طی سالهای اخیر انشعابات بسیار دیگری از آن به وجود آمدهاند؛ مانند دات نتهای ویژهی ویندوزهای 8 و Universal Windows Platform، دات نت مخصوص ویندوز فون 8 و ویندوز فون مبتنی بر پلتفرم سیلورلایت، به علاوه دات نت پلتفرم زامارین برای توسعهی برنامههای iOS و Android نیز هم اکنون وجود دارند (البته در اینجا Mono، دات نت میکرو و غیره را هم باید ذکر کرد). این فریم ورکها و انشعابات، به همراه پیاده سازی یک سری موارد مشترک و مواردی کاملا اختصاصی هستند که به سایر پلتفرمهای دیگر قابل انتقال نیستند.
با زیاد شدن تعداد انشعابات دات نت «بزرگ»، نوشتن کدی که قابل اجرای بر روی تمام پلتفرمهای یاد شده باشد، مشکل شد. اینجا بود که مفهومی را به نام PCL یا Portable class libraries معرفی کردند:
هدف از PCLها، ساده سازی کامپایل و به اشتراک گذاری کد بین پلتفرمهای مختلف بود و پشتیبانی قابل توجهی هم از آن در VS.NET وجود دارد. هرچند این روش نسبتا موفق بود اما مشکلاتی را هم به همراه داشت. برای مثال با ارائهی یک انشعاب و پلتفرم دیگری از دات نت «بزرگ»، کتابخانهی PCL موجود، باید برای این انشعاب جدید مجددا کامپایل شود. به علاوه در اینجا تنها محدود به انتخاب امکانات مشترک بین پلتفرمهای مختلف هستید.
برای رفع این مشکلات در پایان سال 2014، یک «دات نت فریم ورک جدید» به نام NET Core. معرفی شد که سورس باز است و همچنین چندسکویی (از ویندوز، لینوکس و OSX پشتیبانی میکند).
هرچند پیشتر Windows Store و ASP.NET Core app به صورت پلتفرمهایی مجزا ارائه شده بودند، اما اکنون از یک BCL مشترک به نام CoreFX استفاده میکنند و نحوهی توزیع آنها صرفا از طریق نیوگت است. به عبارتی اینبار بجای دریافت یک فریم ورک «بزرگ»، تنها اجزایی را دریافت میکنید که از طریق نیوگت سفارش دادهاید.
به این ترتیب نه تنها کار توزیع برنامههای مبتنی بر NET Core. با سهولت بیشتری انجام خواهد شد، بلکه به روز رسانی اجزای یک برنامه، تاثیری بر روی سایر برنامهها نخواهد داشت و مشکلات جانبی را به وجود نمیآورد. به علاوه دیگر نیازی نیست تا منتظر یک نگارش «بزرگ» دیگر باشید تا بتوانید آخرین به روز رسانیها را دریافت کنید. اینبار به روز رسانی بستههای نیوگت برنامه معادل هستند با به روز رسانی کل فریم ورک در نگارشهای قبلی «بزرگ» آن. در اینجا حتی CoreCLR و NET Native runtime. که مربوط به Windows runtime است هم از طریق نیوگت به روز رسانی میشود.
البته NET Core. انتهای مسیر نیست و هم اکنون NETStandard نیز جهت رفع مشکلات کامپایل مجدد PCLها در حال توسعه است و پس از ارائهی آن، PCLها منسوخ شده درنظر گرفته میشوند. در این حالت با انتخاب target platform ایی به نام NETStandard (بجای مثلا انتخاب دات نت 4.5 و ویندوز فون 8)، اینبار دات نت 4.5 و ویندوز فون 8 و تمام پلتفرمهای دیگر، به صورت یکجا انتخاب میشوند و اگر پلتفرم جدیدی برای مثال از NETStandard نگارش 1.1 پشتیبانی کند، به این معنا است که کتابخانهی شما هم اکنون با آن سازگار است و دیگر نیازی به کامپایل مجدد آن نخواهد بود.
به علاوه هر برنامهای که بر اساس NETStandard تهیه شود، قابلیت اجرای بر روی NET Core. را نیز خواهد داشت. به عبارتی برنامههای NETStandard همان برنامههای مبتنی بر NET Core. هستند.
ASP.NET Core چیست؟
در زمان نگارش این مطلب، دو گزینهی برنامههای وب ASP.NET Core 1.0 و همچنین Windows Store apps (مبتنی بر NET Native Runtime.) قابلیت استفادهی از این پلتفرم جدید NET Core. دارند.
ASP.NET Core 1.0، که پیشتر با نام ASP.NET 5 معرفی شده بود، بازنویسی کامل ASP.NET است که با ایدهی کاملا ماژولار بودن، تهیه شدهاست و از طریق آن، قابلیت به روز رسانی منظم و توزیع آسان از طریق نیوگت، میسر خواهد شد. به علاوه در آن، بسیاری از الگوهای برنامه نویسی شیءگرا مانند تزریق وابستگیها، به صورت توکار و از ابتدا پشتیبانی میشوند.
ASP.NET Core 1.0 از WebForms ، VB ، WebPages و SignalR پشتیبانی نمیکند. البته در این بین عدم پشتیبانی از «وب فرمها» قطعی است؛ اما افزودن سه مورد دیگر یاد شده، جزو لیست کارهای پس از ارائهی نگارش 1 این فریم ورک قرار دارند و به زودی ارائه خواهند شد.
اکنون وضعیت ASP.NET MVC 5 و ASP.NET Web API 2 چگونه است؟
ASP.NET Core 1.0 مدل برنامه نویسی ASP.NET MVC و Web API را به صورت یکپارچه ارائه میدهد و دیگر خبری از ارائهی مجزای اینها نخواهد بود و دقیقا بر مبنای مفاهیم برنامه نویسی این دو بنا شدهاست. به صورت خلاصه MVC + Web API + Web Pages = Core MVC 1.0
پیشتر فضای نام System.Web.MVC مخصوص ASP.NET MVC بود و فضای نام مجزای دیگری به نام System.Web.Http مخصوص ASP.NET Web API. اما اکنون تنها یک فضای نام مشترک و یکپارچه به نام Microsoft.AspNet.Mvc هر دوی اینها را پوشش میدهد.
در این نگارش جدید وابستگی از system.web مبتنی بر IIS حذف شدهاست و با استفاده از هاست جدید چندسکویی به نام Kesterl، به سرعتی 5 برابر سرعت NodeJS دست یافتهاند.
آخرین تاریخ به روز رسانی ASP.NET MVC 5.x دوشنبه، 20 بهمن 1393 است (با ارائه نگارش 5.2.3 که آخرین نگارش رسمی و پایدار آن است) و آخرین تاریخ به روز رسانی ASP.NET Web API 2.x نیز همان روز است.
هرچند مایکروسافت عادت به اعلام رسمی پایان پشتیبانی از بسیاری از محصولات خود را ندارد اما تمام فناوریهای «قدیمی» خودش را بر روی CodePlex نگهداری میکند و تمام فناوریهای «جدید» را به GitHub منتقل کردهاست. بنابراین اگر در مورد فناوری خاصی به Codeplex رسیدید، یعنی «دیگر ادامهی رسمی نخواهد یافت» و حداکثر در حد رفع یک سری باگها و مشکلات گزارش شده باقی میمانند.
مثال 1: هم اکنون نگارش دوم ASP.NET Identity را بر روی Codeplex میتوانید مشاهده کنید. نگارش سوم آن به GitHub منتقل شدهاست که این نگارش صرفا با ASP.NET Core 1.0 سازگار است. در مورد ASP.NET MVC و Web API نیز چنین حالتی رخ دادهاست. نگارشهای 5 و 2 آنها بر روی Codeplex موجود هستند و نگارش ششم که به ASP.NET Core 1.0 تغییر نام یافت و ترکیبی است از MVC و Web API، در GitHub توسعه مییابد.
مثال 2: WCF به علت پیچیدگی بیش از حد و مدرن نبودن طراحی آن، رقابت را به ASP.NET Web API 2.x واگذار کرد و مدل برنامه نویسی ASP.NET Web API 2.x نیز هم اکنون جزئی از ASP.NET Core 1.0 است. بنابراین اگر قصد ایجاد پروژهی جدیدی را بر این مبنا دارید، بهتر است با APS.NET Core 1.0 کار را شروع کنید.
اما هنوز تعداد زیادی از کتابخانههای Full framework به NET Core. انتقال پیدا نکردهاند
برای نمونه هنوز EF Core 1.0 که پیشتر نام EF 7.x به آن داده شده بود، به مرحلهی نهایی تکمیل قابلیتهای آن نرسیدهاست. اما باید دانست که ASP.NET Core 1.0 صرفا بر فراز NET Core. قابل اجرا نیست؛ بلکه قابلیت اجرای بر فراز NET 4.6. و یا همان دات نت «بزرگ و کامل» را نیز دارد. بنابراین به سادگی قابلیت اجرای EF 6.x و یا NHibernate را نیز دارا است. تنها مزیتی را که در اینجا از دست خواهید، قابلیت چندسکویی بودن ASP.NET Core 1.0 است؛ زیرا EF 6.x با چنین دیدی طراحی نشدهاست.
همانطور که ملاحظه میکنید، ASP.NET Core 1.0 قابلیت اجرای بر روی هر دوی NET Core 1.0. و NET 4.6. را دارا است. اما یکی، چندسکویی است و دیگری صرفا مختص به ویندوز.
فناورهای منسوخ شدهی در NET Core.
یکسری از فناوریها و کتابخانهها احتمالا هیچگاه قابلیت انتقال به NET Core. را نخواهند یافت و یا حداقل باید تا چندنگارش بعدی آن صبر کنند. فناوریهای خاتمه یافتهی با NET Core. به شرح زیر هستند:
- Reflection: همانطور که عنوان شد، NET Core. بر فراز CoreCLR و همچنین NET Native runtime. اجرا میشود و تولید برنامههای native و static linking آنها مانند برنامههای ++C، نیاز به دانستن اجزایی دارد که به صورت پویا فراخوانی نمیشوند و بلافاصله و در زمان کامپایل، توسط کامپایلر قابل تشخیص هستند. همین محدودیت سبب شدهاست که استفادهی از Reflection در NET Core. به حداقل ممکن آن برسد. برای مثال در System.Object متد GetType آن تنها نام نوع را باز میگرداند و نه اطلاعات بیشتری را مانند GetMembers سابق.
- App Domains: هرچند CoreCLR از App Domains پشتیبانی میکند اما NET Native runtime. خیر. به همین جهت برای ایزوله سازی برنامهها توصیه شدهاست که از containerهایی مانند docker استفاده شود.
- Remoting: پیش از WCF جهت برقراری ارتباط بین برنامهها مطرح شده بود و هم اکنون در دات نت کامل هم آنچنان استفادهای از آن نمیشود.
- binary serialization: البته کتابخانههایی مانند JSON.NET و امثال آن، نگارش NET Core. هم دارند؛ اما چون binary serialization نیاز به اطلاعات reflection قابل توجهی دارد دیگر پشتیبانی نخواهد شد.
فناورهایی که به زودی به NET Core. منتقل میشوند
یکسری از فناوریها مانند XAML هنوز معادل NET Core. ندارند و لیست زیر قرار است از طرف مایکروسافت سورس باز شده و همچنین به NET Core. منتقل شود:
System.Data
System.DirectoryServices
System.Drawing
System.Transactions
System.Xml.Xsl and System.Xml.Schema
System.Net.Mail
System.IO.Ports
System.Workflow
System.Xaml
مراحل نصب ASP.NET Core 1.0
پیش از نصب نگارش 1.0 RTM باید به این نکته دقت داشت که نصاب آن، نگارشهای آزمایشی قبلی را حذف و یا بازنویسی نمیکند و همین مساله ممکن است سبب بروز تداخلهایی و یا حتی از کار افتادن VS.NET شما شود. بنابراین اگر نگارشهای RC یا بتا را پیشتر نصب کردهاید، به Add remove programs ویندوز مراجعه کرده و سه مورد ذیل را حتما حذف کنید (خیلی مهم):
- Preview Tooling (all versions)
- NET Core Runtime SDK (all versions).
- NET Core Runtime (all Versions).
پس از حذف بستههای قدیمی، برای نصب نگارش 1.0 RTM، ابتدا نیاز است Visual Studio 2015 Update 3 را نصب کنید و پس از آن با استفاده از NET Core for Visual Studio Official MSI Installer. کار نصب اجزای مورد نیاز آن انجام خواهد شد.
بررسی شماره نگارش 1.0 RTM
پس از نصب اجزای عنوان شده، خط فرمان را گشوده و دستور ذیل را صادر کنید:
C:\Users\Vahid>dotnet --version 1.0.0-preview2-003121
اگر یک پروژهی خالی ASP.NET Core Web Application را نیز شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژهی جدید):
.NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template
در اینجا فایل جدیدی را به نام global.json مشاهده میکنید که محتوایات آن شامل دقیقا همین شماره نگارش است؛ به همراه معرفی پوشههای اصلی پروژه:
{ "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-preview2-003121" } }
در چه برنامههایی استفاده از بانکهای اطلاعاتی NoSQL مناسبتر است؟
1) برنامههای مدیریت محتوا
2) کاتالوگهای محصولات (هر برنامهای با تعدادی شیء و خصوصا متادیتای متغیر)
3) شبکههای اجتماعی
4) Big Data
5) سایر
1) برنامههای مدیریت محتوا
بانکهای اطلاعاتی NoSQL سندگرا، جهت تهیه برنامههای مدیریت محتوا، بسیار مناسب هستند. در این نوع برنامهها، یک سری محتوا که دارای متادیتایی هستند، ذخیره خواهند شد. این متادیتاها مانند نوع، گروه و هر نوع خاصیت دیگری، میتواند باشند. برای ذخیره سازی این نوع اطلاعات، جفتهای key-value بسیار خوب عمل میکنند. همچنین در بانکهای اطلاعاتی سندگرای NoSQL، با استفاده از مفهوم برچسبها، امکان الصاق فایلهای متناظری به اسناد پیش بینی شدهاست. همانطور که در قسمت قبل نیز ذکر شد، در Document stores، نگارشهای قدیمی اسناد نیز حفظ میشوند. به این ترتیب، این خاصیت و توانمندی توکار، امکان دسترسی به نگارشهای مختلف یک محتوای خاص را به سادگی میسر میسازد. به علاوه اکثر Document stores امکان دسترسی به این مستندات را به کمک URLها و REST API، به صورت خودکار فراهم میسازند.
برای نمونه به CouchDB، عنوان Web database نیز داده شده است؛ از این جهت که یک برنامه وب را میتوان داخل بانک اطلاعاتی آن قرار داد. در اینجا منظور از برنامه وب، یک وب سایت قابل دسترسی از طریق URLها است و نه برنامههای سازمانی وب. برای نمونه ساختاری شبیه به برنامه معروف EverNote را میتوان داخل این نوع بانکهای اطلاعاتی به سادگی ایجاد کرد (خود بانک اطلاعاتی تشکیل شده است از یک وب سرور که REST API را پشتیبانی کرده و امکان دسترسی به اسناد را بدون نیاز به کدنویسی اضافهتری، از طریق URLها و HTTP Verbs استاندارد مهیا میکند).
2) کاتالوگهای محصولات
محصولات در یک کاتالوگ، ویژگیهای مشابه یکسان فراوانی دارند؛ اما تعدادی از این محصولات، دارای ویژگیهایی خاص و منحصربفردی نیز میباشند.
مثلا یک شیء محصول را درنظر بگیرید که دارای خواص مشترک و یکسان شماره، نام، توضیحات و قیمت است. اما بعضی از محصولات، بسته به ردهی خاصی که دارند، دارای ویژگیهای خاصی مانند قدرت تفکیک، رنگ، سرعت و غیره نیز هستند که از هر گروه، به گروه دیگری متغیر است.
برای مدیریت یک چنین نیازی، هر دو گروه key-value stores و wide column stores بانکهای اطلاعاتی NoSQL مناسب هستند؛ از این جهت که در یک key-value store نیازی به تعریف هیچ نوع ساختار خاصی، در ابتدای کار نیست و این ساختار میتواند از هر رکورد، به رکورد دیگری متفاوت باشد.
یا برای نمونه، یک برنامه فرم ساز را درنظر بگیرید که هر فرم آن، هر چند دارای یک سری خواص ثابت مانند نام، گروه و امثال آن است، اما هر کدام دارای فیلدهای تشکیل دهنده متفاوتی نیز میباشد. به این ترتیب با استفاده از key-value stores، دیگری نیازی به نگران بودن در مورد نحوه مدیریت اسکیمای متغیر مورد نیاز، نخواهد بود.
3) شبکههای اجتماعی
همانطور که در قسمت قبل نیز بحث شد، نوع خاص Graph databases برای کاربردهای برنامههای شبکههای اجتماعی و ردیابی تغییرات آنها بسیار مفید و کارآ هستند. برای مثال در یک شبکه افراد دارای تعدادی دنبال کننده هستند؛ عضو گروههای مختلف میباشند، در قسمتهای مختلفی نظر و مطلب ارسال میکنند. در اینجا، اشیاء نسبت به یکدیگر روابط مختلفی دارند. با استفاده از Graph databases، تشکیل روابط self-joins و تو در تو و بسیاری از روشهای خاص، مانند روابط many-to-many که در بانکهای اطلاعاتی رابطهای با تمهیدات ویژهای قابل تشکیل هستند، با سهولت بهتری مدیریت خواهند شد.
4) Big Data
الگوریتم MapReduce، برای کار با حجم دادههای عظیم، طراحی شده است و در این بین، بانکهای اطلاعاتی Wide column store (که در قسمت قبل بررسی شدند) و یا حتی Key-value store (مانند Amazon DynamoDB) بیشتر کاربرد دارند. در سناریوهای دادههای عظیم، واژههای Hadoop و Hbase دنیای NoSQL را زیاد خواهید شنید. Hadoop نسخه سورس باز MapReduce گوگل است و Hbase نیز نسخه سورس باز BigTable گوگل میباشد. مفاهیم پایهای Sharding و فایل سیستمهای Append-only (با سرعت بالای نوشتن) نیز به مدیریت BigData کمک میکنند.
در اینجا بحث مهم، خواندن اطلاعات و آنالیز آنها است و نه تهیه برنامههای معروف CRUD. بسیاری از اعمال آماری و ریاضی مورد نیاز بر روی دادههای عظیم، نیازی به اسکیمای از پیش مشخص شده بانکهای اطلاعاتی رابطهای را ندارند و یا در اینجا قابلیتهای نوشتن کوئریهای پیچیده نیز آنچنان مهم نیستند.
5) سایر کاربردها
- هر سیستمی که اطلاعات Log مانند را تولید میکند. منظور از Log، اطلاعاتی است که در حین رخداد خاصی تولید میشوند. عموما مرسوم است که این نوع اطلاعات را در فایلها، بجای بانک اطلاعاتی ذخیره کرد. بنابراین مدیریت این نوع فایلها توسط بانکهای اطلاعاتی NoSQL، قابلیت انجام امور آماری را بر روی آنها سادهتر خواهد ساخت.
- مدیریت اطلاعات برنامههایی مانند سیستمهای EMail.
و در چه برنامههایی استفاده از بانکهای اطلاعاتی رابطهای مناسبتر است؟
اگر تا اینجا به مزایای استفاده از بانکهای اطلاعاتی NoSQL اشاره شد، بدین معنا نیست که بانکهای اطلاعاتی رابطهای، منسوخ شدهاند یا دیگر قدر و قیمتی ندارند. واقعیت این است که هنوز بازه وسیعی از کاربردها را میتوان به کمک بانکهای اطلاعاتی رابطهای بهتر از بانکهای اطلاعاتی NoSQL مدیریت کرد. این کاربردها و مزیتها در 5 گروه عمده خلاصه میشوند:
1) نیاز به تراکنشها
2) اسکیمای پیش فرض
3) برنامههای LOB یا Line of business applications
4) زبانهای کوئری نویسی پیشرفته
5) نیاز به امکانات گزارشگیری پیشرفته
1) نیاز به تراکنشها
در سیستمهای تجاری عمومی، نیاز به پیاده سازی مفهوم ACID که در قسمتهای قبل به آن پرداخته شد، مانند Atomic transactions وجود دارد. Atomic transaction به زبان ساده به این معنا است که سیستم قادر است چندین دستور را در قالب یک گروه و در طی یک مرحله، به بانک اطلاعاتی اعمال کند و اگر یکی از این دستورات گروه در حال اعمال، با شکست مواجه شد، باید کل تراکنش برگشت خورده و امنیت کار تضمین گردد. در غیراینصورت با یک سیستم غیر هماهنگ مواجه خواهیم شد.
و همانطور که پیشتر نیز عنوان شد، سیستمهای NoSQL، مبنای کار را بر اساس «عاقبت یک دست شدن» اطلاعات قرار دادهاند؛ تا دسترسی پذیری به آنها افزایش یافته و سرعت عملیات به این نحو بهبود یابد. در این نوع سیستمها تضمینی در مورد ACID وجود ندارد.
2) اسکیمای پیش فرض
پروسههای متداول، دارای ساختاری مشخص و معمولی هستند. زیرا طراحی اولیه یک پروسه، بر مبنای مجموعهای از اطلاعات است که همیشه باید وجود داشته باشند و اگر همانند بحث کاتالوگهای محصولات، نیاز به متادیتای متغیر نباشد، ساختار و اسکیمای یک پروسه، از ابتدای طراحی آن مشخص میباشد.
و ... تمام اینها را به خوبی میتوان توسط بانکهای اطلاعاتی رابطهای، با تعریف یک اسکیمای مشخص، مدیریت کرد.
3) برنامههای LOB یا Line of business applications
در برنامههای تجاری متداول، طراحی طرحبندی فرمهای برنامه یا انقیاد دادهها، بر اساس یک اسکیما و ساختار مشخص صورت میگیرد. بدون داشتن یک اسکیمای مشخص، امکان تعاریف انقیاد دادهها به صورت strongly typed وجود نخواهد داشت. همچنین کل مفهوم Object relational mapping و ORMهای مختلف نیز بر اساس وجود یک اسکیمای مشخص و از پیش تعیین شده کار میکند. بنابراین بانکهای اطلاعاتی رابطهای، انتخاب بسیار مناسبی برای تهیه برنامههای تجاری روزمره هستند.
4) زبانهای کوئری نویسی پیشرفته
همانطور که عنوان شد برای تهیه کوئری بر روی اغلب بانکهای اطلاعاتی NoSQL، باید توسط یک برنامه ثانویه، کار پیاده سازی الگوریتم Map Reduce را انجام داد. هر چند تعدادی از این نوع بانکهای اطلاعاتی به صورت توکار دارای موتور MapReduce هستند، اما بسیاری از آنها خیر. به همین جهت برای تهیه کوئریهای متداول، کار پیاده سازی این برنامههای ثانویه مشکل خواهد بود. به این ترتیب نوشتن Ad Hoc queries و گزارشگیری بسیار مشکل میشوند.
علاوه بر امکانات خوب کوئری گرفتن در بانکهای اطلاعاتی رابطهای، این کوئریها در زمان اجرا نیز بر اساس اسکیمای موجود، بسیار بهینه و با سرعت بالا اجرا میشوند. قابلیتی که رسیدن به آن در بانکهای اطلاعاتی با اسکیمای متغیر، کار سادهای نیست و باید آنرا با کدنویسی شخصی بهینه کرد. البته اگر تعداد این نوع برنامههای ثانویه که به آنها imperative query در مقابل declarative query بانکهای رابطهای میگویند، کم باشد، شاید یکبار نوشتن و بارها استفاده کردن از آنها اهمیتی نداشته باشد؛ در غیراینصورت تبدیل به یک عذاب خواهد شد.
5) نیاز به امکانات گزارشگیری پیشرفته
گزارشگیرهای برنامههای تجاری نیز بر اساس یک ساختار و اسکیمای مشخص به کمک قابلیتهای پیشرفته کوئری نویسی بانکهای اطلاعاتی رابطهای به سادگی قابل تهیه هستند. برای تهیه گزارشاتی که قابلیت چاپ مناسبی را داشته باشند، محل قرارگیری فیلدهای مختلف در صفحه مهم هستند و با متغیر بودن آنها، قابلیت طراحی از پیش آنها را از دست خواهیم داد. در این حالت با اسکیمای متغیر، حداکثر بتوان یک dump از اطلاعات را به صورت ستونی نمایش داد.
بنابراین به صورت خلاصه، بانکهای اطلاعاتی رابطهای، جهت مدیریت کارهای روزمره تجاری اغلب شرکتها، بسیار ضروری و جزو مسایل پایهای بهشمار میروند و به این زودیها هم قرار نیست با نمونهی دیگری جایگزین شوند.
C# 7 - Discards
علت وجود Discards در C# 7.0
گاهی از اوقات میخواهیم از مقادیر بازگشت داده شدهی توسط متدها، خصوصا آرگومانهای out، صرفنظر کنیم:
if (bool.TryParse("TRUE", out parsedValue)) { /* Do your stuff */ }
if (bool.TryParse("TRUE", out bool _)) { /* Do your stuff */ }
مکانهایی که میتوان از Discards در آنها استفاده کرد
پارامترهای از نوع out، یکی از مکانهایی هستند که میتوان از Discards برای معرفی آنها استفاده کرد. سایر کاربردهای آن شامل موارد ذیل میشوند:
-در تعاریف tuples برای صرفنظر کردن از پارامتری خاص (Value Tuple deconstructions)
var (arg1, _, _) = (1, 2, 3);
-در pattern matching برای صرفنظر کردن از نوعی خاص
// in pattern matching switch (input) { case int number: Add(number); break; case object[] _: // ignore break; }
- در پارامترهای delegates
// in delegate parameters var usersWithPosts = context.Users .Join(context.Posts, user => user.UserId, post => post.UserId, (user, _) => user);
واژهی کلیدی _
یک واژهی کلیدی زمینهای است
_ یک contextual keyword به حساب میآید؛ مانند var. به این معنا که اگر پیشتر در کدهای خود، یک متغیر محلی را به نام _ تعریف کرده باشید، با _ بکار رفتهی به صورت discard، تداخل نخواهد کرد:
bool _ = false, v = false; if (bool.TryParse("TRUE", out var _)) { /* Do your stuff */ v = _; }
قابلیت تعریف مجدد را دارد
در مثال
var (arg1, _, _) = (1, 2, 3);
تیم ASP.NET Identity پروژه نمونه ای را فراهم کرده است که میتواند بعنوان نقطه شروعی برای اپلیکیشنهای MVC استفاده شود. پیکربندیهای لازم در این پروژه انجام شدهاند و برای استفاده از فریم ورک جدید آماده است.
شروع به کار : پروژه نمونه را توسط NuGet ایجاد کنید
برای شروع یک پروژه ASP.NET خالی ایجاد کنید (در دیالوگ قالبها گزینه Empty را انتخاب کنید). سپس کنسول Package Manager را باز کرده و دستور زیر را اجرا کنید.
PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre
پس از اینکه NuGet کارش را به اتمام رساند باید پروژه ای با ساختار متداول پروژههای ASP.NET MVC داشته باشید. به تصویر زیر دقت کنید.
همانطور که میبینید ساختار پروژه بسیار مشابه پروژههای معمول MVC است، اما آیتمهای جدیدی نیز وجود دارند. فعلا تمرکز اصلی ما روی فایل IdentityConfig.cs است که در پوشه App_Start قرار دارد.
اگر فایل مذکور را باز کنید و کمی اسکرول کنید تعاریف دو کلاس سرویس را مشاهده میکنید: EmailService و SmsService.
public class EmailService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // Plug in your email service here to send an email. return Task.FromResult(0); } } public class SmsService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // Plug in your sms service here to send a text message. return Task.FromResult(0); } }
اگر دقت کنید هر دو کلاس قرارداد IIdentityMessageService را پیاده سازی میکنند. میتوانید از این قرارداد برای پیاده سازی سرویسهای اطلاع رسانی ایمیلی، پیامکی و غیره استفاده کنید. در ادامه خواهیم دید چگونه این دو سرویس را بسط دهیم.
یک حساب کاربری مدیریتی پیش فرض ایجاد کنید
پیش از آنکه بیشتر جلو رویم نیاز به یک حساب کاربری در نقش مدیریتی داریم تا با اجرای اولیه اپلیکیشن در دسترس باشد. کلاسی بنام ApplicationDbInitializer در همین فایل وجود دارد که هنگام اجرای اولیه و یا تشخیص تغییرات در مدل دیتابیس، اطلاعاتی را Seed میکند.
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext> { protected override void Seed(ApplicationDbContext context) { InitializeIdentityForEF(context); base.Seed(context); } //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role public static void InitializeIdentityForEF(ApplicationDbContext db) { var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>(); const string name = "admin@admin.com"; const string password = "Admin@123456"; const string roleName = "Admin"; //Create Role Admin if it does not exist var role = roleManager.FindByName(roleName); if (role == null) { role = new IdentityRole(roleName); var roleresult = roleManager.Create(role); } var user = userManager.FindByName(name); if (user == null) { user = new ApplicationUser { UserName = name, Email = name }; var result = userManager.Create(user, password); result = userManager.SetLockoutEnabled(user.Id, false); } // Add user admin to Role Admin if not already added var rolesForUser = userManager.GetRoles(user.Id); if (!rolesForUser.Contains(role.Name)) { var result = userManager.AddToRole(user.Id, role.Name); } } }
تایید حسابهای کاربری : چگونه کار میکند
بدون شک با تایید حسابهای کاربری توسط ایمیل آشنا هستید. حساب کاربری ای ایجاد میکنید و ایمیلی به آدرس شما ارسال میشود که حاوی لینک فعالسازی است. با کلیک کردن این لینک حساب کاربری شما تایید شده و میتوانید به سایت وارد شوید.
اگر به کنترلر AccountController در این پروژه نمونه مراجعه کنید متد Register را مانند لیست زیر مییابید.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); var callbackUrl = Url.Action( "ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); await UserManager.SendEmailAsync( user.Id, "Confirm your account", "Please confirm your account by clicking this link: <a href=\"" + callbackUrl + "\">link</a>"); ViewBag.Link = callbackUrl; return View("DisplayEmail"); } AddErrors(result); } // If we got this far, something failed, redisplay form return View(model); }
public static ApplicationUserManager Create( IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) { var manager = new ApplicationUserManager( new UserStore<ApplicationUser>( context.Get<ApplicationDbContext>())); // Configure validation logic for usernames manager.UserValidator = new UserValidator<ApplicationUser>(manager) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; // Configure validation logic for passwords manager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, }; // Configure user lockout defaults manager.UserLockoutEnabledByDefault = true; manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); manager.MaxFailedAccessAttemptsBeforeLockout = 5; // Register two factor authentication providers. This application // uses Phone and Emails as a step of receiving a code for verifying the user // You can write your own provider and plug in here. manager.RegisterTwoFactorProvider( "PhoneCode", new PhoneNumberTokenProvider<ApplicationUser> { MessageFormat = "Your security code is: {0}" }); manager.RegisterTwoFactorProvider( "EmailCode", new EmailTokenProvider<ApplicationUser> { Subject = "SecurityCode", BodyFormat = "Your security code is {0}" }); manager.EmailService = new EmailService(); manager.SmsService = new SmsService(); var dataProtectionProvider = options.DataProtectionProvider; if (dataProtectionProvider != null) { manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>( dataProtectionProvider.Create("ASP.NET Identity")); } return manager; }
در قطعه کد بالا کلاسهای EmailService و SmsService روی وهله ApplicationUserManager تنظیم میشوند.
manager.EmailService = new EmailService(); manager.SmsService = new SmsService();
درست در بالای این کدها میبینید که چگونه تامین کنندگان احراز هویت دو مرحله ای (مبتنی بر ایمیل و پیامک) رجیستر میشوند.
// Register two factor authentication providers. This application // uses Phone and Emails as a step of receiving a code for verifying the user // You can write your own provider and plug in here. manager.RegisterTwoFactorProvider( "PhoneCode", new PhoneNumberTokenProvider<ApplicationUser> { MessageFormat = "Your security code is: {0}" }); manager.RegisterTwoFactorProvider( "EmailCode", new EmailTokenProvider<ApplicationUser> { Subject = "SecurityCode", BodyFormat = "Your security code is {0}" });
تایید حسابهای کاربری توسط ایمیل و احراز هویت دو مرحله ای توسط ایمیل و/یا پیامک نیاز به پیاده سازی هایی معتبر از قراردارد IIdentityMessageService دارند.
پیاده سازی سرویس ایمیل توسط ایمیل خودتان
پیاده سازی سرویس ایمیل نسبتا کار ساده ای است. برای ارسال ایمیلها میتوانید از اکانت ایمیل خود و یا سرویس هایی مانند SendGrid استفاده کنید. بعنوان مثال اگر بخواهیم سرویس ایمیل را طوری پیکربندی کنیم که از یک حساب کاربری Outlook استفاده کند، مانند زیر عمل خواهیم کرد.
public class EmailService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // Credentials: var credentialUserName = "yourAccount@outlook.com"; var sentFrom = "yourAccount@outlook.com"; var pwd = "yourApssword"; // Configure the client: System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient("smtp-mail.outlook.com"); client.Port = 587; client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network; client.UseDefaultCredentials = false; // Creatte the credentials: System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(credentialUserName, pwd); client.EnableSsl = true; client.Credentials = credentials; // Create the message: var mail = new System.Net.Mail.MailMessage(sentFrom, message.Destination); mail.Subject = message.Subject; mail.Body = message.Body; // Send: return client.SendMailAsync(mail); } }
پیاده سازی سرویس ایمیل با استفاده از SendGrid
سرویسهای ایمیل متعددی وجود دارند اما یکی از گزینههای محبوب در جامعه دات نت SendGrid است. این سرویس API قدرتمندی برای زبانهای برنامه نویسی مختلف فراهم کرده است. همچنین یک Web API مبتنی بر HTTP نیز در دسترس است. قابلیت دیگر اینکه این سرویس مستقیما با Windows Azure یکپارچه میشود.
می توانید در سایت SendGrid یک حساب کاربری رایگان بعنوان توسعه دهنده بسازید. پس از آن پیکربندی سرویس ایمیل با مرحله قبل تفاوت چندانی نخواهد داشت. پس از ایجاد حساب کاربری توسط تیم پشتیبانی SendGrid با شما تماس گرفته خواهد شد تا از صحت اطلاعات شما اطمینان حاصل شود. برای اینکار چند گزینه در اختیار دارید که بهترین آنها ایجاد یک اکانت ایمیل در دامنه وب سایتتان است. مثلا اگر هنگام ثبت نام آدرس وب سایت خود را www.yourwebsite.com وارد کرده باشید، باید ایمیلی مانند info@yourwebsite.com ایجاد کنید و توسط ایمیل فعالسازی آن را تایید کند تا تیم پشتیبانی مطمئن شود صاحب امتیاز این دامنه خودتان هستید.
تنها چیزی که در قطعه کد بالا باید تغییر کند اطلاعات حساب کاربری و تنظیمات SMTP است. توجه داشته باشید که نام کاربری و آدرس فرستنده در اینجا متفاوت هستند. در واقع میتوانید از هر آدرسی بعنوان آدرس فرستنده استفاده کنید.
public class EmailService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // Credentials: var sendGridUserName = "yourSendGridUserName"; var sentFrom = "whateverEmailAdressYouWant"; var sendGridPassword = "YourSendGridPassword"; // Configure the client: var client = new System.Net.Mail.SmtpClient("smtp.sendgrid.net", Convert.ToInt32(587)); client.Port = 587; client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network; client.UseDefaultCredentials = false; // Creatte the credentials: System.Net.NetworkCredential credentials = new System.Net.NetworkCredential(credentialUserName, pwd); client.EnableSsl = true; client.Credentials = credentials; // Create the message: var mail = new System.Net.Mail.MailMessage(sentFrom, message.Destination); mail.Subject = message.Subject; mail.Body = message.Body; // Send: return client.SendMailAsync(mail); } }
آزمایش تایید حسابهای کاربری توسط سرویس ایمیل
ابتدا اپلیکیشن را اجرا کنید و سعی کنید یک حساب کاربری جدید ثبت کنید. دقت کنید که از آدرس ایمیلی زنده که به آن دسترسی دارید استفاده کنید. اگر همه چیز بدرستی کار کند باید به صفحه ای مانند تصویر زیر هدایت شوید.
همانطور که مشاهده میکنید پاراگرافی در این صفحه وجود دارد که شامل لینک فعالسازی است. این لینک صرفا جهت تسهیل کار توسعه دهندگان درج میشود و هنگام توزیع اپلیکیشن باید آن را حذف کنید. در ادامه به این قسمت باز میگردیم. در این مرحله ایمیلی حاوی لینک فعالسازی باید برای شما ارسال شده باشد.
پیاده سازی سرویس SMS
برای استفاده از احراز هویت دو مرحله ای پیامکی نیاز به یک فراهم کننده SMS دارید، مانند Twilio . مانند SendGrid این سرویس نیز در جامعه دات نت بسیار محبوب است و یک C# API قدرتمند ارائه میکند. میتوانید حساب کاربری رایگانی بسازید و شروع به کار کنید.
پس از ایجاد حساب کاربری یک شماره SMS، یک شناسه SID و یک شناسه Auth Token به شما داده میشود. شماره پیامکی خود را میتوانید پس از ورود به سایت و پیمایش به صفحه Numbers مشاهده کنید.
شناسههای SID و Auth Token نیز در صفحه Dashboard قابل مشاهده هستند.
اگر دقت کنید کنار شناسه Auth Token یک آیکون قفل وجود دارد که با کلیک کردن روی آن شناسه مورد نظر نمایان میشود.
حال میتوانید از سرویس Twilio در اپلیکیشن خود استفاده کنید. ابتدا بسته NuGet مورد نیاز را نصب کنید.
PM> Install-Package Twilio
public class SmsService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { string AccountSid = "YourTwilioAccountSID"; string AuthToken = "YourTwilioAuthToken"; string twilioPhoneNumber = "YourTwilioPhoneNumber"; var twilio = new TwilioRestClient(AccountSid, AuthToken); twilio.SendSmsMessage(twilioPhoneNumber, message.Destination, message.Body); // Twilio does not return an async Task, so we need this: return Task.FromResult(0); } }
حال که سرویسهای ایمیل و پیامک را در اختیار داریم میتوانیم احراز هویت دو مرحله ای را تست کنیم.
آزمایش احراز هویت دو مرحله ای
پروژه نمونه جاری طوری پیکربندی شده است که احراز هویت دو مرحله ای اختیاری است و در صورت لزوم میتواند برای هر کاربر بصورت جداگانه فعال شود. ابتدا توسط حساب کاربری مدیر، یا حساب کاربری ای که در قسمت تست تایید حساب کاربری ایجاد کرده اید وارد سایت شوید. سپس در سمت راست بالای صفحه روی نام کاربری خود کلیک کنید. باید صفحه ای مانند تصویر زیر را مشاهده کنید.
در این قسمت باید احراز هویت دو مرحله ای را فعال کنید و شماره تلفن خود را ثبت نمایید. پس از آن یک پیام SMS برای شما ارسال خواهد شد که توسط آن میتوانید پروسه را تایید کنید. اگر همه چیز بدرستی کار کند این مراحل چند ثانیه بیشتر نباید زمان بگیرد، اما اگر مثلا بیش از 30 ثانیه زمان برد احتمالا اشکالی در کار است.
حال که احراز هویت دو مرحله ای فعال شده از سایت خارج شوید و مجددا سعی کنید به سایت وارد شوید. در این مرحله یک انتخاب به شما داده میشود. میتوانید کد احراز هویت دو مرحله ای خود را توسط ایمیل یا پیامک دریافت کنید.
پس از اینکه گزینه خود را انتخاب کردید، کد احراز هویت دو مرحله ای برای شما ارسال میشود که توسط آن میتوانید پروسه ورود به سایت را تکمیل کنید.
حذف میانبرهای آزمایشی
همانطور که گفته شد پروژه نمونه شامل میانبرهایی برای تسهیل کار توسعه دهندگان است. در واقع اصلا نیازی به پیاده سازی سرویسهای ایمیل و پیامک ندارید و میتوانید با استفاده از این میانبرها حسابهای کاربری را تایید کنید و کدهای احراز هویت دو مرحله ای را نیز مشاهده کنید. اما قطعا این میانبرها پیش از توزیع اپلیکیشن باید حذف شوند.
بدین منظور باید نماها و کدهای مربوطه را ویرایش کنیم تا اینگونه اطلاعات به کلاینت ارسال نشوند. اگر کنترلر AccountController را باز کنید و به متد ()Register بروید با کد زیر مواجه خواهید شد.
if (result.Succeeded) { var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking this link: <a href=\"" + callbackUrl + "\">link</a>"); // This should not be deployed in production: ViewBag.Link = callbackUrl; return View("DisplayEmail"); } AddErrors(result);
نمایی که این متد باز میگرداند یعنی DisplayEmail.cshtml نیز باید ویرایش شود.
@{ ViewBag.Title = "DEMO purpose Email Link"; } <h2>@ViewBag.Title.</h2> <p class="text-info"> Please check your email and confirm your email address. </p> <p class="text-danger"> For DEMO only: You can click this link to confirm the email: <a href="@ViewBag.Link">link</a> Please change this code to register an email service in IdentityConfig to send an email. </p>
متد دیگری که در این کنترلر باید ویرایش شود ()VerifyCode است که کد احراز هویت دو مرحله ای را به صفحه مربوطه پاس میدهد.
[AllowAnonymous] public async Task<ActionResult> VerifyCode(string provider, string returnUrl) { // Require that the user has already logged in via username/password or external login if (!await SignInHelper.HasBeenVerified()) { return View("Error"); } var user = await UserManager.FindByIdAsync(await SignInHelper.GetVerifiedUserIdAsync()); if (user != null) { ViewBag.Status = "For DEMO purposes the current " + provider + " code is: " + await UserManager.GenerateTwoFactorTokenAsync(user.Id, provider); } return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl }); }
همانطور که میبینید متغیری بنام Status به ViewBag اضافه میشود که باید حذف شود.
نمای این متد یعنی VerifyCode.cshtml نیز باید ویرایش شود.
@model IdentitySample.Models.VerifyCodeViewModel @{ ViewBag.Title = "Enter Verification Code"; } <h2>@ViewBag.Title.</h2> @using (Html.BeginForm("VerifyCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { @Html.AntiForgeryToken() @Html.ValidationSummary("", new { @class = "text-danger" }) @Html.Hidden("provider", @Model.Provider) <h4>@ViewBag.Status</h4> <hr /> <div class="form-group"> @Html.LabelFor(m => m.Code, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.Code, new { @class = "form-control" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <div class="checkbox"> @Html.CheckBoxFor(m => m.RememberBrowser) @Html.LabelFor(m => m.RememberBrowser) </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" class="btn btn-default" value="Submit" /> </div> </div> }
در این فایل کافی است ViewBag.Status را حذف کنید.
از تنظیمات ایمیل و SMS محافظت کنید
در مثال جاری اطلاعاتی مانند نام کاربری و کلمه عبور، شناسههای SID و Auth Token همگی در کد برنامه نوشته شده اند. بهتر است چنین مقادیری را بیرون از کد اپلیکیشن نگاه دارید، مخصوصا هنگامی که پروژه را به سرویس کنترل ارسال میکند (مثلا مخازن عمومی مثل GitHub). بدین منظور میتوانید یکی از پستهای اخیر را مطالعه کنید.
فرض کنید برای آزمایش قسمت «از سرگیری مجدد» دریافت یک فایل حجیم مطلب جاری، یک چنین کدی را در یک برنامهی ASP.NET Core 2.0 تهیه کردهاید:
public class HomeController : Controller { public IActionResult GetFile() { return PhysicalFile(@"C:\path\file.pdf", "application/pdf"); }
اجرای این کد به همراه هدر مخصوص «Accept-Ranges: bytes » که در مطلب جاری در مورد آن بحث شد نیز هست:
یعنی دریافت فایلها در ASP.NET Core 2.0 به صورت توکار از ویژگی «از سرگیری مجدد» پشتیبانی میکند. قابلیتی که در نگارشهای پیشین ASP.NET (تمام نگارشهای آن)، به صورت پیشفرض و توکار وجود نداشت و برای پیاده سازی آن میبایستی مقدار زیادی کد نوشته میشد.
var result = new FileStreamResult(readStream, contentType) { LastModified = lastModified, EntityTag = entityTag, EnableRangeProcessing = true, }; return PhysicalFile(path, "text/plain", "downloadName.txt", lastModified, entityTag, true); return File(data, "text/plain", "downloadName.txt", lastModified: null, entityTag: entityTag, enableRangeProcessing: true);