public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build();
آشنایی با الگوی Adapter
EF Code First #12
public class MyDbContextBase : DbContext, IUnitOfWork
البته اگر از MVC استفاده کنید این data annotation در سمت کلاینت هم به صورت خودکار اعمال میشود.
- نصب آسان با استفاده از Nuget
- بدون نیاز به هیچگونه وب سرویس و یا دانش پیاده سازی سیستمهای پرداخت آنلاین
- پشتیبانی از درگاههای: ملت، ملی (سداد)، پارسیان، پاسارگاد، ایران کیش، سامان و آسان پرداخت ، زرین پال، پی آی آر و آی دی پی
- انجام پرداخت، فقط با نوشتن ۳ خط کد
- طراحی کاملا یکپارچه برای انجام عملیات پرداخت با تمامی بانکها
- رعایت نکات امنیتی پرداخت آنلاین
- درگاه مجازی، برای شبیه سازی عملیات پرداخت
- امکان استفاده از پروکسی برای سرورهای خارج از ایران در صورت نیاز
- استفاده از تکنولوژیهای مدرن و استاندارد
- قابل نصب بر روی پروژههای: ASP.NET Core, ASP.NET MVC, ASP.NET WebForms
دسترسی به اطلاعات درخواست وب جاری در ASP.NET Core
برای دسترسی به اطلاعات درخواست جاری در ASP.NET Core، میتوان از طریق تزریق سرویس جدید IHttpContextAccessor اقدام کرد. این اینترفیس دارای تک خاصیت HttpContext است که به صورت پیش فرض جزو سرویسهای از پیش ثبت شدهی ASP.NET Core نیست و برای اینکه تزریق وابستگیها در اینجا به درستی صورت گیرد، طول عمر این سرویس باید به صورت singleton تنظیم شود:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); }
- هر زمانیکه درخواست جدیدی برای پردازش فرا میرسد، IHttpContextFactory کار ایجاد یک HttpContext جدید را آغاز میکند.
- اگر سرویس IHttpContextAccessor پیشتر ثبت شده باشد، IHttpContextFactory کار مقدار دهی HttpContext آنرا نیز انجام میدهد.
- اینجا شاید این سؤال مطرح شود که طول عمر IHttpContextAccessor «باید» به صورت singleton ثبت شود. پس این سرویس چگونه میتواند HttpContextهای مختلفی را شامل شود؟ کلاس HttpContextAccessor که پیاده سازی کنندهی IHttpContextAccessor است، دارای یک خاصیت AsyncLocal است که از این خاصیت جهت ذخیره سازی اطلاعات Contextهای مختلف استفاده میشود. بنابراین کلاس HttpContextAccessor دارای طول عمر singleton است، اما خاصیت AsyncLocal آن دارای طول عمری محدود به یک درخواست (request scoped) میباشد.
بنابراین به صورت خلاصه:
- هرجایی که نیاز به اطلاعات HTTP context وجود داشت، از تزریق اینترفیس IHttpContextAccessor استفاده کنید.
- ثبت سرویس IHttpContextAccessor را در ابتدای برنامه فراموش نکنید.
- طول عمر سرویس ثبت شدهی IHttpContextAccessor باید singleton باشد.
یک نکته: اگر از ASP.NET Core Identity استفاده میکنید، متد services.AddIdentity کار ثبت سرویس IHttpContextAccessor را نیز انجام میدهد.
یک مثال: ذخیره سازی اطلاعاتی با طول عمر کوتاه در HttpContext و سپس دسترسی به آنها در کلاسهای دیگر برنامه
استفادهی از مجموعهی Items شیء HttpContext، یکی از روشهایی است که از آن میتوان جهت ذخیره سازی اطلاعات موقتی و محدود به طول عمر درخواست جاری استفاده کرد. برای مثال در یک کنترلر و اکشن متدی خاص، دو key/value جدید را به آن اضافه میکنیم:
public IActionResult ProcessForm() { HttpContext.Items["firstname"] = "Vahid"; HttpContext.Items["lastname"] = "N."; return View(); }
public class MyHelperClass { private readonly IHttpContextAccessor _contextAccessor; public MyHelperClass(IHttpContextAccessor contextAccessor) { _contextAccessor = contextAccessor; } public string DoWork() { string firstName = _contextAccessor.HttpContext.Items["firstname"].ToString(); string lastName = _contextAccessor.HttpContext.Items["lastname"].ToString(); return $"Hello {firstName} {lastName}!"; } }
__arglist __reftype __makeref __refvalue کلمات کلیدی
var i = 28; TypedReference tr = __makeref( i ); Type t = __reftype( tr ); Console.WriteLine( t ); int rv = __refvalue( tr, int ); Console.WriteLine( rv ); ArglistTest.DisplayNumbers( __arglist( 1, 2, 3, 5, 6 ) );
public static class ArglistTest { public static void DisplayNumbers( __arglist ) { var ai = new ArgIterator( __arglist ); while ( ai.GetRemainingCount() > 0 ) { var tr = ai.GetNextArg(); Console.WriteLine( TypedReference.ToObject( tr ) ); } } }
Environment.NewLine
Console.WriteLine( "NewLine: {0}first line{0}second line{0}third line", Environment.NewLine );
ExceptionDispatchInfo
ExceptionDispatchInfo possibleException = null; try { int.Parse( "a" ); } catch ( FormatException ex ) { possibleException = ExceptionDispatchInfo.Capture( ex ); } possibleException?.Throw();
Debug.Assert & Debug.WriteIf & Debug.Indent
Debug.Assert(1 == 0, "عدد 1 برابر با 0 نیست");
Debug.WriteIf( 1 == 1, "display message in output window :D" );
Debug.WriteLine("تست تورفتگی"); Debug.Indent(); Debug.WriteLine("یک واحد افزایش داده شد"); Debug.Unindent(); Debug.WriteLine("یک واحد کاهش داده شد"); Debug.WriteLine("پایان تست");
Mvc File Manager
Admin (Full access) FileManager_Read(readonly access) FileManager_Write(Creat Folder & upload file) FileManager_Change(Move & Rename) FileManager_Delete(Delete file and Folder
این مرورگرها در صورتیکه پیاده سازی پروتکل Open Search را در سایت شما پیدا کنند، به صورت خودکار امکان افزودن آنرا به عنوان منبع جستجوی جدیدی جهت جعبه متنی جستجوی خود ارائه میدهند. در ادامه قصد داریم با جزئیات پیاده سازی آن آشنا شویم.
تهیه OpenSearchResult سفارشی
برنامه باید بتواند محتوای XML ایی ذیل را مطابق پروتکل Open Search به صورت پویا تهیه و در اختیار مرورگر قرار دهد:
<?xml version="1.0" encoding="UTF-8" ? /> <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> <ShortName>My Site's Asset Finder</ShortName> <Description>Find all your assets</Description> <Url type="text/html" method="get" template="http://MySite.com/Home/Search/?q=searchTerms"/> <InputEncoding>UTF-8</InputEncoding> <SearchForm>http://MySite.com/</SearchForm> </OpenSearchDescription>
using System; using System.Text; using System.Web; using System.Web.Mvc; using System.Xml; namespace WebToolkit { public class OpenSearchResult : ActionResult { public string ShortName { set; get; } public string Description { set; get; } public string SearchForm { set; get; } public string FavIconUrl { set; get; } public string SearchUrlTemplate { set; get; } public override void ExecuteResult(ControllerContext context) { if (context == null) throw new ArgumentNullException("context"); var response = context.HttpContext.Response; writeToResponse(response); } private void writeToResponse(HttpResponseBase response) { response.ContentEncoding = Encoding.UTF8; response.ContentType = "application/opensearchdescription+xml"; using (var xmlWriter = XmlWriter.Create(response.Output, new XmlWriterSettings { Indent = true })) { xmlWriter.WriteStartElement("OpenSearchDescription", "http://a9.com/-/spec/opensearch/1.1/"); xmlWriter.WriteElementString("ShortName", ShortName); xmlWriter.WriteElementString("Description", Description); xmlWriter.WriteElementString("InputEncoding", "UTF-8"); xmlWriter.WriteElementString("SearchForm", SearchForm); xmlWriter.WriteStartElement("Url"); xmlWriter.WriteAttributeString("type", "text/html"); xmlWriter.WriteAttributeString("template", SearchUrlTemplate); xmlWriter.WriteEndElement(); xmlWriter.WriteStartElement("Image"); xmlWriter.WriteAttributeString("width", "16"); xmlWriter.WriteAttributeString("height", "16"); xmlWriter.WriteString(FavIconUrl); xmlWriter.WriteEndElement(); xmlWriter.WriteEndElement(); xmlWriter.Close(); } } } }
تهیه OpenSearchController
در ادامه برای استفاده از Action Result سفارشی تهیه شده، نیاز است یک کنترلر را نیز به برنامه اضافه کنیم:
using System.Web.Mvc; namespace Readers { public partial class OpenSearchController : Controller { public virtual ActionResult Index() { var fullBaseUrl = Url.Action(result: MVC.Home.Index(), protocol: "http"); return new OpenSearchResult { ShortName = ".NET Tips", Description = ".NET Tips Contents Search", SearchForm = fullBaseUrl, FavIconUrl = fullBaseUrl + "favicon.ico", SearchUrlTemplate = Url.Action(result: MVC.Search.Index(), protocol: "http") + "?term={searchTerms}" }; } } }
الف) آدرسهای مطرح شده در آن باید مطلق باشند و نه نسبی. به همین جهت پارامتر protocol در اینجا ذکر شده است تا سبب تولید یک چنین آدرسهایی گردد.
ب) Url.Action ایی که در اینجا استفاده شده است مطابق تعاریف T4MVC است؛ ولی کلیات آن با نمونه پیش فرض ASP.NET MVC تفاوتی نمیکند. توسط T4MVC بجای ذکر نام اکشن متد و کنترلر مد نظر به صورت رشتهای، میتوان به صورت Strongly typed به این موارد ارجاع داد.
ج) تنها نکته مهم این کلاس، خاصیت SearchUrlTemplate است. قسمت انتهایی آن یعنی ={searchTerms} همیشه ثابت است. اما ابتدای این آدرس باید به کنترلر جستجوی شما که قادر است پارامتری را به شکل کوئری استرینگ دریافت کند، اشاره نماید.
د) FavIconUrl به آدرس یک آیکن در سایت شما اشاره میکند. برای نمونه ذکر favicon.ico پیش فرض سایت میتواند مفید باشد.
معرفی OpenSearchController به Header سایت
<link href="@Url.Action(result: MVC.OpenSearch.Index(), protocol: "http")" rel="search" title=".NET Tips Search" type="application/opensearchdescription+xml" />
پیاده سازی الگوی Decorator به کمک سیستم تزریق وابستگیهای NET Core.
مثال زیر را در نظر بگیرید که در آن یک سرویس تعریف شدهاست و در این بین استثنائی رخ دادهاست.
public interface ITaskService { void Run(); } public class MyTaskService : ITaskService { public void Run() { throw new InvalidOperationException("An exception from the MyTaskService!"); } }
using System; using Microsoft.Extensions.Logging; namespace CoreIocServices { public class MyTaskServiceDecorator : ITaskService { private readonly ILogger<MyTaskServiceDecorator> _logger; private readonly ITaskService _decorated; public MyTaskServiceDecorator( ILogger<MyTaskServiceDecorator> logger, ITaskService decorated) { _logger = logger; _decorated = decorated; } public void Run() { try { _decorated.Run(); } catch (Exception ex) { _logger.LogCritical(ex, "An unhandled exception has been occurred."); } } } }
مزیت اینکار، پیاده سازی اصل DRY یا Don't repeat yourself است. کاری که برای رفع این مشکل قرار است انجام دهیم، استفاده از یک تزئین کننده (محصور کننده)، کپسوله سازی اعمال تکراری و سپس اتصال آن به قسمتهای مختلف برنامه است. همچنین در این حالت اصل open closed principle نیز بهتر رعایت خواهد شد. از این جهت که کدهای تکراری برنامه به یک لایهی دیگر منتقل شدهاند و دیگر نیازی نیست برای تغییر آنها، کدهای قسمتهای اصلی برنامه را تغییر داد (کدهای برنامه باز خواهند بود برای توسعه و بسته برای تغییر).
پس از طراحی این تزئین کننده، اکنون نوبت به معرفی آن به سیستم تزریق وابستگیهای NET Core. است:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<MyTaskService>(); services.AddTransient<ITaskService>(serviceProvider => new MyTaskServiceDecorator( serviceProvider.GetService<ILogger<MyTaskServiceDecorator>>(), serviceProvider.GetService<MyTaskService>()) );
در اینجا هم میتوان در صورت نیاز اصل کلاس MyTaskService را بدون هیچ نوع تزئین کنندهای از سیستم تزریق وابستگیها دریافت کرد و یا اگر وهلهای از سرویس ITaskService را از آن درخواست کردیم، ابتدا شیء MyTaskServiceDecorator وهله سازی شده و سپس توسط آن یک نمونهی محصور شده و تزئین شدهی MyTaskService به فراخوان بازگشت داده خواهد شد.
ساده سازی معرفی تزئین کنندهها به سیستم تزریق وابستگیهای NET Core. به کمک Scrutor
در «قسمت هشتم - ساده سازی معرفی سرویسها توسط Scrutor» با کتابخانهی Scrutor آشنا شدیم. یکی دیگر از قابلیتهای آن، امکان ساده سازی تعریف تزئین کنندها است:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<ITaskService, MyTaskService>(); services.Decorate<ITaskService, MyTaskServiceDecorator>();
در ادامه مطلب قبلی، یکی از مشکلاتی که طراحی Builder از آن رنج میبرد، نقض کردن قانون command query separation است که در ادامه دربارهی این اصل بیشتر بحث خواهیم کرد.
اصل Command query separation یا به اختصار CQS، در کتاب Object-Oriented Software Construction توسط Bertrand Meyer معرفی شدهاست. بر اساس آن، عملیاتهای سیستم باید یا Command باشند و یا Query و نه هر دوی آنها. وقتی یک کلاینت به امضای یک متد توجه میکند، اینکه این متد چه کاری را انجام میدهد Commands نام داشته و به شیء فرمان میدهد تا کاری را انجام بدهد. این عملیات وضعیت خود شیء و یا اشیاء دیگر را تغییر میدهد. در اینجا Queries به شیء فرمان میدهند تا نتیجهی سؤال ( ویا درخواست) را برگرداند.
در آن سوی دیگر، متدهایی را که وضعیت شیء را تغییر میدهند، به عنوان Command در نظر میگیریم (بدون آنکه مقداری را برگردانند). اگر این نوع متدها، مقداری را برگردانند، باعث سردرگمی کلاینت میشوند؛ زیرا کلاینت نمیداند این متد باعث تغییر شیء شدهاست و یا Query؟
public class FileStore { public string WorkingDirectory { get; set; } public string Save(int id, string message) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); File.WriteAllText(path, message); return path; } public event EventHandler<MessageEventArgs> MessageRead; public void Read(int id) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); var msg = File.ReadAllText(path); this.MessageRead(this, new MessageEventArgs { Message = msg }); } }
public string Read(int id) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); var msg = File.ReadAllText(path); this.MessageRead(this, new MessageEventArgs { Message = msg }); return msg; }
public void Save(int id, string message) { var path = Path.Combine(this.WorkingDirectory + id + ".txt"); File.WriteAllText(path, message); }
public class FileStore { public string WorkingDirectory { get; set; } public void Save(int id, string message) { var path = GetFileName(id); //ok to query from Command File.WriteAllText(path, message); } public string Read(int id) { var path = GetFileName(id); var msg = File.ReadAllText(path); return msg; } public string GetFileName(int id) { return Path.Combine(this.WorkingDirectory , id + ".txt"); } }
چکیده:
هدف اصلی از طراحی نرم افزار، غالب شدن بر پیچیدگیها میباشد. اصل CQS متدها را به دو دستهی Command و Query تقسیم میکند که Query ، اطلاعاتی را از وضعیت سیستم بر میگرداند، ولی command وضعیت سیستم را تغییر میدهد و مهمترین دستاورد CQS ما را به سمت کدی تمیزتر و با قابلیت درک بهتر میرساند.