protected override void OnModelCreating( DbModelBuilder modelBuilder ) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.AddFormAssembly( Assembly.GetAssembly( typeof( Entity.Map.CategoryMap ) ) ); }
آشنایی با الگوی MVP
پروژههای زیادی را میتوان یافت که اگر سورس کدهای آنها را بررسی کنیم، یک اسپاگتی کد تمام عیار را در آنها میتوان مشاهده نمود. منطق برنامه، قسمت دسترسی به دادهها، کار با رابط کاربر، غیره و غیره همگی درون کدهای یک یا چند فرم خلاصه شدهاند و آنچنان به هم گره خوردهاند که هر گونه تغییر یا اعمال درخواستهای جدید کاربران، سبب از کار افتادن قسمت دیگری از برنامه میشود.
همچنین از کدهای حاصل در یک پروژه، در پروژههای دیگر نیز نمیتوان استفاده کرد (به دلیل همین در هم تنیده بودن قسمتهای مختلف). حداقل نتیجه یک پروژه برای برنامه نویس، باید یک یا چند کلاس باشد که بتوان از آن به عنوان ابزار تسریع انجام پروژههای دیگر استفاده کرد. اما در یک اسپاگتی کد، باید مدتی طولانی را صرف کرد تا بتوان یک متد را از لابلای قسمتهای مرتبط و گره خورده با رابط کاربر استخراج و در پروژهای دیگر استفاده نمود. برای نمونه آیا میتوان این کدها را از یک برنامه ویندوزی استخراج کرد و آنها را در یک برنامه تحت وب استفاده نمود؟
یکی از الگوهایی که شیوهی صحیح این جدا سازی را ترویج میکند، الگوی MVP یا Model-View-Presenter میباشد. خلاصهی این الگو به صورت زیر است:
Model :
من میدانم که چگونه اشیاء برنامه را جهت حصول منطقی خاص، پردازش کنم.
من نمیدانم که چگونه باید اطلاعاتی را به شکلی بصری به کاربر ارائه داد یا چگونه باید به رخدادها یا اعمال صادر شده از طرف کاربر پاسخ داد.
View :
من میدانم که چگونه باید اطلاعاتی را به کاربر به شکلی بصری ارائه داد.
من میدانم که چگونه باید اعمالی مانند data binding و امثال آن را انجام داد.
من نمیدانم که چگونه باید منطق پردازشی موارد ذکر شده را فراهم آورم.
Presenter :
من میدانم که چگونه باید درخواستهای رسیده کاربر به View را دریافت کرده و آنها را به Model انتقال دهم.
من میدانم که چگونه باید اطلاعات را به Model ارسال کرده و سپس نتیجهی پردازش آنها را جهت نمایش در اختیار View قرار دهم.
من نمیدانم که چگونه باید اطلاعاتی را ترسیم کرد (مشکل View است نه من) و نمیدانم که چگونه باید پردازشی را بر روی اطلاعات انجام دهم. (مشکل Model است و اصلا ربطی به اینجانب ندارد!)
یک مثال ساده از پیاده سازی این روش
برنامهای وبی را بنویسید که پس از دریافت شعاع یک دایره از کاربر، مساحت آنرا محاسبه کرده و نمایش دهد.
یک تکست باکس در صفحه قرار خواهیم داد (txtRadius) و یک دکمه جهت دریافت درخواست کاربر برای نمایش نتیجه حاصل در یک برچسب به نام lblResult
الف) پیاده سازی به روش متداول (اسپاگتی کد)
protected void btnGetData_Click(object sender, EventArgs e)
{
lblResult.Text = (Math.PI * double.Parse(txtRadius.Text) * double.Parse(txtRadius.Text)).ToString();
}
اما این مشکلات را هم دارد:
- منطق برنامه (روش محاسبه مساحت دایره) با رابط کاربر گره خورده.
- کدهای برنامه در پروژهی دیگری قابل استفاده نیست. (شما متد یا کلاسی را اینجا با قابلیت استفاده مجدد میتوانید پیدا میکنید؟ آیا یکی از اهداف برنامه نویسی شیءگرا تولید کدهایی با قابلیت استفاده مجدد نبود؟)
- چگونه باید برای آن آزمون واحد نوشت؟
ب) بهبود کد و جدا سازی لایهها از یکدیگر
در روش MVP متداول است که به ازای هر یک از اجزاء ابتدا یک interface نوشته شود و سپس این اینترفیسها پیاده سازی گردد.
پیاده سازی منطق برنامه:
1- ایجاد Model :
یک فایل جدید را به نام CModel.cs به پروژه اضافه کرده و کد زیر را به آن خواهیم افزود:
using System;
namespace MVPTest
{
public interface ICircleModel
{
double GetArea(double radius);
}
public class CModel : ICircleModel
{
public double GetArea(double radius)
{
return Math.PI * radius * radius;
}
}
}
- خبری از textbox و برچسب و غیره نیست. اصلا نمیداند که رابط کاربری وجود دارد یا نه.
- خبری از رخدادهای برنامه و پاسخ دادن به آنها نیست.
- از این کد میتوان مستقیما و بدون هیچ تغییری در برنامههای دیگر هم استفاده کرد.
- اگر باگی در این قسمت وجود دارد، تنها این کلاس است که باید تغییر کند و بلافاصله کل برنامه از این بهبود حاصل شده میتواند بدون هیچگونه تغییری و یا به هم ریختگی استفاده کند.
- نوشتن آزمون واحد برای این کلاس که هیچگونه وابستگی به UI ندارد ساده است.
2- ایجاد View :
فایل دیگری را به نام CView.cs را به همراه اینترفیس زیر به پروژه اضافه میکنیم:
namespace MVPTest
{
public interface IView
{
string RadiusText { get; set; }
string ResultText { get; set; }
}
}
کار View دریافت ابتدایی مقادیر از کاربر توسط RadiusText و نمایش نهایی نتیجه توسط ResultText است البته با یک اما.
View نمیداند که چگونه باید این پردازش صورت گیرد. حتی نمیداند که چگونه باید این مقادیر را به Model جهت پردازش برساند یا چگونه آنها را دریافت کند (به همین جهت از اینترفیس برای تعریف آن استفاده شده).
3- ایجاد Presenter :
در ادامه فایل جدیدی را به نام CPresenter.cs با محتویات زیر به پروژه خواهیم افزود:
namespace MVPTest
{
public class CPresenter
{
IView _view;
public CPresenter(IView view)
{
_view = view;
}
public void CalculateCircleArea()
{
CModel model = new CModel();
_view.ResultText = model.GetArea(double.Parse(_view.RadiusText)).ToString();
}
}
}
کار این کلاس برقراری ارتباط با Model است.
میداند که چگونه اطلاعات را به Model ارسال کند (از طریق _view.RadiusText) و میداند که چگونه نتیجهی پردازش را در اختیار View قرار دهد. (با انتساب آن به _view.ResultText)
نمیداند که چگونه باید این پردازش صورت گیرد (کار مدل است نه او). نمیداند که نتیجهی نهایی را چگونه نمایش دهد (کار View است نه او).
روش معرفی View به این کلاس به constructor dependency injection معروف است.
اکنون کد وب فرم ما که در قسمت (الف) معرفی شده به صورت زیر تغییر میکند:
using System;
namespace MVPTest
{
public partial class _Default : System.Web.UI.Page, IView
{
protected void Page_Load(object sender, EventArgs e)
{
}
public string RadiusText
{
get { return txtRadius.Text; }
set { txtRadius.Text = value; }
}
public string ResultText
{
get { return lblResult.Text; }
set { lblResult.Text = value; }
}
protected void btnGetData_Click(object sender, EventArgs e)
{
CPresenter presenter = new CPresenter(this);
presenter.CalculateCircleArea();
}
}
}
در اینجا یک وهله از Presenter برای برقراری ارتباط با Model ایجاد میشود. همچنین کلاس وب فرم ما اینترفیس View را نیز پیاده سازی خواهد کرد.
Soft Delete در Entity Framework 6
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>().HasQueryFilter(e => !e.IsDeleted); base.OnModelCreating(modelBuilder); }
امروز داشتم یک سری از پلاگینهای jQuery را مرور میکردم، مورد زیر به نظرم واقعا حرفهای اومد و کمبود آن هم در بین کنترلهای استاندارد ASP.Net محسوس است:
Masked Input Plugin
استفاده از آن به صورت معمولی بسیار ساده است. فقط کافی است اسکریپتهای jQuery و سپس این افزونه به هدر صفحه اضافه شوند و بعد هم مطابق صفحه usage آن عمل کرد.
خیلی هم عالی! ولی این شیوهی متداول کار در ASP.Net نیست. آیا بهتر نیست این مجموعه را تبدیل به یک کنترل کنیم و از این پس به سادگی با استفاده از Toolbox ویژوال استودیو آنرا به صفحات اضافه کرده و بدون درگیر شدن با دستکاری سورس html صفحه، از آن استفاده کنیم؟ بهعبارتی دیگر یکبار باید با جزئیات درگیر شد، آنرا بسته بندی کرد و سپس بارها از آن استفاده نمود. (مفاهیم شیءگرایی)
برای اینکار، یک پروژه جدید ایجاد ASP.Net server control را آغاز نمائید (به نام MaskedEditCtrl).
به صورت پیش فرض یک قالب استاندارد ایجاد خواهد شد که کمی نیاز به اصلاح دارد. نام کلاس را به MaskedEdit تغییر خواهیم داد و همچنین در قسمت ToolboxData نیز نام کنترل را به MaskedEdit ویرایش میکنیم.
برای اینکه مجبور نشویم یک کنترل کاملا جدید را از صفر ایجاد کنیم، خواص و تواناییهای اصلی این کنترل را از TextBox استاندارد به ارث خواهیم برد. بنابراین تا اینجای کار داریم:
namespace MaskedEditCtrl
{
[DefaultProperty("MaskFormula")]
[ToolboxData("<{0}:MaskedEdit runat=server></{0}:MaskedEdit>")]
[Description("MaskedEdit Control")]
public class MaskedEdit : TextBox
{
از ASP.Net 2.0 به بعد، امکان قرار دادن فایلهای اسکریپت و یا تصاویر همراه یک کنترل، درون فایل dll آن بدون نیاز به توزیع مجزای آنها به صورت WebResource مهیا شده است. برای این منظور اسکریپتهای jQuery و افزونه mask edit را به پروژه اضافه نمائید. سپس به قسمت خواص آنها (هر دو اسکریپت) مراجعه کرده و build action آنها را به Embedded Resource تغییر دهید (شکل زیر):
از این پس با کامپایل پروژه، این فایلها به صورت resource به dll ما اضافه خواهند شد. برای تست این مورد میتوان به برنامه reflector مراجعه کرد (تصویر زیر):
پس از افزودن مقدماتی اسکریپتها و تعریف آنها به صورت resource ، باید آنها را در فایل AssemblyInfo.cs پروژه نیز تعریف کرد (به صورت زیر).
[assembly: WebResource("MaskedEditCtrl.jquery.min.js", "text/javascript")]
[assembly: WebResource("MaskedEditCtrl.jquery.maskedinput-1.1.4.pack.js", "text/javascript")]
پس از آن نوبت به افزودن این اسکریپتها به صورت خودکار در هنگام نمایش کنترل است. برای این منظور داریم:
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
//adding .js files
if (!Page.ClientScript.IsClientScriptIncludeRegistered("jquery_base"))
{
string scriptUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),
"MaskedEditCtrl.jquery.min.js");
Page.ClientScript.RegisterClientScriptInclude("jquery_base", scriptUrl);
}
if (!Page.ClientScript.IsClientScriptIncludeRegistered("edit_ctrl"))
{
string scriptUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),
"MaskedEditCtrl.jquery.maskedinput-1.1.4.pack.js");
Page.ClientScript.RegisterClientScriptInclude("edit_ctrl", scriptUrl);
}
if (!Page.ClientScript.IsStartupScriptRegistered("MaskStartup" + this.ID))
{
// Form the script to be registered at client side.
StringBuilder sbStartupScript = new StringBuilder();
sbStartupScript.AppendLine("jQuery(function($){");
sbStartupScript.AppendLine("$(\"#" + this.ClientID + "\").mask(\"" + MaskFormula + "\");");
sbStartupScript.AppendLine("});");
Page.ClientScript.RegisterStartupScript(typeof(Page),
"MaskStartup" + this.ID, sbStartupScript.ToString(), true);
}
}
نکته جاوا اسکریپتی: علت استفاده از this.ClientID جهت معرفی نام کنترل جاری این است که هنگامیکه کنترل توسط یک master page رندر شود، ID آن توسط موتور ASP.Net کمی تغییر خواهد کرد. برای مثال myTextBox به ctl00_ContentPlaceHolder1_myTextBox تبدیل خواهد شد و اگر صرفا this.ID ذکر شده باشد دیگر دسترسی به آن توسط کدهای جاوا اسکریپت مقدور نخواهد بود. بنابراین از ClientID جهت دریافت ID نهایی رندر شده توسط ASP.Net کمک میگیریم.
در اینجا MaskFormula مقداری است که هنگام افزودن کنترل به صفحه میتوان تعریف کرد.
[Description("MaskedEdit Formula such as 99/99/9999")]
[Bindable(true), Category("MaskedEdit"), DefaultValue(0)]
public string MaskFormula
{
get
{
if (ViewState["MaskFormula"] == null) ViewState["MaskFormula"] = "99/99/9999";
return (string)ViewState["MaskFormula"];
}
set { ViewState["MaskFormula"] = value; }
}
نکته مهم: در اینجا حتما باید از view state جهت نگهداری مقدار این خاصیت استفاده کرد تا در حین post back ها مقادیر انتساب داده شده حفظ شوند.
اکنون پروژه را کامپایل کنید. برای افزودن کنترل ایجاد شده به toolbox میتوان مطابق تصویر عمل کرد:
نکته: برای افزودن آیکون به کنترل (جهت نمایش در نوار ابزار) باید: الف) تصویر مورد نظر از نوع bmp باشد با اندازه 16 در16 pixel . ب) باید آنرا به پروژه افزود و build action آن را به Embedded Resource تغییر داد. سپس آنرا در فایل AssemblyInfo.cs پروژه نیز تعریف کرد (به صورت زیر).
[assembly: System.Web.UI.WebResource("MaskedEditCtrl.MaskedEdit.bmp", "img/bmp")]
جهت دریافت سورس کامل و فایل بایناری این کنترل، اینجا کلیک کنید.
افزودن و اعتبارسنجی خودکار Anti-Forgery Tokens در برنامههای Angular مبتنی بر ASP.NET Core
میانافزار طراحی شدهی در این مطلب، پیش از لاگین و با اولین درخواست تک صفحهی برنامه، کوکیهای antiforgery را دریافت میکند. اما ... سیستم antiforgery طوری طراحی شدهاست که پیش از تولید کوکی، مشخصات دقیق this.HttpContext.User را دریافت و هش میکند (لیست Claims آنرا به صورت رشته در میآورد و هش SHA256 آن را محاسبه میکند). از این هش هم جهت تولید محتوای کوکی نهایی خود استفاده میکند. بنابراین در بار اولی که صفحه درخواست شدهاست، یک کوکی antiforgery مخصوص کاربر null و اعتبارسنجی نشده، تولید خواهد شد. بعد از آن پس از لاگین، اگر میانافزار یاد شده مجددا نیز فراخوانی شود، هیچ اتفاق خاصی رخ نخواهد داد. از این جهت که در طراحی متد GetAndStoreTokens آن، به ازای یک صفحه، فقط یکبار این کوکی تولید میشود و اگر هزار بار دیگر هم این متد را جهت برنامهی تک صفحهای خود فراخوانی کنیم، به این معنا نخواهد بود که مشخصات this.HttpContext.User را به کوکی جدیدی اضافه میکند؛ چون اصلا کوکی جدیدی را تولید نمیکند!
بنابراین راه حل نهایی به این صورت است:
الف) میان افزار AngularAntiforgeryTokenMiddleware فوق را حذف کنید. این میانافزار عملا کاربردی برای برنامههای SPA دارای اعتبارسنجی ندارد.
ب) امضای متد Login را به این صورت تغییر دهید که شامل IgnoreAntiforgeryToken باشد:
[AllowAnonymous] [IgnoreAntiforgeryToken] [HttpPost("[action]")] public async Task<IActionResult> Login([FromBody] User loginUser)
ج) در متد لاگین، پس از تولید توکنها، اکنون کار تولید کوکی را به صورت زیر انجام میدهیم:
private void regenerateAntiForgeryCookie(IEnumerable<Claim> claims) { this.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme)); var tokens = _antiforgery.GetAndStoreTokens(this.HttpContext); this.HttpContext.Response.Cookies.Append( key: "XSRF-TOKEN", value: tokens.RequestToken, options: new CookieOptions { HttpOnly = false // Now JavaScript is able to read the cookie }); }
نکتهی مهم! جائیکه Claimهای برنامه را تولید میکنید، باید حتما Issuer را هم ذکر کنید:
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
خلاصه این تغییرات به پروژهی ASPNETCore2JwtAuthentication اعمال شدهاند.
هنگامیکه درحال طراحی کلاسهایی هستیم که وابستگیهایی دارند، ممکن است با شرایطی مواجه شویم که به این وابستگیها نیاز نباشد و یا به رفتار عادی بعضی از وابستگیها نیاز نداشته باشیم. شاید راهی که در این مواقع به ذهن برسد این باشد که بجای شیء واقعی وابستگی موردنظر، از یک شیء Null Reference استفاده کنیم. ولی استفاده از این روش کدهایمان را پیچیده خواهد کرد؛ چون هر جای کد که نیازمند استفادهی از اعضای شیء وابستگی موردنظرمان باشیم، مثلا متدی را فراخوانی کنیم یا از یک پراپرتی آن استفاده کنیم، باید ابتدا از نال بودن یا نبودن آن اطمینان حاصل کنیم و سپس از آن استفاده نماییم؛ چون در غیر این صورت با خطای Null Pointer مواجه میشویم.
الگوی طراحی Null Object این مشکل را حل میکند که جای پاس دادن شیء Null Reference بجای شیء ای که واقعا به آن وابستگی وجود دارد و باید هر بار قبل استفادهی از آن بررسی کنیم که آیا آن شیء ای که داریم با آن کار میکنیم نال است یا خیر، کلاسی خاصی را بسازیم که یک وابستگی غیر کاربردی است. به این معنا که قرار نیست هیچ کاری را انجام دهد و عملا یک non-functional Dependency است. این کلاس یا یک اینترفیس خاصی را پیاده سازی میکند و یا اینکه از یک کلاس انتزاعی ارث بری خواهد کرد؛ ولی هیچ عملکرد خاصی را نخواهد داشت. به این معنا که متدها و پراپرتیهای این کلاس کاری را انجام نداده و یک مقدار پیشفرض و یا یک مقدار خاصی را برگشت خواهند داد. این روش به ساده سازی کد کمک خواهد کرد، چون میتوان بدون انجام پیش شرطهایی مانند بررسی نال بودن یا نبودن یک شیء وابسته، از آن استفاده کرد.
این الگوی طراحی معمولا همراه با دیگر الگوهای طراحی مورد استفاده قرار میگیرد. بهینهتر است که خود کلاس Null Object به صورت Singleton پیاده سازی شود. مزیت این کار در این است که چون شیء ساخته شده از این کلاس، نه کار خاصی را انجام میدهد و نه حالت خاصی را نگه میدارد، پس ساختن شیءای از آن عملا ضرورتی نداشته و هیچگونه ارزشی ندارد و فقط سرباری را بر روی نرم افزار قرار میدهد. پس سزاوار است فقط به یک شیء از این کلاس اکتفا کرد و هر بار همان شیء را برگشت داد. الگوی دیگری که غالبا از الگوی Null Object در آن استفاده میشود، الگوی Strategy است. زمانیکه یکی از استراتژیها این باشد که کار خاصی را انجام نداد و یا استراتژی مورد نظر عملکردی نداشته باشد، از الگوی Null Object استفاده میکنیم. الگوی دیگری که از الگوی Null Object زیاد استفاده میکند، الگوی Factory است. برای مثال هنگامیکه بخواهیم بر طبق شرایط برنامه یک شیء Null Reference را بسازیم و برگردانیم، از الگوی Null Object استفاده خواهیم کرد.
فرض کنید میخواهیم ماژولی را توسعه دهیم که وظیفهی آن گزارش دادن وضعیت وقوع رخدادها است و میخواهیم پیامهای وضعیت، به روشهای مختلفی مانند ارسال ایمیل و یا ثبت لاگ در سرورهای راه دور که برای لاگ گیری تعبیه شدهاند، انجام گیرد و در بعضی از مواقع هم میخواهیم برای برخی از رخدادها نیاز به گزارش نباشد. در این مواقع برای استراتژی سوم از الگوی طراحی Null Object استفاده میکنند.
پیاده سازی الگوی طراحی Null Object
کلاس دیاگرام زیر چگونگی پیاده سازی این الگو را نشان میدهد. در ادامه قصد داریم بخشهای مختلف این دیاگرام را توضیح دهیم.
Client : این کلاس دارای یک وابستگی به یک کلاس دیگر است که در بعضی مواقع نیازی به این وابستگی پیدا نمیکند و در صورتیکه به کارکرد اصلی وابستگی نیاز پیدا نکند، متدهای داخل کلاس Null Object را اجرا میکند.
DependencyBase : این قسمت کلاس پایهای است که به صورت Abstract بوده و شامل همه وابستگیهایی است که ممکن است Client به آن وابسته باشد. همچنین این بخش، کلاس پایهی کلاس Null Object هم است. شایان ذکر است که بجای استفاده از کلاس Abstract میتوان از یک Interface هم استفاده کرد؛ چون این کلاس هیچ عملکرد مشترکی را برای زیر کلاسهایش پیاده سازی نمیکند.
Dependency : این کلاس یک عملکرد واقعی از یک وابستگی است که Client به آن وابسته است.
NullObject : این همان کلاس Null Object است که به عنوان یک وابستگی توسط Client مورد استفاده قرار میگیرد. این کلاس هیچ عملکرد مشخصی را ندارد ولی باید تمام اعضای کلاس پایه، یعنی DependencyBase را پیاده سازی کند.
مثال زیر کدهای اصلی پیاده سازی الگوی طراحی Null Object را نشان خواهد داد که با زبان سی شارپ نوشته شدهاست. کلاس Client، وابستگیهای خود را از طریق سازنده دریافت خواهد کرد که به آن Constructor injection گفته میشود. همانطور که میبینید در کلاس NullObject، تنها متد Operation بازنویسی شده است و داخل آن هیچ عملکرد خاصی پیاده سازی نشده است؛ زیر تنها به وجود آن نیاز است و نه عملکرد داخلی آن.
public class Client { DependencyBase _dependency; public void Client(DependencyBase dependency) { _dependency = dependency; } public void DoSomething() { _dependency.Operation(); } } public abstract class DependencyBase { public abstract void Operation(); } public class Dependency : DependencyBase { public override void Operation() { Console.WriteLine("Dependency.Operation() executed"); } } public class NullObject : DependencyBase { public override void Operation() { } }
یک نمونه واقعی از الگوی طراحی Null Object
در این بخش قصد داریم مثالی از الگوی استراتژی را ارائه دهیم که در یکی از استراتژیهایش از کلاس Null Object استفاده خواهد کرد. در این مثال کلاسی وجود دارد به نام StatusMonitor که پس از انجام کارهایی، وضعیت انجام آن را اعلام میکند. ۳ نوع استراتژی برای اعلام وضعیت انجام کارها متصور است که بسته به موقعیتهای مختلف، یکی از آنها انتخاب خواهد شد. استراتژیهای اعلام وضعیت شامل ارسال ایمیل، ارسال وضعیت به یک وب سرویس و یا اصلا اعلام نکردن وضعیت هستند. زمانیکه قصد داریم هیچگونه وضعیتی اعلام نشود، از نمونهای از کلاس Null Object استفاده خواهد شد که در این مثال کلاس NullStatusReporter این وابستگی را تامین میکند. همه کلاسهای استراتژی که بیان شد تنها شامل یک متد هستند که از آن برای گزارش پیام وضعیت استفاده خواهیم کرد.
کلاسهای EmailStatusReporter و WebServiceStstusReporter در صورتیکه بتوانند به درستی پیامها را گزارش دهند، مقدار true را برگشت خواهند داد و در غیر اینصورت مقدار false برگشت داده میشود. اما کلاس Null Object هیچ کاری را انجام نمیدهد و چیزی را گزارش نمیدهد و تنها مقدار true را برگشت خواهد داد. اینکه این کلاس چه مقداری را برگشت دهد، قراردادی است که بین Client و Dependency انجام میگیرد. به این نکته هم توجه بفرمایید که کلاس NullStatusReporter به صورت Singleton پیاده سازی شده است.
public class StatusMonitor { StatusReporterBase _reporter; public StatusMonitor(StatusReporterBase reporter) { _reporter = reporter; } public void CheckStatus() { // Do something to check status if (!_reporter.Report("Everything's OK")) { Console.WriteLine("Failed to report status."); } } } public abstract class StatusReporterBase { public abstract bool Report(string message); } public class EmailStatusReporter : StatusReporterBase { public override bool Report(string message) { try { Console.WriteLine("Emailed '{0}'.", message); return true; } catch { return true; throw; } } } public class WebServiceStatusReporter : StatusReporterBase { public override bool Report(string message) { try { Console.WriteLine("Sent '{0}' to web service.", message); return true; } catch { return true; throw; } } } public class NullStatusReporter : StatusReporterBase { private static NullStatusReporter _instance; private static object _lock = new object(); private NullStatusReporter() { } public static NullStatusReporter GetReporter() { lock (_lock) { if (_instance == null) _instance = new NullStatusReporter(); } return _instance; } public override bool Report(string message) { return true; } }
تست کلاس Null Object
برای تست کلاس StatusMonitor باید یکی از انواع استرتژیها را برایش تعیین و آن را به سازنده کلاس تزریق کرد و با آن استراتژی، کلاس را تست نمود. در کد زیر از استراتژی NullObject استفاده شدهاست. پس یک نمونهی آن ساخته شده و از طریق سازنده به کلاس StatusMonitor فرستاده میشود. سپس متد CheckStatus فراخوانی میگردد. اما این متد کاری را انجام نمیدهد و تنها مقدار true برگشت داده میشود. بررسی روشهای دیگر را به خودتان واگذار میکنم.
StatusReporterBase reporter = NullStatusReporter.GetReporter(); StatusMonitor monitor = new StatusMonitor(reporter); monitor.CheckStatus();
SettingService
منظورم این بود وقتی با الگوی UnitOfWork ترکیبش کردید .یکسری توابع مثل
public Task<T> LoadSetting<T>() where T : ISettings, new() { throw new NotImplementedException(); }
میشه روش استفاده یا حتی پیاده سازی یکی از این توابع رو بگید
- if else
- switch case
بله مورد اولی که به ذهن خود من رسید، استفاده از if else هست. شاید خروجی مناسبی را از نظر کدنویسی داشته باشد؛ ولی خوانایی مناسبی را ندارد. حالا چطور اثبات کنیم خوانایی و قابلیت توسعهی پایینی را دارد؟
فرض کنید شما برنامه را نوشتهاید و تحویل مدیر خود دادهاید. بعد از دو ماه به شما گفته میشود که مراحل 1 و 2 را جابجا کنید و یا یک step را اضافه کنید که بعد از مرحله دو (بررسی رمز) است تا یک منطق جدید را دنبال کند. اینجاست که دچار دردسر و اتلاف زمان میشویم؛ چون باید بیزینس را مجددا review کنیم و بدتر از آن کدها را هم تغییر دهیم که امکان رخ دادن خطا به شدت بالا میرود.
- انجام کار در چند مرحله
- حذف پیچیدگیهای پیاده سازی
حالا بیایید با هم با الگوی Chain Of Responsibility، این مثال را پیاده سازی کنیم. منطق کار به صورت زیر است:
به این شکل که مراحل بصورت سلسله مراتبی، تحت successorهای یکدیگر پیش میروند. اگر بخواهم successor را در این مثال توضیح دهم من بهعنوان دانشجو (successor اول) بعد از چک شدن موارد مربوط به دانشجو، درخواست به سمت مسئول مربوطه رفته (successor دوم ) و الی اخر.
public class Customer { public string Password { get; set; } public string Stno { get; set; } public int value { get; set; } public bool Active { get; set; } }
public class RequestContext { public int VamValue { get; set; } public Customer student { get; set; } }
public class ResponseContext { public string Response { get; set; } }
حال طبق شکل بالا باید handler خود را که گرفتن وام است، پیاده سازی نماییم:
public abstract class GetVam { protected readonly GetVam successor; public GetVam(GetVam _getVam) { this.successor = _getVam; } public abstract ResponseContext execute(RequestContext requestContext); }
public class CheckUseractive : GetVam { public CheckUseractive(GetVam _getVam) : base(_getVam) { } public override ResponseContext execute(RequestContext requestContext) { if (requestContext.student.Active == true) { return successor.execute(requestContext); } else { return new ResponseContext { Response = "student is inactive" }; } } }
2-بررسی رمز کاربر :
public class ChechPassword : GetVam { public ChechPassword(GetVam _getVam) : base(_getVam) { } public override ResponseContext execute(RequestContext requestContext) { if (requestContext.student.Password == "123") { return successor.execute(requestContext); } else { return new ResponseContext { Response = "invalid pass", }; } } }
3-بررسی میزان بدهکاری دانشجو :
public class ChechUserBedehkar : GetVam { public ChechUserBedehkar(GetVam _getVam) : base(_getVam) { } public override ResponseContext execute(RequestContext requestContext) { if (requestContext.student.value < requestContext.VamValue) { return successor.execute(requestContext); } else { return new ResponseContext { Response = "you are dont permission" }; } } }
4-و مرحله آخر که در صورتیکه تمامی مراحل قبلی پاس شوند چک کردن مقدار وامی است که به دانشجو باید داده شود :
public class AssignVam : GetVam { public AssignVam(GetVam _getVam) : base(_getVam) { } public override ResponseContext execute(RequestContext requestContext) { return new ResponseContext { Response = "value of vam: " + (requestContext.VamValue - requestContext.student.value).ToString(); }; } }
partial class Program { static void Main(string[] args) { Customer customer = new Customer() { Active = true, Password = "123", Stno = "111", value = 2000 }; RequestContext requestContext = new RequestContext() { student = customer, VamValue = 3000, }; var GetVam = new CheckUseractive(new ChechPassword(new ChechUserBedehkar(new AssignVam(null)))); var res = GetVam.execute(requestContext); Console.Write(res.Response); Console.ReadKey(); } }
Long Polling در WCF
در این حالت thread جاری سمت کلاینت نیز در حالت wait است و برنامه سمت کلاینت از کار میافتد تا زمانی که پاسخ از سرور دریافت نماید. اما در WCF به صورت پیش فرض هر درخواست ارسالی باید در طی یک دقیقه در اختیار سرور قرار گیرد و سرور نیز باید در طی یک دقیقه پاسخ مورد نظر را برگرداند(مقادیر خواص SendTimeout و ReceiveTimeout برای مدیریت این موارد به کار میروند). افزایش مقادیر این خواص کمک خاصی به این حالت نمیکند زیرا هم چنان کلاینت در حالت wait است و سرور نیز پاسخ خاصی ارسال نمیکند. حتی اگر کل عملیات را به صورت Async پیاده سازی نماییم باز ممکن است بعد از منقضی شدن زمان پردازش با یک TimeoutException برنامه از کار بیفتد. برای حل اینگونه موارد پیاده سازی سرویسها به صورت Long Polling به ما کمک خوبی خواهد کرد.
حال سناریو زیر را در نظر بگیرید:
سمت سرور:
»یک درخواست دریافت میشود؛
»سرور در حالت wait (البته توسط یک thread دیگر) منتظر تامین منابع برای پاسخ به کلاینت است؛
»در نهایت پاسخ مورد نظر ارسال خواهد شد.
سمت کلاینت:
»درخواست مورد نظر به سرور ارسال میشود؛
»کلاینت منتظر پاسخ از سمت سرور است(البته توسط یک Thread دیگر)؛
»اگر در حین انتظار برای پاسخ از سمت سرور، با یک TimeoutException روبرو شدیم به جای توقف برنامه و نمایش پیغام خطای Server is not available، باید عملیات به صورت خودکار restart شود.
»در نهایت پاسخ مورد نظر دریافت خواهد شد.
پیاده سازی این سناریو در WCF کار پیچیده ای نیست. بدین منظور میتوانید از کلاس زیر استفاده کنید( لینک دانلود ). سورس آن به صورت زیر است:
public abstract class LongPollingAsyncResult<TResult> : IAsyncResult where TResult : class { #region - Fields - private AsyncCallback _callback; private TimeSpan _timoutSpan; private TimeSpan _intervalWaitSpan; #endregion #region - Properties - public Exception Exception { get; private set; } public TResult Result { get; private set; } public object SyncRoot { get; private set; } #endregion #region - Ctor - public LongPollingAsyncResult(AsyncCallback callback, object asyncState, int timeoutSeconds = 300, int intervalWaitMilliseconds = 500) { SyncRoot = new object(); _callback = callback; AsyncState = asyncState; AsyncWaitHandle = new ManualResetEvent(IsCompleted); _timoutSpan = TimeSpan.FromSeconds(timeoutSeconds); _intervalWaitSpan = TimeSpan.FromMilliseconds(intervalWaitMilliseconds); ThreadPool.QueueUserWorkItem(new WaitCallback(LoopWithIntervalAndTimeout)); } #endregion #region - Private Helper Methods - private void LoopWithIntervalAndTimeout(object input) { try { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (!IsCompleted) { if (stopwatch.Elapsed > _timoutSpan) throw new TimeoutException(); DoWork(); if (!IsCompleted) Thread.Sleep(_intervalWaitSpan); } } catch (Exception e) { Complete(null, e); } } #endregion #region - Protected/Abstract Methods - protected void Complete(TResult result, Exception e = null, bool completedSynchronously = false) { lock (SyncRoot) { CompletedSynchronously = completedSynchronously; Result = result; Exception = e; IsCompleted = true; if (_callback != null) _callback(this); } } protected abstract void DoWork(); #endregion #region - Public Methods - public TResult WaitForResult() { if (!IsCompleted) AsyncWaitHandle.WaitOne(); if (Exception != null) { if (Exception is TimeoutException && WebOperationContext.Current != null) WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.RequestTimeout; throw Exception; } return Result; } #endregion #region - IAsyncResult Implementation - public object AsyncState { get; private set; } public WaitHandle AsyncWaitHandle { get; private set; } public bool CompletedSynchronously { get; private set; } public bool IsCompleted { get; private set; } #endregion }
برای استفاده از کلاس تهیه شده ابتدا باید عملیات خود را به صورت Async پیاده سازی نمایید که در این مقاله به صورت کامل شرح داده شده است.
یک مثال
قصد داریم Operation زیر را به صورت Long Polling پیاده سازی نماییم:
[OperationContract] public string GetNotification();
[OperationContract(AsyncPattern = true)] public IAsyncResult BeginWaitNotification(AsyncCallback callback, object state); public string EndWaitNotification(IAsyncResult result);
public class MyNotificationResult : LongPollingAsyncResult<string> { protected override DoWork() { کدهای مورد نظر را اینجا قرار دهید base.Complete(...) } }
public IAsyncResult BeginWaitNotification(AsyncCallback callback, object state) { return new MyNotificationResult(callback, state); } public string EndWaitNotification(IAsyncResult result) { MyNotificationResult myResult = result as MyNotificationResult; if(myResult == null) throw new ArgumentException("result was of the wrong type!"); myResult.WaitForResult(); return myResult.Result; }
سری آموزشی Blazor C# Tutorials
Blazor C# Tutorials
30 videos
In this playlist, I am going through all the fundamentals and sharing my journey to be a full stack Blazor developer. This is the future of web development in ASP.NET world. If you want to learn Blazor this is the best place to start.
1. Build Your First App - EP01
2. Getting Started - EP02
3. #Routing - EP03
4. Dependency #Injection - EP04
5. Forms & #Validations - EP05
6. JavaScript #Interop - EP06
7. #Razor #Components | Re-usability - EP07
8. Razor Components | #Lifecycle Methods - EP08
9. Razor Component #Libraries - EP09
10. Call #REST #API - #CRUD Methods - EP10
11. #Authentication | Out of the box- EP11
12. Custom AuthenticationStateProvider - EP12
13. Layouts | Login Pages - EP13
14. HttpClient | Login User
15. IHttpClientFactory | Login User
16. Sending JWT token & Request Middleware
17. Handling Exceptions