استفاده از NuGet Package Manager برای مدیریت وابستگیها
درباره اهمیت NuGet برای مصرف کنندگان قبلا این مطلب نوشته شده است.
بجای صرف وقت برای اینکه بررسی کنیم آیا این نسخهی جدید کتابخانهی X یا اسکریپت jQuery آمده است یا خیر، میتوان این وظیفه را به NuGet سپرد. علاوه بر این NuGet مزیتهای دیگری هم دارد؛ مثلا تیمهای برنامه نویسی میتوانند کتاب خانههای مشترک خودشان را در مخزنهای سفارشی NuGet قرار دهند و توزیع و Versioning آنرا به NuGet بسپارند.
تکیه بر Abstraction (انتزاع)
Abstraction در طراحی سیستمها منجر به تولید نرم افزار هایی Loosely coupled با قابلیت نگهداری بالا و همچنین فراهم شدن زمینه برای نوشتن Unit Test میشود.
اگر به مطالب قبلی وب سایت برگردیم در مطلب چرا ASP.NET MVC گفته شد که :
2) دستیابی به کنترل بیشتر بر روی اجزای فریم ورک :
در طراحی ASP.NET MVC همهجا interfaceها قابل مشاهد هستند. همین مساله به معنای افزونه پذیری اکثر قطعات تشکیل دهنده ASP.NET MVC است؛ برخلاف ASP.NET web forms. برای مثال تابحال چندین view engine، routing engine و غیره توسط برنامه نویسهای مستقل برای ASP.NET MVC طراحی شدهاند که هیچکدام با ASP.NET web forms میسر نیست. برای مثال از view engine پیش فرض آن خوشتان نمیآید؟ عوضش کنید! سیستم اعتبار سنجی توکار آنرا دوست ندارید؟ آنرا با یک نمونه بهتر تعویض کنید و الی آخر ...
به علاوه طراحی بر اساس interfaceها یک مزیت دیگر را هم به همراه دارد و آن هم ساده سازی mocking (تقلید) آنها است جهت ساده سازی نوشتن آزمونهای واحد.
از کلمهی کلیدی New استفاده نکنید
هر جا ممکن است کار وهله سازی اشیاء را به لایه و حتی Framework دیگری بسپارید. هر زمان اشیاء نرم افزار خودتان را با کلمهی new وهله سازی میکنید اصل Abstraction را فراموش کرده اید. هر زمان اشیاء نرم افزار را مستقیم وهله سازی میکنید در نظر داشته باشید میتوانید آنها را به صورت وابستگی تزریق کنید.
در همین رابطه مطالب زیر را از دست ندهید :
- تزریق وابستگی (dependency injection) به زبان ساده
- تزریق وابستگی (Dependency Injection) و توسعه پذیری
از HttpContext مستقیما استفاده نکنید (از HttpContextBase استفاده کنید)
از .NET 4 به بعد فضای نامی تعریف شده که در بر دارندهی کلاسهای انتزاعی (Abstraction) خیلی از قسمتهای اصلی ASP.NET است. یکی از مواردی که در توسعهی ASP.NET معمولا زیاد استفاده میشود، شیء HttpContext است . استفاده از HttpContextBase را به استفاده از HttpContext ترجیح دهید. اهمیت این موضوع در راستای اهمیت انتزاع (Abstraction) میباشد.
از "رشتههای جادویی" اجتناب کنید
استفاده از رشتههای جادویی در خیلی از جاها کار را ساده میکند؛ بعضی وقتها هم به آنها نیاز است اما مشکلات زیادی دارند :
- رشتهها معنای باطنی ندارند (مثلا : دشوار است که از روی نام یک ID مشخص کنم این ID چطور به ID دیگری مرتبط است و یا اصلا ربط دارد یا خیر)
- با اشتباهات املایی یا عدم رعایت حروف بزرگ و کوچک ایجاد مشکل میکنند.
- به Refactoring واکنش خوبی نشان نمیدهند. (برای درک بهتر این مطلب را بخوانید.)
<p> <label for="FirstName">First Name:</label> <span id="FirstName">@ViewData["FirstName"]</span> </p>
<p> <label for="FirstName">First Name:</label> <span id="FirstName">@Model.FirstName</span> </p>
در رابطه با Magic strings این مطلب را مطالعه بفرمایید.
از نوشتن HTML در کدهای "Backend" خودداری کنید
با توجه به اصل جداسازی وابستگیها (Separation of Concerns) وظیفهی کنترلرها و دیگر کدهای backend رندر کردن HTML نیست. (ساده سازی کنترلر ها) البته در نظر داشته باشید که قطعا تولید HTML در متدهای کمکی کلاس هایی که "تنها" وظیفهی آنها کمک به Viewها جهت تولید کد هست ایرادی ندارد. این کلاسها بخشی از View در نظر گرفته میشوند نه کدهای "backend".
در Viewها "Business logic" انجام ندهید
معکوس بند قبلی هم کاملا صدق میکند ، منظور این است که Viewها تا جایی که ممکن است باید حاوی کمترین Business logic ممکن باشند. در واقع تمرکز Viewها باید استفاده و نحوهی نمایش داده ای که برای آنها فراهم شده باشد نه انجام عملیات روی آن.
استفاده از Presentation Model را به استفاده مستقیم از Business Objectها ترجیح دهید
در مطالب مختلف وب سایت اشاره به اهمین ViewModelها شده است. برای اطلاعات بیشتر بند ج آموزش 11 از سری آموزشهای ASP.NET MVC را مطالعه بفرمایید.
Ifهای شرطی را در Viewها را در متدهای کمکی کپسوله کنید
استفاده از شرطها در View کار توسعه دهنده را برای یک سری اعمال ساده میکند اما میتواند باعث کمی کثیف کاری هم شود. مثلا:
@if(Model.IsAnonymousUser) { <img src="@Url.Content("~/content/images/anonymous.jpg")" /> } else if(Model.IsAdministrator) { <img src="@Url.Content("~/content/images/administrator.jpg")" /> } else if(Model.Membership == Membership.Standard) { <img src="@Url.Content("~/content/images/member.jpg")" /> } else if(Model.Membership == Membership.Preferred) { <img src="@Url.Content("~/content/images/preferred_member.jpg")" /> }
public static string UserAvatar(this HtmlHelper<User> helper) { var user = helper.ViewData.Model; string avatarFilename = "anonymous.jpg"; if (user.IsAnonymousUser) { avatarFilename = "anonymous.jpg"; } else if (user.IsAdministrator) { avatarFilename = "administrator.jpg"; } else if (user.Membership == Membership.Standard) { avatarFilename = "member.jpg"; } else if (user.Membership == Membership.Preferred) { avatarFilename = "preferred_member.jpg"; } var urlHelper = new UrlHelper(helper.ViewContext.RequestContext); var contentPath = string.Format("~/content/images/{0}", avatarFilename) string imageUrl = urlHelper.Content(contentPath); return string.Format("<img src='{0}' />", imageUrl); }
@Html.UserAvatar()
منتظر قسمت بعدی باشید.
In this post I described the problem that by default, DataAnnotation validation doesn't recursively inspect all properties in an object for DataAnnotation attributes. There are several solutions to this problem, but in this post I used the MiniValidation library from Damian Edwards. This simple library provides a convenience wrapper around DataAnnotation validation, as well as providing features like recursive validation. Finally I showed how you can replace the built-in DataAnnotation validation with a MiniValidation-based validator
var builder = WebApplication.CreateBuilder(args); builder.Services.AddOptions<MySettings>() .BindConfiguration("MySettings") .ValidateMiniValidation() // 👈 Replace with mini validation .ValidateOnStart(); var app = builder.Build();
OptionsValidationException: DataAnnotation validation failed for 'MySettings' member: 'Nested.Value' with errors: 'The Value field is required.'.; DataAnnotation validation failed for 'MySettings' member: 'Nested.Count' with errors: 'The field Count must be between 1 and 100.'. Microsoft.Extensions.Options.OptionsFactory<TOptions>.Create(string name) Microsoft.Extensions.Options.OptionsMonitor<TOptions>+<>c__DisplayClass10_0.<Get>b__0()
<Project> <PropertyGroup> <BlazorMode>Client</BlazorMode> <DefineConstants Condition=" '$(BlazorMode)' == 'Client' ">$(DefineConstants);BlazorClient</DefineConstants> <DefineConstants Condition=" '$(BlazorMode)' == 'Server' ">$(DefineConstants);BlazorServer</DefineConstants> <LangVersion>8.0</LangVersion> </PropertyGroup> </Project>
#if BlazorClient ... #elif BlazorServer ... #endif
Domain Model یا Business Layer
پیاده سازی را از منطق تجاری یا Business Logic آغاز میکنیم. در روش کد نویسی Smart UI، منطق تجاری در Code Behind قرار میگرفت اما در روش لایه بندی، منطق تجاری و روابط بین دادهها در Domain Model طراحی و پیاده سازی میشوند. در مطالب بعدی راجع به Domain Model و الگوهای پیاده سازی آن بیشتر صحبت خواهم کرد اما بصورت خلاصه این لایه یک مدل مفهومی از سیستم میباشد که شامل تمامی موجودیتها و روابط بین آنهاست.
الگوی Domain Model جهت سازماندهی پیچیدگیهای موجود در منطق تجاری و روابط بین موجودیتها طراحی شده است.
شکل زیر مدلی را نشان میدهد که میخواهیم آن را پیاده سازی نماییم. کلاس Product موجودیتی برای ارائه محصولات یک فروشگاه میباشد. کلاس Price جهت تشخیص قیمت محصول، میزان سود و تخفیف محصول و همچنین استراتژیهای تخفیف با توجه به منطق تجاری سیستم میباشد. در این استراتژی همکاران تجاری از مشتریان عادی تفکیک شده اند.
Domain Model را در پروژه SoCPatterns.Layered.Model پیاده سازی میکنیم. بنابراین به این پروژه یک Interface به نام IDiscountStrategy را با کد زیر اضافه نمایید:
public interface IDiscountStrategy { decimal ApplyExtraDiscountsTo(decimal originalSalePrice); }
علت این نوع نامگذاری Interface فوق، انطباق آن با الگوی Strategy Design Pattern میباشد که در مطالب بعدی در مورد این الگو بیشتر صحبت خواهم کرد. استفاده از این الگو نیز به این دلیل بود که این الگو مختص الگوریتم هایی است که در زمان اجرا قابل انتخاب و تغییر خواهند بود.
توجه داشته باشید که معمولا نام Design Pattern انتخاب شده برای پیاده سازی کلاس را بصورت پسوند در انتهای نام کلاس ذکر میکنند تا با یک نگاه، برنامه نویس بتواند الگوی مورد نظر را تشخیص دهد و مجبور به بررسی کد نباشد. البته به دلیل تشابه برخی از الگوها، امکان تشخیص الگو، در پاره ای از موارد وجود ندارد و یا به سختی امکان پذیر است.
الگوی Strategy یک الگوریتم را قادر میسازد تا در داخل یک کلاس کپسوله شود و در زمان اجرا به منظور تغییر رفتار شی، بین رفتارهای مختلف سوئیچ شود.
حال باید دو کلاس به منظور پیاده سازی روال تخفیف ایجاد کنیم. ابتدا کلاسی با نام TradeDiscountStrategy را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class TradeDiscountStrategy : IDiscountStrategy { public decimal ApplyExtraDiscountsTo(decimal originalSalePrice) { return originalSalePrice * 0.95M; } }
سپس با توجه به الگوی Null Object کلاسی با نام NullDiscountStrategy را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class NullDiscountStrategy : IDiscountStrategy { public decimal ApplyExtraDiscountsTo(decimal originalSalePrice) { return originalSalePrice; } }
از الگوی Null Object زمانی استفاده میشود که نمیخواهید و یا در برخی مواقع نمیتوانید یک نمونه (Instance) معتبر را برای یک کلاس ایجاد نمایید و همچنین مایل نیستید که مقدار Null را برای یک نمونه از کلاس برگردانید. در مباحث بعدی با جزئیات بیشتری در مورد الگوها صحبت خواهم کرد.
با توجه به استراتژیهای تخفیف کلاس Price را ایجاد کنید. کلاسی با نام Price را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class Price { private IDiscountStrategy _discountStrategy = new NullDiscountStrategy(); private decimal _rrp; private decimal _sellingPrice; public Price(decimal rrp, decimal sellingPrice) { _rrp = rrp; _sellingPrice = sellingPrice; } public void SetDiscountStrategyTo(IDiscountStrategy discountStrategy) { _discountStrategy = discountStrategy; } public decimal SellingPrice { get { return _discountStrategy.ApplyExtraDiscountsTo(_sellingPrice); } } public decimal Rrp { get { return _rrp; } } public decimal Discount { get { if (Rrp > SellingPrice) return (Rrp - SellingPrice); else return 0; } } public decimal Savings { get{ if (Rrp > SellingPrice) return 1 - (SellingPrice / Rrp); else return 0; } } }
کلاس Price از نوعی Dependency Injection به نام Setter Injection در متد SetDiscountStrategyTo استفاده نموده است که استراتژی تخفیف را برای کالا مشخص مینماید. نوع دیگری از Dependency Injection با نام Constructor Injection وجود دارد که در مباحث بعدی در مورد آن بیشتر صحبت خواهم کرد.
جهت تکمیل لایه Model، کلاس Product را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class Product { public int Id {get; set;} public string Name { get; set; } public Price Price { get; set; } }
موجودیتهای تجاری ایجاد شدند اما باید روشی اتخاذ نمایید تا لایه Model نسبت به منبع داده ای بصورت مستقل عمل نماید. به سرویسی نیاز دارید که به کلاینتها اجازه بدهد تا با لایه مدل در اتباط باشند و محصولات مورد نظر خود را با توجه به تخفیف اعمال شده برای رابط کاربری برگردانند. برای اینکه کلاینتها قادر باشند تا نوع تخفیف را مشخص نمایند، باید یک نوع شمارشی ایجاد کنید که به عنوان پارامتر ورودی متد سرویس استفاده شود. بنابراین نوع شمارشی CustomerType را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public enum CustomerType { Standard = 0, Trade = 1 }
برای اینکه تشخیص دهیم کدام یک از استراتژیهای تخفیف باید بر روی قیمت محصول اعمال گردد، نیاز داریم کلاسی را ایجاد کنیم تا با توجه به CustomerType تخفیف مورد نظر را اعمال نماید. کلاسی با نام DiscountFactory را با کد زیر ایجاد نمایید:
public static class DiscountFactory { public static IDiscountStrategy GetDiscountStrategyFor (CustomerType customerType) { switch (customerType) { case CustomerType.Trade: return new TradeDiscountStrategy(); default: return new NullDiscountStrategy(); } } }
در طراحی کلاس فوق از الگوی Factory استفاده شده است. این الگو یک کلاس را قادر میسازد تا با توجه به شرایط، یک شی معتبر را از یک کلاس ایجاد نماید. همانند الگوهای قبلی، در مورد این الگو نیز در مباحث بعدی بیشتر صحبت خواهم کرد.
لایهی سرویس با برقراری ارتباط با منبع داده ای، دادههای مورد نیاز خود را بر میگرداند. برای این منظور از الگوی Repository استفاده میکنیم. از آنجایی که لایه Model باید مستقل از منبع داده ای عمل کند و نیازی به شناسایی نوع منبع داده ای ندارد، جهت پیاده سازی الگوی Repository از Interface استفاده میشود. یک Interface به نام IProductRepository را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public interface IProductRepository { IList<Product> FindAll(); }
الگوی Repository به عنوان یک مجموعهی در حافظه (In-Memory Collection) یا انباره ای از موجودیتهای تجاری عمل میکند که نسبت به زیر بنای ساختاری منبع داده ای کاملا مستقل میباشد.
کلاس سرویس باید بتواند استراتژی تخفیف را بر روی مجموعه ای از محصولات اعمال نماید. برای این منظور باید یک Collection سفارشی ایجاد نماییم. اما من ترجیح میدهم از Extension Methods برای اعمال تخفیف بر روی محصولات استفاده کنم. بنابراین کلاسی به نام ProductListExtensionMethods را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public static class ProductListExtensionMethods { public static void Apply(this IList<Product> products, IDiscountStrategy discountStrategy) { foreach (Product p in products) { p.Price.SetDiscountStrategyTo(discountStrategy); } } }
الگوی Separated Interface تضمین میکند که کلاینت از پیاده سازی واقعی کاملا نامطلع میباشد و میتواند برنامه نویس را به سمت Abstraction و Dependency Inversion به جای پیاده سازی واقعی سوق دهد.
حال باید کلاس Service را ایجاد کنیم تا از طریق این کلاس، کلاینت با لایه Model در ارتباط باشد. کلاسی به نام ProductService را با کد زیر به پروژه SoCPatterns.Layered.Model اضافه کنید:
public class ProductService { private IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public IList<Product> GetAllProductsFor(CustomerType customerType) { IDiscountStrategy discountStrategy = DiscountFactory.GetDiscountStrategyFor(customerType); IList<Product> products = _productRepository.FindAll(); products.Apply(discountStrategy); return products; } }
در اینجا کدنویسی منطق تجاری در Domain Model به پایان رسیده است. همانطور که گفته شد، لایهی Business یا همان Domain Model به هیچ منبع داده ای خاصی وابسته نیست و به جای پیاده سازی کدهای منبع داده ای، از Interfaceها به منظور برقراری ارتباط با پایگاه داده استفاده شده است. پیاده سازی کدهای منبع داده ای را به لایهی Repository واگذار نمودیم که در بخشهای بعدی نحوه پیاده سازی آن را مشاهده خواهید کرد. این امر موجب میشود تا لایه Model درگیر پیچیدگیها و کد نویسیهای منبع داده ای نشود و بتواند به صورت مستقل و فارغ از بخشهای مختلف برنامه تست شود. لایه بعدی که میخواهیم کد نویسی آن را آغاز کنیم، لایهی Service میباشد.
در کد نویسیهای فوق از الگوهای طراحی (Design Patterns) متعددی استفاده شده است که به صورت مختصر در مورد آنها صحبت کردم. اصلا جای نگرانی نیست، چون در مباحث بعدی به صورت مفصل در مورد آنها صحبت خواهم کرد. در ضمن، ممکن است روال یادگیری و آموزش بسیار نامفهوم باشد که برای فهم بیشتر موضوع، باید کدها را بصورت کامل تست نموده و مثالهایی را پیاده سازی نمایید.
معرفی WebAssembly بر روی Server
WebAssembly is moving beyond the browser and is pitched to become a foundational element of modern cloud-native architecture. It lets any language compile to universal binaries that run on any OS or processor, robustly sandboxed and with great performance. This session covers a new approach to running .NET in WASI environments. You’ll see how your existing .NET code could be built into WASI-compliant modules, plus the opportunities this opens. This is experimental, not yet a committed product.
Here’s a summary of what’s new in this preview release:
Servers & middleware
Antiforgery middleware
API authoring
Antiforgery integration for minimal APIs
Native AOT
Request Delegate Generator supports interceptors feature
Full TrimMode is used for web projects compiled with trimming enabled
WebApplication.CreateEmptyBuilder
Blazor
Antiforgery integration
Server-side form handling improvements
Auto render mode
Register root-level cascading values
Improved integration of interactive components with server-side rendering
New EmptyContent parameter for Virtualize
Identity
New bearer token authentication handler
New API endpoints
Single page apps (SPA)
New Visual Studio templates
Security Advisory Notices
CVE-2019-1077 Visual Studio Extension Auto Update Vulnerability
An elevation of privilege vulnerability exists when the Visual Studio Extension auto-update process improperly performs certain file operations. An attacker who successfully exploited this vulnerability could delete files in arbitrary locations. To exploit this vulnerability, an attacker would require unprivileged access to a vulnerable system. The security update addresses the vulnerability by securing locations the Visual Studio Extension auto-update performs file operations in.
CVE-2019-1075 ASP.NET Core Spoofing Vulnerability
A spoofing vulnerability exists in ASP.NET Core that could lead to an open redirect. An attacker who successfully exploited the vulnerability could redirect a targeted user to a malicious website. To exploit the vulnerability, an attacker could send a link that has a specially crafted URL and convince the user to click the link.
The security update addresses the vulnerability by correcting how ASP.NET Core parses URLs. Details can be found in the .NET Core release notes.
CVE-2019-1113 WorkflowDesigner XOML deserialization allows code execution
A XOML file referencing certain types could cause random code to be executed when the XOML file is opened in Visual Studio. There is now a restriction on what types are allowed to be used in XOML files. If a XOML file containing one of the newly unauthorized types is opened, a message is displayed explaining that the type is unauthorized.
For further information, please refer to https://support.microsoft.com/en-us/help/4512190/remote-code-execution-vulnerability-if-types-are-specified-in-xoml.
Angular 2.0 will be built using the TypeScript language. It will embrace TypeScript's idioms for working with immersive web experiences in larger applications.
You can get those same benefits by working with TypeScript and Angular together. In this session, you'll learn how Angular and TypeScript work together to create single page applications. You'll see how you can leverage the features of ECMAScript 6, and still support today's browsers. You'll see how adopting TypeScript can be as easy as changing the extensions on your .js files. How you use the TypeScript features is completely in your control.