public class IdentityUser : IUser { public IdentityUser(); public IdentityUser(string userName); public virtual ICollection<identityuserclaim> Claims { get; } public virtual string Id { get; set; } public virtual ICollection<identityuserlogin> Logins { get; } public virtual string PasswordHash { get; set; } public virtual ICollection<identityuserrole> Roles { get; } public virtual string SecurityStamp { get; set; } public virtual string UserName { get; set; } }
public class ApplicationUser : IdentityUser { public string Email { get; set; } }
public class RegisterViewModel { [Required] [Display(Name = "User name")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } [Required] [Display(Name = "Email address")] public string Email { get; set; } }
<div class="form-group"> @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) </div> </div>
public ActionResult About() { ViewBag.Message = "Your application description page."; UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())); var user = UserManager.FindById(User.Identity.GetUserId()); if (user != null) ViewBag.Email = user.Email; else ViewBag.Email = "User not found."; return View(); }
WAS یا Windows Process Activation Service
پیکربندی مدیریتی در WAS
- Global configuration information
- Protocol configuration information for both HTTP and non-HTTP protocols
- Application pool configuration, such as the process account information
- Site configuration, such as bindings and applications
- Application configuration, such as the enabled protocols and the application pools to which the applications belong
مدیریت پروسهها Process Managment
نکته: از آنجایی که WAS هم پروسههای http و هم non-http را مدیریت میکند، پس میتوانید از یک applicatio pool برای چندین protocol استفاده کنید. به عنوان مثال شما یکی سرویس XML دارید که میتوانید از آن برای سرویس دهی به پروتوکلهای Http و net.tcp بهره بگیرید.
ماژولها در IIS
ماژولهای کد ماشین یا native
جدول ماژولهای HTTP
نام ماژول | توضیحات | نام فایل منبع |
CustomErrorModule | موقعی که هنگام response، کد خطایی تولید میگردد، پیام خطا را پیکربندی و سپس ارسال میکند. | Inetsrv\Custerr.dll |
HttpRedirectionModule | تنظمیات redirection برای درخواستهای http را در دسترس قرار میدهد. | Inetsrv\Redirect.dll |
ProtocolSupportModule | انجام عملیات مربوط به پروتوکلها بر عهده این ماژول است؛ مثل تنظیم کردن قسمت هدر برای response. | Inetsrv\Protsup.dll |
RequestFilteringModule | این ماژول از IIS 7.5 به بعد اضافه شد. درخواستها را فیلتر میکند تا پروتوکل و رفتار محتوا را کنترل کند. | Inetsrv\modrqflt.dll |
WebDAVModule | این ماژول از IIS 7.5 به بعد اضافه شد. امنیت بیشتر در هنگام انتشار محتوا روی HTTP SSL | Inetsrv\WebDAV.dll |
ماژولهای امنیتی
نام ماژول | توضیحات | نام فایل منبع |
AnonymousAuthenticationModule | موقعی که هیچ کدام از عملیات authentication با موفقیت روبرو نشود، عملیات Anonymous authentication انجام میشود. | Inetsrv\Authanon.dll |
BasicAuthenticationModule | عمل ساده و اساسی authentication را انجام میدهد. | Inetsrv\Authbas.dll |
CertificateMappingAuthenticationModule | انجام عمل Certificate Mapping authentication در Active Directory | Inetsrv\Authcert.dll |
DigestAuthenticationModule | Digest authentication | Inetsrv\Authmd5.dll |
IISCertificateMappingAuthenticationModule | همان Certificate Mapping authentication ولی اینبار با IIS Certificate . | Inetsrv\Authmap.dll |
RequestFilteringModule | عملیات اسکن URL از قبیل نام صفحات و دایرکتوریها ، توع verb و یا کاراکترهای مشکوک و خطرآفرین | Inetsrv\Modrqflt.dll |
UrlAuthorizationModule | عمل URL authorization | Inetsrv\Urlauthz.dll |
WindowsAuthenticationModule | عمل NTLM integrated authentication | Inetsrv\Authsspi.dll |
IpRestrictionModule | محدود کردن IPهای نسخه 4 لیست شده در IP Security در قسمت پیکربندی | Inetsrv\iprestr.dll |
ماژولهای محتوا
نام ماژول | توضیحات | نام فایل منبع |
CgiModule | ایجاد پردازشهای (Common Gateway Interface (CGI به منظور ایجاد خروجی response | Inetsrv\Cgi.dll |
DefaultDocumentModule | تلاش برای ساخت یک سند پیش فرض برای درخواست هایی که دایرکتوری والد ارسال میشود | Inetsrv\Defdoc.dll |
DirectoryListingModule | لیست کردن محتوای یک دایرکتوری | Inetsrv\dirlist.dll |
IsapiModule | میزبانی فایل های ISAPI | Inetsrv\Isapi.dll |
IsapiFilterModule | پشتیبانی از فیلتر های ISAPI | Inetsrv\Filter.dll |
ServerSideIncludeModule | پردازش کدهای include شده سمت سرور | Inetsrv\Iis_ssi.dll |
StaticFileModule | ارائه فایلهای ایستا | Inetsrv\Static.dll |
FastCgiModule | پشتبانی از CGI | Inetsrv\iisfcgi.dll |
ماژولهای فشرده سازی
DynamicCompressionModule | فشرده سازی پاسخ response با gzip | Inetsrv\Compdyn.dll |
StaticCompressionModule | فشرده سازی محتوای ایستا | Inetsrv\Compstat.dll |
ماژولهای کش کردن
FileCacheModule | تهیه کش در مد کاربری برای فایلها. | Inetsrv\Cachfile.dll |
HTTPCacheModule | تهیه کش مد کاربری و مد کرنل برای http.sys | Inetsrv\Cachhttp.dll |
TokenCacheModule | تهیه کش مد کاربری بر اساس جفت نام کاربری و یک token که توسط Windows user principals تولید شده است. | Inetsrv\Cachtokn.dll |
UriCacheModule | تهیه یک کش مد کاربری از اطلاعات URL | Inetsrv\Cachuri.dll |
ماژولهای عیب یابی و لاگ کردن
CustomLoggingModule | بارگزاری ماژولهای خصوصی سازی شده جهت لاگ کردن | Inetsrv\Logcust.dll |
FailedRequestsTracingModule | برای ردیابی درخواستهای ناموفق | Inetsrv\Iisfreb.dll |
HttpLoggingModule | دریافت اطلاعات و پردازش وضعیت http.sys برای لاگ کردن | Inetsrv\Loghttp.dll |
RequestMonitorModule | ردیابی درخواست هایی که در حال حاضر در پروسههای کارگر در حال اجرا هستند و گزارش اطلاعاتی در مورد وضعیت اجرا و کنترل رابط برنامه نویسی کاربردی. | Inetsrv\Iisreqs.dll |
TracingModule | گزارش رخدادهای Microsoft Event Tracing for Windows یا به اختصار ETW | Inetsrv\Iisetw.dll |
ماژولهای مدیریتی و نظارتی بر کل ماژولها
ManagedEngine | مدیرتی بر ماژولهای غیر native که در پایین قرار دارند. | Microsoft.NET\Framework\v2.0.50727\webengine.dll |
ConfigurationValidationModule | اعتبارسنجی خطاها، مثل موقعی که برنامه در حالت integrated اجرا شده و ماژولها یا هندلرها در system.web تعریف شدهاند. | Inetsrv\validcfg.dll |
ماژول | توضیحات | منبع |
AnonymousIdentification | مدیریت منابع تعیین هویت برای کاربران ناشناس مانند asp.net profile | System.Web.Security.AnonymousIdentificationModule |
DefaultAuthentication | اطمینان از وجود شی Authentication در context مربوطه | System.Web.Security.DefaultAuthenticationModule |
FileAuthorization | تایید هویت کاربر برای دسترسی به فایل درخواست | System.Web.Security.FileAuthorizationModule |
FormsAuthentication | با این قسمت که باید کاملا آشنا باشید؛ برای تایید هویت کاربر | System.Web.Security.FormsAuthenticationModule |
OutputCache | مدیریت کش | System.Web.Caching.OutputCacheModule |
Profile | مدیریت پروفایل کاربران که تنظیماتش را در یک منبع دادهای چون دیتابیس ذخیره و بازیابی میکند. | System.Web.Profile.ProfileModule |
RoleManager | مدیریت نقش و سمت کاربران | System.Web.Security.RoleManagerModule |
Session | مدیریت session ها | System.Web.SessionState.SessionStateModule |
UrlAuthorization | آیا کاربر جاری حق دسترسی به URL درخواست را دارد؟ | System.Web.Security.UrlAuthorizationModule |
UrlMappingsModule | تبدیل یک Url واقعی به یک Url کاربرپسند | System.Web.UrlMappingsModule |
WindowsAuthentication | شناسایی و تایید و هویت یک کاربر بر اساس لاگین او به ویندوز | System.Web.Security.WindowsAuthenticationModule |
- امکان مشاهده و بررسی کوئریهای خام ADO.NET از قبیل SQL Server,Oracle و LINQ-to-SQL و EF/First Code و...
- نمایش زمان اجرای عملی صفحات
PM> Install-Package MiniProfiler
@using StackExchange.Profiling; <head> .. </head> <body> ... @MiniProfiler.RenderIncludes() </body>
protected void Application_BeginRequest() { if (Request.IsLocal) { MiniProfiler.Start(); } } protected void Application_EndRequest() { MiniProfiler.Stop(); }
<system.webServer> ... <handlers> <add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" /> </handlers> </system.webServer>
PM> Install-Package MiniProfiler.MVC
PM> Install-Package MiniProfiler.EF
protected void Application_BeginRequest() { if (Request.IsLocal) { MiniProfiler.Start(); MiniProfilerEF.Initialize(); } }
public ActionResult Index() { var profiler = MiniProfiler.Current; using (profiler.Step("Step 1")) { //code 1 } using (profiler.Step("Step 2")) { //code 2 } return View(); }
<%= StackExchange.Profiling.MiniProfiler.RenderIncludes() %>
در ادامهی مطلب قبلی، نکاتی دیگر را جهت افزایش کارآیی سیستمهای مبتنی بر EF اشاره خواهیم کرد:
عدم استفاده از کوئریهای کلی
فرض کنید در یک فرم جستجو، 4 تکست باکس FirstName, LastName, City و PostalZipCode برای عملیات جستجو در نظر گرفته شده است و کاربر میتواند بر اساس آنها جستجو را انجام دهد.
var searchModel = new Pupil { FirstName = "Ben", LastName = null, City = null, PostalZipCode = null }; List<Pupil> pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.City == null || p.LastName == searchModel.City) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList()
USE [EFSchoolSystem] DECLARE @p__linq__0 NVarChar(4000) SET @p__linq__0 = 'Ben' DECLARE @p__linq__1 NVarChar(4000) SET @p__linq__1 = 'Ben' DECLARE @p__linq__2 NVarChar(4000) SET @p__linq__2 = '' DECLARE @p__linq__3 NVarChar(4000) SET @p__linq__3 = '' DECLARE @p__linq__4 NVarChar(4000) SET @p__linq__4 = '' DECLARE @p__linq__5 NVarChar(4000) SET @p__linq__5 = '' DECLARE @p__linq__6 NVarChar(4000) SET @p__linq__6 = '' DECLARE @p__linq__7 NVarChar(4000) SET @p__linq__7 = '' -- Executed query SELECT TOP (100) [Extent1].[PupilId] AS [PupilId] , [Extent1].[FirstName] AS [FirstName] , [Extent1].[LastName] AS [LastName] , [Extent1].[Address1] AS [Address1] , [Extent1].[Adderss2] AS [Adderss2] , [Extent1].[PostalZipCode] AS [PostalZipCode] , [Extent1].[City] AS [City] , [Extent1].[PhoneNumber] AS [PhoneNumber] , [Extent1].[SchoolId] AS [SchoolId] , [Extent1].[Picture] AS [Picture] FROM [dbo].[Pupils] AS [Extent1] WHERE (@p__linq__0 IS NULL OR [Extent1].[FirstName] = @p__linq__1) AND (@p__linq__2 IS NULL OR [Extent1].[LastName] = @p__linq__3) AND (@p__linq__4 IS NULL OR [Extent1].[LastName] = @p__linq__5) AND (@p__linq__6 IS NULL OR [Extent1].[PostalZipCode] = @p__linq__7)
مشکلی که در این دسته از کوئریهای عمومی ایجاد میگردد آن است که ممکن است پلنی که برای یک گروه از پارامترهای ورودی مناسب باشد (جستجو بر اساس نام) برای سایر پارامترهای ورودی نامناسب باشد. تصور کنید تمام دانش آموزان، در شهر نیویورک یا بوستون زندگی میکنند. بنابراین این ستون از تنوع انتخاب کمتری برخوردار است در مقایسه با ستون نام خانوادگی و فرض کنید پلن، براساس پارامتر شهر ایجاد شده است. بنابراین ایجاد این پلن برای سایر پارامترها از کارآیی کافی برخوردار نیست. این مشکل با نام Bad Parameter Sniffing شناخته میشود و دربارهی Parameter Sniffing در اینجا به تفصیل اشاره شده است.
این مشکل زمانی بیشتر مشکل ساز خواهد شد که 99 درصد دانش آموزان در شهر نیویورک و فقط 1 درصد آنها در شهر بوستون زندگی میکنند و پلن ایجاد شده بر اساس پارامتر شهر بوستون باشد.
راه حل اول:
برای حل این مشکل تنها یک راه حل خاص وجود ندارد و باید براساس شرایط برنامه، کوئری از حالت عمومی خارج گردد؛ مانند زیر:
if (searchModel.City == null) { pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList(); } else { pupils = db.Pupils.Where(p => (searchModel.FirstName == null || p.FirstName == searchModel.FirstName) && (searchModel.LastName == null || p.LastName == searchModel.LastName) && (searchModel.City == null || p.LastName == searchModel.City) && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)) .Take(100) .ToList(); }
راه حل دوم:
کامپایل مجدد پلن در اجرای هر کوئری، اما این راه حل سرباری را تحمیل میکند. بدین منظور مترجم زیر را ایجاد کنید:
public class RecompileDbCommandInterceptor : IDbCommandInterceptor { public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if(!command.CommandText.EndsWith(" option(recompile)")) { command.CommandText += " option(recompile)"; } } //and implement other interface members }
var interceptor = new RecompileDbCommandInterceptor(); DbInterception.Add(interceptor); var pupils = db.Pupils.Where(p => p.City = city).ToList(); DbInterception.Remove(interceptor);
راه حل سوم:
استفاده از اجرای به تعویق افتاده به شکل زیر است:
var result = db.Pupils.AsQueryable(); if(searchModel.FirstName != null ) result = result.Where(p => p.FirstName == searchModel.FirstName); if(searchModel.LastName != null ) result = result.Where(p => p.LastName == searchModel.LastName); if(searchModel.PostalZipCode != null ) result = result.Where(p => p.PostalZipCode == searchModel.PostalZipCode); if(searchModel.City != null ) result = result.Where(p => p.City == searchModel.City);
افزونگی کشِ پلن
استفادهی مجدد از پلن بدلیل عدم ایجاد مجدد آن در زمان اجرای هر کوئری، بسیار خوب است. برای استفادهی مجدد از پلن، باید دستورات ارسالی یکسان باشند؛ مانند کوئریهای پارامتریک. در EF هنگامیکه از متغیرها استفاده کنید، کوئریها پارامتریک تولید میکند؛ اما یک استثناء وجود دارد: ()Skip و ()Take
2 متد فوق بیشتر جهت صفحه بندی استفاده میشوند:
var schools = db.Schools .OrderBy(s => s.PostalZipCode) .Skip(model.Page * model.ResultsPerPage) .Take(model.ResultsPerPage) .ToList();
حال اگر قصد دریافت اطلاعات صفحهی 500 را داشته باشید، مقادیر کوئری بعدی بترتیب 100 و 50000 خواهد بود و بجای مقادیر تصویر بالا 100 و 50000 قرار داده میشوند و کوئری متفاوتی با پلن متفاوتی ایجاد خواهد شد و اس کیو ال پلن کوئری قبلی را مورد استفاده قرار نخواهد داد و با اجرای کوئری دوم، پلن متفاوتی ایجاد خواهد کرد که این باعث ایجاد افزونگی پلنها خواهد شد و همانگونه که قبلا اشاره شد ایجاد پلن جدید هزینه بر است.
نکته: جهت مشاهده پلنهای کش شده در اس کیو ال، دستور زیر اجرا کنید:
SELECT text, query_plan FROM sys.dm_exec_cached_plans CROSS APPLY sys.dm_exec_query_plan(plan_handle) CROSS APPLY sys.dm_exec_sql_text(plan_handle)
- صدمه به کارآیی؛ هربار EF یک کوئری و اس کیو ال یک پلن جدید را ایجاد میکنند.
- افزایش اشغال حافظه؛ کش شدن کوئریها توسط EF سمت کلاینت و کش شدن پلنها در اس کیو ال سرور (کش بی رویهی پلنها باعث حذف سایر پلنهای مورد استفاده بدلیل محدودیت حافظه میشود که امکان بروز اختلال در کارآیی را بههمراه خواهد داشت.)
علت بروز مشکل:
هنگامیکه یک مقدار int، به متدهای ()Skip و ()Take ارسال میشود، EF نمیتواند تشخیص دهد این مقدار ارسالی ثابت (absolute) مانند (100)Take است یا توسط یک متغیر مانند (متغیر)Take تولید شده است. به همین خاطر EF مقدار ارسال شده را پارامتریک در نظر نمیگیرد.
راه حل:
در EF6 پیاده سازی دیگری برای متدهای ()Skip و ()Take ارائه شده است که برای حل مشکل فوق میتوان به کار گرفت، این پیاده سازی امکان دریافت lambada بجای int را دارد که باعث ایجاد کوئریهای پارامتریک خواهد شد.
int resultsToSkip = model.Page * model.ResultsPerPage; var schools = db.Schools .OrderBy(s => s.PostalZipCode) .Skip(() => resultsToSkip) //must pre-calculate this value .Take(() => model.ResultsPerPage) .ToList();
همانطور که مشاهده میکنید این بار EF کوئری پارامتریک ایجاد و ارسال کرده است.
پیشنیاز: ایجاد یک برنامهی خالی React و ASP.NET Core
یک پوشهی خالی را ایجاد کرده و در آن دستور dotnet new react را صادر کنید، تا قالب خاص پروژههای React یکی سازی شدهی با پروژههای ASP.NET Core، یک پروژهی جدید را ایجاد کند.
همانطور که در تصویر فوق نیز مشاهده میکنید، این پروژه از دو برنامه تشکیل شدهاست:
الف) برنامهی SPA که در پوشهی ClientApp قرار گرفتهاست و شامل کدهای کامل یک برنامهی React است.
ب) برنامهی سمت سرور ASP.NET Core که یک برنامهی متداول وب، به همراه فایل Startup.cs و سایر فایلهای مورد نیاز آن است.
در ادامه نکات ویژهی ساختار این پروژه را بررسی خواهیم کرد.
تجربهی توسعهی برنامهها توسط این قالب ویژه
اکنون اگر این پروژهی وب را برای مثال با فشردن دکمهی F5 و یا اجرای دستور dotnet run، اجرا کنیم، چه اتفاقی رخ میدهد؟
- به صورت خلاصه برنامهی ASP.NET Core شروع به کار کرده و سبب ارائه همزمان برنامهی SPA نیز خواهد شد.
- پورتی که برنامهی وب بر روی آن قرار دارد، با پورتی که برنامهی React بر روی روی آن ارائه میشود، یکی است. یعنی نیازی به تنظیمات CORS را ندارد.
- در این حالت اگر در برنامهی React تغییری را ایجاد کنیم (در هر قسمتی از آن)، hot reloading آن هنوز هم برقرار است و سبب بارگذاری مجدد برنامهی SPA در مرورگر خواهد شد و برای اینکار نیازی به توقف و راه اندازی مجدد برنامهی ASP.NET Core نیست.
اما این تجربهی روان کاربری و توسعه، چگونه حاصل شدهاست؟
بررسی ساختار فایل Startup.cs یک پروژهی مبتنی بر dotnet new react
برای درک نحوهی عملکرد این قالب ویژه، نیاز است از فایل Startup.cs آن شروع کرد.
// ... using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; namespace dotnet_template_sample { public class Startup { // ... public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); // In production, the React files will be served from this directory services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/build"; }); }
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.2" /> </ItemGroup>
در متد ConfigureServices، ثبت سرویسهای مرتبط با فایلهای استاتیک پروژهی SPA، توسط متد AddSpaStaticFiles صورت گرفتهاست. در اینجا RootPath آن، به پوشهی ClientApp/build اشاره میکند. البته این پوشه هنوز در این ساختار، قابل مشاهده نیست؛ اما زمانیکه پروژهی ASP.NET Core را برای ارائهی نهایی، publish کردیم، به صورت خودکار ایجاد شده و حاوی فایلهای قابل ارائهی برنامهی React نیز خواهد بود.
قسمت مهم دیگر کلاس آغازین برنامه، متد Configure آن است:
// ... using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; namespace dotnet_template_sample { public class Startup { // ... public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.UseStaticFiles(); app.UseSpaStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller}/{action=Index}/{id?}"); }); app.UseSpa(spa => { spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UseReactDevelopmentServer(npmScript: "start"); } }); } } }
- متد UseSpaStaticFiles، سبب ثبت میانافزاری میشود که امکان دسترسی به فایلهای استاتیک پوشهی ClientApp حاوی برنامهی React را میسر میکند؛ مسیر این پوشه را در متد ConfigureServices تنظیم کردیم.
- متد UseSpa، سبب ثبت میانافزاری میشود که دو کار مهم را انجام میدهد:
1- کار اصلی آن، ثبت مسیریابی معروف catch all است تا مسیریابیهایی را که توسط کنترلرهای برنامهی ASP.NET Core مدیریت نمیشوند، به سمت برنامهی React هدایت کند. برای مثال مسیر https://localhost:5001/api/users به یک کنترلر API برنامهی سمت سرور ختم میشود، اما سایر مسیرها مانند https://localhost:5001/login قرار است صفحهی login برنامهی سمت کلاینت SPA را نمایش دهند و متناظر با اکشن متد خاصی در کنترلرهای برنامهی وب ما نیستند. در این حالت، کار این مسیریابی catch all، نمایش صفحهی پیشفرض برنامهی SPA است.
2- بررسی میکند که آیا شرایط IsDevelopment برقرار است؟ آیا در حال توسعهی برنامه هستیم؟ اگر بله، میانافزار دیگری را به نام UseReactDevelopmentServer، اجرا و ثبت میکند.
برای درک عملکرد میانافزار ReactDevelopmentServer نیاز است به سورس آن مراجعه کرد. این میانافزار بر اساس پارامتر start ای که دریافت میکند، سبب اجرای npm run start خواهد شد. به این ترتیب دیگر نیازی به اجرای جداگانهی این دستور نخواهد بود و همچنین این اجرا، به همراه تنظیمات proxy مخصوصی نیز هست تا پورت اجرایی برنامهی React و برنامهی ASP.NET Core یکی شده و دیگر نیازی به تنظیمات CORS مخصوص برنامههای React نباشد. بنابراین hot reloading ای که از آن صحبت شد، توسط ASP.NET Core مدیریت نمیشود. در پشت صحنه همان npm run start اصلی برنامههای React، در حال اجرای وب سرور آزمایشی React است که از hot reloading پشتیبانی میکند.
یک مشکل: با این تنظیم، هربار که برنامهی ASP.NET Core اجرا میشود (به علت تغییرات در کدها و فایلهای پروژه)، سبب اجرای مجدد و پشت صحنهی react development server نیز خواهد شد که ... آغاز برنامه را در حالت توسعه، کند میکند. برای رفع این مشکل میتوان این وب سرور توسعهی برنامههای React را به صورت جداگانهای اجرا کرد و فقط تنظیمات پروکسی آنرا در اینجا ذکر نمود:
// replace spa.UseReactDevelopmentServer(npmScript: "start"); // with spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
تغییرات ویژهی فایل csproj برنامه
اگر به فایل csproj برنامه دقت کنیم، دو تغییر جدید نیز در آن قابل مشاهده هستند:
الف) نصب خودکار وابستگیهای برنامهی client
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') "> <!-- Ensure Node.js is installed --> <Exec Command="node --version" ContinueOnError="true"> <Output TaskParameter="ExitCode" PropertyName="ErrorCode" /> </Exec> <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." /> <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> </Target>
ب) یکی کردن تجربهی publish برنامهی ASP.NET Core با publish پروژههای React
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish"> <!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" /> <!-- Include the newly-built files in the publish output --> <ItemGroup> <DistFiles Include="$(SpaRoot)build\**" /> <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)"> <RelativePath>%(DistFiles.Identity)</RelativePath> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <ExcludeFromSingleFile>true</ExcludeFromSingleFile> </ResolvedFileToPublish> </ItemGroup> </Target>
- ابتدا npm install را جهت اطمینان از به روز بودن وابستگیهای برنامه مجددا اجرا میکند.
- سپس npm run build را برای تولید فایلهای قابل ارائهی برنامهی React اجرا میکند.
- در آخر تمام فایلهای پوشهی ClientApp/build تولیدی را به بستهی نهایی توزیعی برنامهی ASP.NET Core، اضافه میکند.
شروع به کار با Postman
پس از نصب و اجرای Postman، در ابتدا درخواست میکند که اکانتی را در سایت آنها ایجاد کنید. البته این مورد اختیاری است و امکان ذخیره سازی بهتر کارها را فراهم میکند. همچنین در اولین بار اجرای برنامه، یک صفحهی دیالوگ انتخاب گزینههای مختلف را نمایش میدهد که میتوانید نمایش آتی آنرا با برداشتن تیک Show this window on launch، غیرفعال کنید.
رابط کاربری Postman، از چندین قسمت تشکیل میشود:
1) Request builder
در قسمت سمت راست و بالای رابط کاربری Postman میتوان انواع و اقسام درخواستها را جهت ارسال به یک Web API، ساخت و ایجاد کرد. توسط آن میتوان HTTP method، آدرس، بدنه، هدرها و کوکیهای یک درخواست را تنظیم کرد. برای مثال در اینجا httpbin.org را وارد کرده و بر روی دکمهی send کلیک کنید:
2) قسمت نمایش Response
پس از ارسال درخواست، بلافاصله، نتیجهی نهایی را در ذیل قسمت ساخت درخواست، میتوان مشاهده کرد:
در اینجا status code بازگشتی از سرور و همچنین response body را مشاهده میکنید. به علاوه نوع خروجی را نیز HTML تشخیص دادهاست و با توجه به اینکه این درخواست، به یک وب سایت معمولی بودهاست، طبیعی میباشد.
همچنین در این خروجی، سه برگهی pretty/raw/preview نیز قابل مشاهده هستند. حالت pretty آنرا که به همراه syntax highlighting است، مشاهده میکنید. اگر حالت نمایش raw را انتخاب کنید، حالت متنی و اصل خروجی بازگشتی از سمت سرور را مشاهده خواهید کرد. برگهی preview آن، این خروجی را شبیه به یک مرورگر نمایش میدهد.
3) قسمت History
با ارسال این درخواست، در سمت چپ صفحه، تاریخچهی این عملیات نیز درج میشود:
4) رابط کاربری چند برگهای
برای ارسال یک درخواست جدید، یا میتوان مجددا یکی از گزینههای History را انتخاب کرد و آنرا ارسال نمود و یا میتوان در همان قسمت سمت راست و بالای رابط کاربری، بر روی دکمهی + کلیک و برگهی جدیدی را جهت ایجاد درخواستی جدید، باز کرد:
در اینجا درخواستی را به endpoint جدید https://httpbin.org/get ارسال کردهایم که در آن نوع پروتکل HTTPS نیز صریحا ذکر شدهاست. اگر به خروجی دریافتی از سرور دقت کنید، اینبار نوع بازگشتی را JSON تشخیص دادهاست که خروجی متداول بسیاری از HTTP Restful APIs است. در این حالت، انتخاب نوع نمایش pretty/raw/preview آنچنان تفاوتی را ایجاد نمیکند و همان حالت pretty که syntax highlighting را نیز به همراه دارد، مناسب است.
ارسال کوئری استرینگها توسط Postman
برای ارسال درخواستی به همراه کوئری استرینگها مانند https://httpbin.org/get?param1=val1¶m2=val2، میتوان به صورت زیر عمل کرد:
یا میتوان مستقیما URL فوق را وارد کرد و سپس بر روی دکمهی send کلیک نمود و یا در ذیل این قسمت، در برگهی Params نیز این کوئری استرینگها به صورت key/valueهایی ظاهر میشوند که وارد کردن آنها به این نحو سادهتر است؛ خصوصا اگر تعداد این پارامترها زیاد باشد، تغییر پارامترها و آزمایش آنها توسط این رابط کاربری گرید مانند، به سهولت قابل انجام است. همچنین جائیکه علامت check-mark را مشاهده میکنید، میتوان اشارهگر ماوس را قرار داد تا آیکن تغییر ترتیب پارامترها نیز ظاهر شود. به این ترتیب توسط drag & drop میتوان ترتیب این ردیفها را تغییر داد:
اگر نیازی به پارامتری ندارید، میتوانید با عبور اشارهگر ماوس از روی یک ردیف، علامت ضربدر حذف کلی آن ردیف را نیز مشاهده کنید و یا با برداشتن تیک هر کدام میتوان به سادگی و بسیار سریع، بجای حذف یک پارامتر، آنرا غیرفعال و یک URL جدید را تولید و آزمایش کرد که برای آزمایش دستی حالتهای مختلف یک API، صرفهجویی زمانی قابل توجهی را فراهم میکند.
ذخیره سازی عملیات انجام شده
تا اینجا اگر به رابط کاربری تولید شده دقت کنید، بالای هر برگه، یک علامت دایرهای نارنجی رنگ، قابل مشاهدهاست که به معنای عدم ذخیره سازی آن برگهاست.
در همینجا بر روی دکمهی Save کنار دکمهی Send کلیک کنید. اگر دقت کنید، دکمهی Save دیالوگ ظاهر شده غیرفعال است:
علت اینجا است که در Postman نمیتوان یک تک درخواست را به صورت مستقل ذخیره کرد. Postman درخواستها را در مجموعههای خاص خودش (collections) مدیریت میکند؛ چیزی شبیه به پوشهی bookmarks، در یک مرورگر. بنابراین در همینجا بر روی لینک Create collection کلیک کرده و برای مثال نام گروه دلخواهی را مانند httpbin وارد کنید. سپس بر روی دکمهی check-mark کنار آن کلیک نمائید تا این مجموعه ایجاد شود.
اکنون پس از ایجاد این مجموعه و انتخاب آن، دکمهی Save to httpbin در پایین صفحه ظاهر میشود.
به صورت پیشفرض، نام فیلد درخواست، در این صفحهی دیالوگ، همان آدرس درخواست است که قابلیت ویرایش را نیز دارد. بنابراین برای مثال فیلد request name را به Get request تغییر داده و سپس بر روی دکمهی Save to httpbin کلیک کنید.
نتیجهی این عملیات را در برگهی Collections سمت چپ صفحه میتوان مشاهده کرد. در این حالت اگر درخواست مدنظری را انتخاب کنید و سپس جزئیات آنرا ویرایش کنید، مجددا همان علامت دایرهای نارنجی رنگ، بالای برگهی ساخت درخواست ظاهر میشود که بیانگر حالت ذخیره نشدهی این درخواست است. اکنون اگر بر روی دکمهی Save کنار Send کلیک کنید، در همان آیتم گروه جاری انتخابی، به صورت خودکار ذخیره و بازنویسی خواهد شد.
ارسال درخواستهایی از نوع POST
برای آزمایش ارسال یک درخواست از نوع Post، مجددا بر روی دکمهی + کنار آخرین برگهی باز شده کلیک میکنیم تا یک برگهی جدید باز شود. سپس در ابتدا، نوع درخواست را از Get پیشفرض، به Post تغییر میدهیم:
در این حالت آدرس https://httpbin.org/post را وارد کرده و سپس برگهی body را که پس از انتخاب حالت Post فعال شدهاست، انتخاب میکنیم:
در اینجا برای مثال گزینهی x-www-form-urlencoded، همان حالتی است که اطلاعات را از طریق یک فرم واقع در صفحات وب به سمت سرور ارسال میکنیم. اما اگر برای مثال نیاز باشد تا اطلاعات را با فرمت JSON، به سمت Web API ای ارسال کنیم، نیاز است گزینهی raw را انتخاب کرد و سپس قالب پیشفرض آنرا که text است به JSON تغییر داد:
در اینجا برای مثال یک payload ساده را ایجاد کرده و سپس بر روی دکمهی send کلیک کنید تا به عنوان بدنهی درخواست، به سمت Web API ارسال شود:
که نتیجهی آن چنین خروجی از سمت سرور خواهد بود:
در یک قسمت آن، raw data ما مشخص است و در قسمتی دیگر، اطلاعات با فرمت JSON، به درستی تشخیص دادهاست.
در ادامه بر روی دکمهی Save این برگه کلیک کنید. در صفحهی باز شده، نام پیشفرض آنرا که آدرس درخواست است، به Post request تغییر داده، گروه httpbin را انتخاب و سپس بر روی دکمهی Save to httpbin کلیک کنید:
اکنون مجموعهی httpbin به همراه دو درخواست است:
برای آزمایش آن، تمام برگههای باز را با کلیک بر روی دکمهی ضربدر آنها ببندید. در ادامه اگر بر روی هر کدام از آیتمهای این مجموعه کلیک کنید، جزئیات آن قابل بازیابی خواهد بود.
Jon P. Smith, author of Entity Framework Core in Action, explains what a multi-tenant app is and then digs into the things you need to do to make a multi-tenant app using ASP.NET Core with EF Core.
00:00 Countdown
02:19 Introduction and Community Links
18:25 What are multi-tenant web applications?
21:14 Single level multi-tenant demo
29:00 Partitioning tenants with EF Core QueryFilter
38:00 Admin features: creating users and tenants
43:00 Q&A
43:00 Hierarchical multi-tenant
59:00 How to get started
1:06:30 Database sharding and connection string management
1:16:30 Scaling with Azure SQL Elastic Pools
1:19:00 Conclusion
ساخت دیتابیس sqlite با EF6 Code First
PM> update-package
ب) همچنین رشتهی اتصالی آن باید به محل دقیق قرارگیری فایل phonebook.sqlite اشاره کند.
ج) نام بستههای موجود در فایل packages.config خودتان را با نمونهی پروژهی پیوستی مطابقت دهید.
این موارد را بررسی کنید؛ وگرنه EF 6x در حال توسعهی پیوسته نیست و تغییر خاصی از زمان نگارش این مطلب نداشتهاست.
+ اگر میخواهید نسخهی EF Core آنرا بررسی کنید:
- نیاز است سری EF Core را در سایت از ابتدا مطالعه کنید.
- VS 2015 دیگر برای کار با NET Core. مناسب نیست. حتما نیاز است VS 2017 را نصب کنید. اطلاعات بیشتر
- پیشنیازهای جدید کار با SQLite در فایل csproj آن برای VS 2017 و EF Core 1.1.1 به صورت ذیل هستند:
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0" PrivateAssets="All" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.*" /> </ItemGroup> </Project>
public class ClientSideDbContext : DbContext { // ... protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { configurationBuilder .Properties<string>() .HaveMaxLength(500) .UseCollation("nocase"); configurationBuilder .Properties<decimal>() .HavePrecision(12, 2); } }