الف) به قسمت application pools در IIS Manager مراجعه کرده و گزینهی add application pool را انتخاب کنید. سپس یک نمونهی جدید را برای مثال به نام no-managed ایجاد کنید که net clr version. آن به گزینهی no managed code اشاره میکند.
ب) پس از publish برنامه مطابق مطلب فوق (برای مثال با اجرای دستور dotnet publish -c Release) و معرفی مسیر پوشهی publish به IIS (برای مثال به عنوان یک Application جدید ذیل default web site با کلیک راست بر روی default web site و انتخاب گزینهی Add application)، این application pool جدید را به برنامهی خود در IIS نسبت دهید. برای اینکار basic settings سایت را باز کرده و بر روی دکمهی select که در کنار نام application pool هست، کلیک کرده و گزینهی no-managed قسمت الف را انتخاب کنید.
نکته 1: برنامههای blazor wasm، یا standalone هستند و یا hosted. مورد standalone یعنی کاری به Web API ندارد و به خودی خود، به صورت یک سایت استاتیک قابل مشاهدهاست. حالت hosted یعنی به همراه web api هم هست و توسط دستور برای مثال «dotnet new blazorwasm -o BlazorIIS --hosted --no-https» ایجاد میشود که به همراه سه پوشهی کلاینت، سرور و shared است. برای توزیع حالت متکی به خود، فقط محتویات پوشهی publish، به عنوان مسیر برنامه، در IIS معرفی خواهند شد. در حالت hosted، مسیر اصلی، پوشهی publish مربوط به پروژهی سرور است؛ یعنی: Server\bin\Release\net5.0\publish. در این حالت پوشهی Client\bin\Release\net5.0\publish باید به داخل همین پوشهی publish سرور کپی شود. یعنی پوشهی publish پروژهی client باید به درون پوشهی publish پروژهی server کپی شود تا با هم یک برنامهی قابل توزیع توسط IIS را تشکیل دهند.
ج) اکنون اگر برنامه را برای مثال با مسیر فرضی جدید http://localhost/blazortest اجرا کنید، خطای 500.19 را دریافت میکنید. علت آنرا در مطلب «بررسی خطاهای ممکن در حین راه اندازی اولیه برنامههای ASP.NET Core در IIS» بررسی کردهایم. باید IIS URL Rewrite ماژول را نصب کرد؛ تا این مشکل برطرف شود. همچنین دلیل دیگر مشاهدهی این خطا، عدم نصب بستهی هاستینگ متناظر با شماره نگارش NET. مورد استفادهاست (اگر برنامهی شما از نوع hosted است و web api هم دارد).
د) پس از آن باز هم برنامه اجرا نمیشود! اگر در خطاها دقت کنید، به دنبال اسکریپتهایی شروع شده از مسیر localhost است و نه از پوشهی جدید blazortest. برای رفع این مشکل باید فایل publish\wwwroot\index.html را مطابق نکتهی base-URL که کمی بالاتر ذکر شد، ویرایش کرد تا blazor بداند که فایلها، در چه مسیری قرار دارند (و در ریشهی سایت واقع نشدهاند):
<head> <base href="/blazortest/" />
- تنظیمات پیش فرض باید تغییر کنند تا کلمات عبور حداقل 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 قرار دارد.
از کاربردهای چنین سیستمی به سایتهای قابل حمل، و یا ارائه خدمات یک نرم افزار بر روی صفحات html میتوان اشاره کرد. مثل گوگل دسکتاپ و یا گزارشات برخی سرویسهای ویندوزی و یا حتی تنظیم یک سخت افزار متصل به سیستم از روی شبکه. یک ایده جالب میتواند ارسال اس ام اس از طریق شبکه و با جی اس ام مودم باشد. که به عنوان مثال کاربران با ورود به یک صفحه و ثبت پیام بتوانند از طریق جی اس ام مودم متصل به سرور آن را ارسال کنند. با یک مثال ساده ادامه میدهیم.
برای شروع یک پروژه از نوع Console بسازید و در Package manager کتابخانه Nancy.Hosting.Self را نصب کنید.
حالا یک ماژول جدید به نام TestModule.cs به پروژه اضافه میکنیم.
public class TestModule:NancyModule { public TestModule() { Get["/"] = x=> { return "It is a test for nancy self hosting."; }; } }
حالا وارد program.cs شده و در متدMain کد زیر را مینویسیم:
var selfHost = new NancyHost(new Uri("http://localhost:12345")); selfHost.Start(); Console.ReadKey(); selfHost.Stop();
در خط اول پورتی که منتظر دریافت درخواستهای کاربران است را برابر 12345 قرار میدهیم. بنابراین برای تست این کد باید در مرورگر آدرس
http://localhost:12345 را تایپ کنید. اگر بخواهیم کاربر عدد انتهایی را وارد نکند باید از پورت 80 استفاده کنیم که پیش فرض http است ولی اکثرا در سیستم برنامه نویسها توسط IIS مشغول میباشد.
در خط بعد سرور را اجرا کرده ایم و برنامه را به حالت انتظار برای فشرده شدن کلیدی در کنسول برده ایم.
وقتی کلیدی در کنسول فشرده شود سرور به حالت توقف میرود و اجرای برنامه پایان مییابد.
Nancy امکانات دیگری هم دارد. به عنوان مثال میتوان برای طراحی نمای ماژولها از موتورهای دید استفاده کرد (ViewEngines). موتورهایی مثل Razor و ... . در صورت علاقمندی دوستان، در این باره هم خواهم نگاشت.
@Input('readOnly') FormIsReadOnly: boolean;
ردیابی تغییرات اعمال شده بر روی خصوصیت
- ردیابی تغییرات صورت گرفته از طریق تنظیم setter به متغیر تعریف شده با متادیتای Input@
- پیاده سازی onChanges توسط کامپوننت فرزند جهت ردیابی تغییرات کامپوننت
ردیابی تغییرات از طریق تنظیم setter
<app-customer-info FormIsReadOnly="true"></app-customer-info>
@Component({ selector: 'app-customer-info', templateUrl: './customer-info.component.html', styleUrls: ['./customer-info.component.css'] }) export class CustomerInfoComponent implements OnInit { private _formIsReadOnly: boolean; @Input() set FormIsReadOnly(value: boolean) { if (typeof (value) != 'boolean') throw new Error(`${value} type is not boolean.`); this._formIsReadOnly = value; } get FormIsReadOnly(): boolean { return this._formIsReadOnly; } constructor() { } ngOnInit() { } }
پیاده سازی onChanges توسط کامپوننت فرزند جهت ردیابی تغییرات کامپوننت
import { Component, OnInit, Input, OnChanges, SimpleChange } from '@angular/core'; import { ICustomerInfo } from '../../core/model/ICustomerInfo'; @Component({ selector: 'app-customer-info', template: '<ul>< li *ngFor="let change of changeLog">{{change }}</li></ul>', styleUrls: ['./customer-info.component.css'] }) export class CustomerInfoComponent implements OnChanges { changeLog: string[] = []; @Input() FormIsReadOnly: boolean; ngOnChanges(changes: { [propKey: string]: SimpleChange }) { let log: string[] = []; for (let propName in changes) { let changedProp = changes[propName]; let to = JSON.stringify(changedProp.currentValue); if (changedProp.isFirstChange()) { log.push(`Initial value of ${propName} set to ${to}`); } else { let from = JSON.stringify(changedProp.previousValue); log.push(`${propName} changed from ${from} to ${to}`); } } this.changeLog.push(log.join(', ')); } constructor() { } }
• Initial value of FormIsReadOnly set to true • FormIsReadOnly changed from true to "trued" • FormIsReadOnly changed from "trued" to "true" • FormIsReadOnly changed from "true" to "truef" • FormIsReadOnly changed from "truef" to "true" • FormIsReadOnly changed from "true" to "tru" • FormIsReadOnly changed from "tru" to "tr" • FormIsReadOnly changed from "tr" to "t" • FormIsReadOnly changed from "t" to "" • FormIsReadOnly changed from "" to "t" • FormIsReadOnly changed from "t" to "tr" • FormIsReadOnly changed from "tr" to "tru" • FormIsReadOnly changed from "tru" to "true"
در ادامه عنوان «بهجریان انداختن رخدادها از کامپوننت فرزند و گرفتن آنها را از طریق کامپوننت پدر» را مورد برسی قرار خواهیم داد.
ادامه دارد/
فعال سازی GZip توکار IIS
تنظیمات پیش فرض فعال سازی ماژول توکار GZip وب سرورهای جدید شامل دو مرحله است:
الف) تنظیمات سرور جهت فعال سازی فشرده سازی
بر روی ویندوزهای سرور، پس از مراجعه به Administrative Tools -> Server Manager و گشودن Roles آن، ذیل قسمت Web Server که در اینجا IIS است، نیاز است نقش جدیدی به نام Performance اضافه شود و مطابق تصویر، هر دو گزینهی فشرده سازی استاتیک و پویا انتخاب گردد.
بنابراین اولین قدم برای عیب یابی کار نکردن GZip توکار IIS، از این مرحله شروع میشود که آیا اصلا ماژول مربوطه نصب هست یا خیر؟
ب) تنظیمات برنامه جهت فعال سازی ماژول GZip
پس از اطمینان از نصب ماژول توکار فشرده سازی صفحات وب IIS در سمت تنظیمات سرور، اکنون باید چند سطر ذیل را به Web.Config برنامه اضافه کرد:
<system.webServer> <httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files"> <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" /> <dynamicTypes> <add mimeType="text/*" enabled="true" /> <add mimeType="message/*" enabled="true" /> <add mimeType="application/x-javascript" enabled="true" /> <add mimeType="application/javascript" enabled="true" /> <add mimeType="application/json" enabled="true" /> <add mimeType="application/json; charset=utf-8" enabled="true" /> <add mimeType="application/atom+xml" enabled="true" /> <add mimeType="application/xaml+xml" enabled="true" /> <add mimeType="*/*" enabled="false" /> </dynamicTypes> <staticTypes> <add mimeType="text/*" enabled="true" /> <add mimeType="message/*" enabled="true" /> <add mimeType="application/x-javascript" enabled="true" /> <add mimeType="application/javascript" enabled="true" /> <add mimeType="application/json" enabled="true" /> <add mimeType="application/json; charset=utf-8" enabled="true" /> <add mimeType="application/atom+xml" enabled="true" /> <add mimeType="application/xaml+xml" enabled="true" /> <add mimeType="*/*" enabled="false" /> </staticTypes> </httpCompression> <urlCompression doStaticCompression="true" doDynamicCompression="true" /> </system.webServer>
این تنظیماتی است که در اکثر سایتها نیز یافت میشود. ذکر آنها اجباری است و پس از اعمال، اگر برنامه را اجرا کنید ... چیزی فشرده نمیشود! علت اصلی را باید در تنظیماتی یافت که مخصوص سرور است و در اینجا ذکر نشدهاند.
تنظیمات مخصوص آستانهی فشرده سازی صفحات
علت اصلی عدم مشاهدهی هدر gzip، در Response برنامه، به frequent hit threshold تنظیم شدهی در IIS بر میگردد. مقدار آن به 2 درخواست در طی 10 ثانیه تنظیم شدهاست. یعنی اگر به صفحهای در طی 10 ثانیه دو درخواست نرسد، فشرده نخواهد شد. این تنظیم را میتوان با مراجعهی به configuration editor نود اصلی سرور وب در IIS manager، ویرایش کرد:
برای نمونه در تصویر فوق، این آستانه به یک درخواست در طی 10 ساعت تنظیم شدهاست. این عدد سبب خواهد شد تا تمامی درخواستهای رسیده حتما فشرده سازی شوند.
این تنظیم معادل یک سطر ذیل در فایل web.config است. اما چون قسمت system.webServer/serverRuntime در تنظیمات سرور قفل شدهاست، هیچ تاثیری نخواهد داشت و حتما باید در سمت سرور و توسط IIS manager اعمال شود:
<system.webServer> <serverRuntime frequentHitThreshold="1" frequentHitTimePeriod="10:00:00" /> </system.webServer>
C:\Windows\System32\inetsrv\appcmd.exe unlock config /section:system.webServer/serverRuntime
یک نکته
اگر از سرورهای پس از 2008 استفاده میکنید، گزینهی staticCompressionIgnoreHitFrequency نیز به تنظیمات serverRuntime اضافه شدهاست که با تنظیم آن به true، از این حد آستانه، برای فایلهای استاتیک صرفنظر خواهد شد.
تنظیمات مخصوص اندازهی فایلهایی که باید فشرده سازی شوند
تنها حد آستانهی درخواست صفحات وب نیست که بر روی فشرده سازی یا عدم آن ثاثیرگذار است. در اینجا میزان CPU Usage سیستم و یا حتی اندازهی Response خروجی نیز مهم هستند که نمونهای از تنظیمات آنرا در شکل ذیل مشاهده میکنید:
در اینجا با تنظیم minFileSizeForComp به 1024، اعلام شدهاست که حجمهایی کمتر از یک کیلوبایت، فشرده سازی نشوند (مقدار پیش فرض آن، بیش از این عدد است).
البته این عدد را به شکل زیر نیز میتوان به تنظیمات httpCompression وب کانفیگ اضافه کرد:
<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files" minFileSizeForComp="1024">
پس از اعمال این تنظیمات نیاز است یکبار IIS را نیز ری استارت کرد.
نتیجه گیری
اگر پس از فعال سازی GZip وب سرور، خروجی برنامه فشرده سازی نشد (Content-Encoding: gzip)، علت اینجا است که هنوز 2 درخواست مورد نیاز، در طی 10 ثانیه به سمت سرور ارسال نشدهاند و تنظیمات پیش فرض این ماژول، جهت حداقل مصرف CPU و فشار بر روی سرور است.
شما نیز با دانستن قواعد ساده کد نویسی برای Text templateها میتوانید از این امکان برای تولید اتوماتیک کلاسها، HTML ، XML و بطور کلی هر نوع فایل متنی استفاده نمایید. کاربردهای عملی Text template بیشتر برای تولید کد از روی دیتابیس و مثلا بر اساس فیلدهای هر جدول و امثال آن میباشد. اما با کمی خلاقیت استفادههای بسیار زیاد و جالبی را میتوانید از آن مشاهده نمایید.
کاربردهایی مانند : تولید خود کار فایل Config، تولید خودکار Unit Test، تولید خودکار کلاسهای CRUD برای هر موجودیت در لایه Data، تولید خودکار SQL برای StoredProcedure ها، تولید خودکار Html و Js، تولید خودکار فرمهای ASPX و ... برای فرمهای ثابت و تکراری با فرآیند مشخص، تولید خودکار مستندات پروژه در قالب Word Document , … ، تولید خودکار فایلهای Excel از اطلاعات خاص و تولید خودکار فرمهای xaml و .....
در این آموزش با قواعد اصلی نوشتن کد برای Text templateها آشنا میشویم و چند نمونه از کاربردهای آن را به عنوان مثال کاربردی مورد بررسی قرار خواهیم داد.
برای شروع قبل از توضیح در مورد قواعد کد نویسی Text template، یک فایل Text template ایجاد نمایید. برای ایجاد، از پنجره Add New Item یک فایل Text template به پروژه اضافه نمایید:
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".txt" #>
Extension نیز پسوند فایل خروجی تولید شده را مشخص مینماید .
اگر در ادامه متن Hello dotnettips را بنویسید و فایل را Save کنید، کنار فایل tt مذکور، یک فایل با همان نام و پسوند txt ایجاد خواهد شد که داخل آن نوشته است:
Hello dotnettips
برای ایجاد فایل خروجی همچنین میتوانید روی فایل tt کلیک راست نموده و از منوی باز شده Run Custom Tool را انتخاب نمایید. توجه داشته باشید که در تنظیمات Custom Tool باید مقدار TextTemplatingFileGenerator وجود داشته باشد:
خوب؛ برای ادامه، باید با قواعد کد نویسی در Text template آشنا شوید. جلسه بعدی قواعد کد نویسی T4 را بررسی خواهیم کرد.