نحوه صحیح تولید Url در ASP.NET MVC
از آن میتوان جهت ثبت استثناهای مدیریت نشده در انواع و اقسام برنامههای وب دات نتی استفاده کرد. از برنامههای ASP.NET Webforms ، ASP.NET MVC تا سرویسهای WCF ، WCF RIA و غیره. سپس این Http module ویژه امکان مرور خطاهای ثبت شده را از راه دور میسر میسازد، به همراه فید RSS از خطاها، امکان مشاهدهی مقادیر فیلدها در لحظهی بروز خطا، ارسال ایمیل خطاها و امکان ذخیره سازی آنها در فایلهای XML تا انواع بانکهای اطلاعاتی.
برای نمونه، سایت Stackoverflow از یک نمونهی سفارشی شدهی آن استفاده میکند (+).
همانطور که در این معرفی آمده است : ثبت استثناهای "مدیریت نشده". اما آیا امکان ثبت استثناهای مدیریت شده هم توسط آن وجود دارد؟
پاسخ: بله. به صورت زیر:
... } catch (Exception ex) { Elmah.ErrorSignal.FromCurrentContext().Raise(ex); ...
در یک سری از متدهای فایل global.asax عموما Context ای وجود ندارد (دقیقا مانند برنامههای دسکتاپ). در این حالت از روش زیر
Elmah.ErrorLog.GetDefault(null).Log(new Error(ex))
using System; using System.Text; using Elmah; namespace Common.WebToolkit { public static class ElmahLogEx { public static void LogException(this string ex) { if (string.IsNullOrWhiteSpace(ex)) return; LogException(new Exception(ex)); } public static void LogException(this Exception ex) { if (ex == null) return; try { ErrorSignal.FromCurrentContext().Raise(ex); } catch { ErrorLog.GetDefault(null).Log(new Error(ex)); } } } }
یک روش «نمایش و یا مخفی کردن قسمتهای مختلف صفحه بر اساس نقشهای کاربر وارد شدهی به سیستم» را در مطلب جاری مطالعه کردید. روش دیگر اینکار، تهیهی یک دایرکتیو و سپس اعمال آن به المانهای مختلف صفحه است. به علاوه با توجه به اینکه Auth Service ما رخداد خروج کاربر را نیز گزارش میکند، روش ارائه شدهی در اینجا نیاز به اندکی بهبود هم دارد:
ngOnInit() { this.isAdmin = this.authService.isAuthUserInRole("Admin"); this.isUser = this.authService.isAuthUserInRole("User"); }
برای پیاده سازی آن و همچنین کپسوله سازی این عملیات تکراری، دایرکتیو جدیدی را در مسیر src\app\shared\directives\is-visible-for-auth-user.directive.ts ایجاد میکنیم:
import { Directive, ElementRef, Input, OnDestroy, OnInit } from "@angular/core"; import { Subscription } from "rxjs/Subscription"; import { AuthService } from "../../core/services/auth.service"; @Directive({ selector: "[isVisibleForAuthUser]" }) export class IsVisibleForAuthUserDirective implements OnInit, OnDestroy { private subscription: Subscription; @Input() isVisibleForRoles: string[]; constructor(private elem: ElementRef, private authService: AuthService) { } ngOnDestroy(): void { this.subscription.unsubscribe(); } ngOnInit(): void { this.subscription = this.authService.authStatus$.subscribe(status => this.changeVisibility(status)); this.changeVisibility(this.authService.isAuthUserLoggedIn()); } private changeVisibility(status: boolean) { const isInRoles = !this.isVisibleForRoles ? true : this.authService.isAuthUserInRoles(this.isVisibleForRoles); this.elem.nativeElement.style.display = isInRoles && status ? "" : "none"; } }
سپس تعریف آنرا به قسمتهای declarations و exports مربوط به SharedModule اضافه میکنیم:
import { IsVisibleForAuthUserDirective } from "./directives/is-visible-for-auth-user.directive"; @NgModule({ declarations: [ IsVisibleForAuthUserDirective ], exports: [ IsVisibleForAuthUserDirective ] }) export class SharedModule {}
اکنون ماژول Dashboard برای استفادهی از این امکانات تنها کافی است SharedModule را دریافت کند (یا هر ماژول دیگری نیز به همین ترتیب است):
import { SharedModule } from "../shared/shared.module"; @NgModule({ imports: [ SharedModule ] }) export class DashboardModule { }
پس از آن برای مخفی سازی یک المان از دید کاربران وارد نشدهی به سیستم، فقط کافی است دایرکتیو isVisibleForAuthUser را به المان اعمال کنیم:
<div class="alert alert-info" isVisibleForAuthUser> Is-Visible-For-AuthUser </div>
<div class="alert alert-success" isVisibleForAuthUser [isVisibleForRoles]="['Admin','User']"> Is-Visible-For-Roles = ['Admin','User'] </div>
خلاصهی این تغییرات به کدهای نهایی این سری اعمال شدهاند.
namespace ProductService.Models { public class ProductRating { public int ID { get; set; } public int Rating { get; set; } public int ProductID { get; set; } public virtual Product Product { get; set; } } }
ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Product>("Products"); builder.Namespace = "ProductService"; builder.EntityType<Product>() .Action("Rate") .Parameter<int>("Rating");
[HttpPost] public async Task<IHttpActionResult> Rate([FromODataUri] int key, ODataActionParameters parameters) { if (!ModelState.IsValid) { return BadRequest(); } int rating = (int)parameters["Rating"]; db.Ratings.Add(new ProductRating { ProductID = key, Rating = rating }); await db.SaveChangesAsync(); return StatusCode(HttpStatusCode.NoContent); }
POST http://localhost/Products(1)/ProductService.Rate HTTP/1.1 Content-Type: application/json Content-Length: 12 {"Rating":5}
HttpClient client = new HttpClient(); var response = client.PostAsync(postUrl, new StringContent(JsonConvert.SerializeObject(new { Rating = 5 }), Encoding.UTF8, "application/json")).Result;
اضافه کردن متد Function
ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Product>("Products"); builder.EntitySet<Supplier>("Suppliers"); builder.Namespace = "ProductService"; builder.EntityType<Product>().Collection .Function("MostExpensive") .Returns<double>();
نکته:
ReturnsFromEntitySet<Product>("Products")
ReturnsCollectionFromEntitySet<Product>("Products");
ReturnsCollection<string>();
برای فراخوانی این متد میتوان از آدرس زیر استفاده نمود:
GET http://localhost/Products/ProductService.MostExpensive
حال فقط کافیست که متد آن را در کنترلر مربوطه پیاده سازی نماییم:
public class ProductsController : ODataController { [HttpGet] public IHttpActionResult MostExpensive() { var product = db.Products.Max(x => x.Price); return Ok(product); } // Other controller methods not shown. }
HTTP/1.1 200 OK Content-Type: application/json; odata.metadata=minimal; odata.streaming=true OData-Version: 4.0 Date: Sat, 28 Jun 2016 00:44:07 GMT Content-Length: 85 { "@odata.context":"http://localhost:38479/$metadata#Edm.Decimal","value":50.00 }
اضافه کردن Unbound Function
ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Product>("Products"); builder.Function("GetSalesTaxRate") .Returns<double>() .Parameter<int>("PostalCode");
[HttpGet] [ODataRoute("GetSalesTaxRate(PostalCode={postalCode})")] public IHttpActionResult GetSalesTaxRate([FromODataUri] int postalCode) { double rate = 5.6; // Use a fake number for the sample. return Ok(rate); }
برای فراخوانی آن نیز از درخواست زیر استفاده مینماییم:
GET http://localhost/GetSalesTaxRate(PostalCode=10) HTTP/1.1
HTTP/1.1 200 OK Content-Type: application/json; odata.metadata=minimal; odata.streaming=true OData-Version: 4.0 Date: Sat, 28 Jun 2016 01:05:32 GMT Content-Length: 82 { "@odata.context":"http://localhost:38479/$metadata#Edm.Double","value":5.6 }
شاید سؤالی برایتان پیش بیاید که آیا برای تعریف هر متد، این همه مراحل کانفیگ لازم است؟! در واقع باید عرض کنم، این نوع استفاده از OData، ابتداییترین نوع طراحی آن میباشد و قطعا در یک برنامهی واقعی این همه کد نویسی برای نوشتن فقط یک متد، شاید منطقی به نظر نمیرسد. از آنجاییکه این مقاله فقط جنبهی آموزشی خیلی ساده از این پروتکل را دارد، فعلا به همین اندازه بسنده میکنیم. اما در مقالههای بعدی راهحلهایی برای بینهایت ساده کردن کانفیگ OData را شرح خواهیم داد.
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
اعتبارسنجی سفارشی سمت کاربر و سمت سرور در jqGrid
پیشتر با نحوهی فعال سازی صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid آشنا شدیم. اما ... شاید علاقمند نباشید که اصلا از این صفحات استفاده کنید. شاید به نظر شما با کلیک بر روی دکمهی + افزودن یک رکورد جدید، بهتر باشد داخل خود گرید، یک سطر خالی جدید باز شده تا بتوان آنرا پر کرد. شاید این نحو کار کردن با گرید، از دید عدهای طبیعیتر باشد نسبت به حالت نمایش صفحات popup افزودن و یا ویرایش رکوردها. در ادامه این مورد را بررسی خواهیم کرد.
فعال سازی افزودن، ویرایش و حذف Inline
فعال سازی ویرایش و حذف Inline را پیشتر نیز بررسی کرده بودیم. تنها کافی است یک ستون جدید را با 'formatter: 'actions تعریف کنیم. به صورت خودکار، دکمهی ویرایش، حذف، ذخیره سازی و لغو Inline ظاهر میشوند و همچنین بدون نیاز به کدنویسی بیشتری کار میکنند.
اما در کدهای ذیل اندکی این ستون را سفارشی سازی کردهایم. در قسمت formatter آن، دکمههای edit و delete یک سطر جدید توکار اضافه شده را حذف کردهایم. زیرا در این حالت خاص، وجود این دکمهها ضروری نیستند. بهتر است در این حالت دکمههای save و cancel ظاهر شوند:
$('#list').jqGrid({ caption: "آزمایش نهم", // .... colModel: [ { name: 'myac', width: 80, fixed: true, sortable: false, resize: false, //formatter: 'actions', formatter: function (cellvalue, options, rowObject) { if (cellvalue === undefined && options.rowId === "_empty") { // در حالت نمایش ردیف توکار جدید دکمههای ویرایش و حذف معنی ندارند options.colModel.formatoptions.editbutton = false; options.colModel.formatoptions.delbutton = false; } return $.fn.fmatter.actions(cellvalue, options, rowObject); }, formatoptions: { keys: true, afterSave: function (rowid, response) { }, delbutton: true, delOptions: { url: "@Url.Action("DeleteUser", "Home")" } } } ], //... }).navGrid( '#pager', //... ) .jqGrid('gridResize', { minWidth: 400, minHeight: 150 }) .jqGrid('inlineNav', '#pager', { edit: true, add: true, save: true, cancel: true, edittext: "ویرایش", addtext: "جدید", savetext: "ذخیره", canceltext: "لغو", addParams: { // اگر میخواهید ردیفهای جدید در ابتدا ظاهر شوند، این سطر را حذف کنید position: "last", //ردیفهای جدید در آخر ظاهر میشوند rowID: '_empty', useDefValues: true, addRowParams: getInlineNavParams(true) }, editParams: getInlineNavParams(false) });
در اینجا 4 دکمهی ویرایش، جدید، ذخیره و لغو، در نوار pager پایین گرید ظاهر خواهند شد (سمت چپ؛ سمت راست همان دکمههای نمایش فرمهای پویا هستند).
سپس باید دو قسمت مهم addParams و editParams آنرا مقدار دهی کرد.
در قسمت addParams، مشخص میکنیم که ID ردیف اضافه شده، مساوی کلمهی _empty باشد. اگر به کدهای formatter ستون action دقت کنید، از این ID برای تشخیص افزوده شدن یک ردیف جدید استفاده شدهاست.
position در اینجا به معنای محل افزوده شدن یک ردیف خالی است. مقدار پیش فرض آن first است؛ یعنی همیشه در اولین ردیف گرید، این ردیف جدید اضافه میشود. در اینجا به last تنظیم شدهاست تا در پایین گرید و پس از رکوردهای موجود، نمایش داده شود.
useDefValues سبب استفاده از مقادیر پیش فرض تعریف شده در ستونهای گرید در حین افزوده شدن یک ردیف جدید میگردد.
addRowParams و editParams هر دو ساختار تقریبا یکسانی دارند که به نحو ذیل تعریف میشوند:
function getInlineNavParams(isAdd) { return { // استفاده از آدرسهای مختلف برای حالات ویرایش و ثبت اطلاعات جدید url: isAdd ? '@Url.Action("AddUser", "Home")' : '@Url.Action("EditUser","Home")', key: true, restoreAfterError: false, // این مورد سبب میشود تا اعتبارسنجی سمت سرور قابل اعمال شود oneditfunc: function (rowId) { // نمایش دکمههای ذخیره و لغو داخل همان سطر $("#jSaveButton_" + rowId).show(); $("#jCancelButton_" + rowId).show(); }, successfunc: function () { var $self = $(this); setTimeout(function () { $self.trigger("reloadGrid"); // دریافت کلید اصلی ردیف از سرور }, 50); }, errorfunc: function (rowid, response, stat) { if (stat != 'error') // this.Response.StatusCode == 200 return; var result = $.parseJSON(response.responseText); if (result.success === false) { //نمایش خطای اعتبار سنجی سمت سرور پس از ویرایش یا افزودن $.jgrid.info_dialog($.jgrid.errors.errcap, '<div class="ui-state-error">' + result.message + '</div>', $.jgrid.edit.bClose, { buttonalign: 'center' }); } } }; }
تنظیم restoreAfterError به false بسیار مهم است. اگر در سمت سرور خطای اعتبارسنجی گزارش شود و restoreAfterError مساوی true باشد (مقدار پیش فرض)، کاربر مجبور خواهد شد اطلاعات را دوباره وارد کند.
در روال رویدادگران oneditfunc دکمهی save و cancel ردیف را که مخفی هستند، ظاهر میکنیم (مکمل formatter ستون action است).
در قسمت successfunc، پس از پایان موفقیت آمیز کار، متد reloadGrid را فراخوانی میکنیم. اینکار سبب میشود تا Id واقعی رکورد، از سمت سرور دریافت شود. از این Id برای ویرایش و همچنین حذف، استفاده خواهد شد. علت استفاده از setTimeout در اینجا این است که اندکی به DOM فرصت داده شود تا کارش به پایان برسد.
در قسمت errorfunc خطاهای اعتبارسنجی سفارشی سمت سرور را میتوان دریافت و سپس توسط متد توکار info_dialog به کاربر نمایش داد.
یک نکتهی مهم در مورد ارسال خطاهای اعتبارسنجی از سمت سرور در حالت Inline Add
if (_usersInMemoryDataSource.Any( user => user.Name.Equals(postData.Name, StringComparison.InvariantCultureIgnoreCase))) { this.Response.StatusCode = 500; //این مورد برای افزودن داخل ردیفهای گرید لازم است return Json(new { success = false, message = "نام کاربر تکراری است" }, JsonRequestBehavior.AllowGet); }
مدیریت StatusCodeهای غیر از 200 در حالت کار با فرمهای jqGrid
اگر هر دو حالت Inline Add و فرمهای پویا را فعال کردهاید، بازگشت StatusCode = 500 سبب میشود تا دیگر نتوان خطاهای سفارشی سمت سرور را در بالای فرمها به کاربر نمایش داد و در این حالت تنها یک internal server error را مشاهده خواهند کرد. برای رفع این مشکل فقط کافی است روال رویدادگران errorTextFormat را مدیریت کرد:
$('#list').jqGrid({ caption: "آزمایش نهم", //......... }).navGrid( '#pager', //enabling buttons { add: true, del: true, edit: true, search: false }, //edit option { //......... errorTextFormat: serverErrorTextFormat }, //add options { //......... errorTextFormat: serverErrorTextFormat }, //delete options { //......... }) .jqGrid('gridResize', { minWidth: 400, minHeight: 150 }) .jqGrid('inlineNav', '#pager', { //......... }); function serverErrorTextFormat (response) { // در حالتیکه وضعیت خروجی از سرور 200 نیست فراخوانی میشود var result = $.parseJSON(response.responseText); if (result.success === false) { return result.message; } return "لطفا ورودیهای وارد شده را بررسی کنید"; }
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
jqGrid09.zip
استفاده از pjax بجای ajax در ASP.NET MVC
از کمک شما ممنون
بالاخره خطا رو پیدا کردم
The following sections have been defined but have not been rendered for the layout page "~/Views/Shared/_PjaxLayout.cshtml": "Scripts".
ولی دلیلش چی میتونه باشه مگه فقط نمیاد قسمت مثلا main در کد زیر را جایگذاری کنه؟
<div id="main"> @RenderBody() </div>
//********** @Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("Scripts", required: false)
<script type="text/javascript"> $(function () { $(document).pjax('a[withpjax]', '#main', { timeout: 5000 });
و لینک هم به اینصورت:
@Html.ActionLink("ارتباط با ما","Contact", "Home" , null,new { withpjax="with-pjax" })
الف) تصاویر رو نمیتونه پیدا کنه، یا صفحه کش شده بیش از حد. قسمت «اجرای کدهای jQuery Ajax فوق، چه تغییری را در صفحه سبب میشوند؟» را بررسی کنید که چه آدرسی توسط کدهای جیکوئری در حال پردازش است.
همچنین کش شدن نتایج قبلی رو هم میشود غیرفعال کرد:
$.ajax({ cache: false /* گاهی از اوقات خصوصا برای آی ایی نیاز است */ });
<script type="text/javascript"> $(function () { // کدهای جیکوئری در اینجا }); </script>
متدی تحت عنوان ValidateEmail را تصور کنید. این متد از حیث بازگشت نتیجه به عنوان خروجی میتواند به اشکال مختلفی پیاده سازی شود که در ادامه مشاهده میکنیم:
متد ValidateEmail با خروجی Boolean
public bool ValidateEmail(string email) { var valid = true; if (string.IsNullOrWhiteSpace(email)) { valid = false; } var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; } var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; } return valid; }
همانطور که در تکه کد زیر مشخص میباشد، استفاده کننده از متد بالا، امکان بررسی خروجی آن را در قالب یک شرط خواهد داشت و علاوه بر اینکه پیاده سازی آن ساده میباشد، خوانایی کد را نیز بالا میبرد؛ ولی با این حال نمیتوان متوجه شد مشکل اصلی آدرس ایمیل ارسالی به عنوان آرگومان، دقیقا چیست.
var email = "email@example.com"; var isValid = ValidateEmail(email); if(isValid) { //do something }
متد ValidateEmail با صدور استثناء
public void ValidateEmail(string email) { if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email)); var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) throw new ArgumentException("email is not in a correct format"); var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) throw new ArgumentException("email does not include a valid domain.") }
روش بالا هم جواب میدهد ولی بهتر است کلاس Exception سفارشی به عنوان مثال ValidationException برای این قضیه در نظر گرفته شود تا بتوان وهلههای صادر شده از این نوع را در لایههای بالاتر مدیریت کرد.
متد ValidateEmail با چندین خروجی
برای این منظور چندین راه حل پیش رو داریم.
با استفاده از پارامتر out:
public bool ValidateEmail(string email, out string message) { var valid = true; message = string.Empty; if (string.IsNullOrWhiteSpace(email)) { valid = false; message = "email is null."; } if (valid) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { valid = false; message = "email is not in a correct format"; } } if (valid) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { valid = false; message = "email does not include a valid domain."; } } return valid; }
var email = "email@example.com"; var isValid = ValidateEmail(email, out string message); if (isValid) { //do something }
Tuple<bool, List<string>> result = Tuple.Create<bool, List<string>>(true, new List<string>());
public class OperationResult { public bool Success { get; set; } public IList<string> Messages { get; } = new List<string>(); public void AddMessage(string message) { Messages.Add(message); } }
public OperationResult ValidateEmail(string email) { var result = new OperationResult(); if (string.IsNullOrWhiteSpace(email)) { result.Success = false; result.AddMessage("email is null."); } if (result.Success) { var isValidFormat = true;//todo: using RegularExpression if (!isValidFormat) { result.Success = false; result.AddMessage("email is not in a correct format"); } } if (result.Success) { var isRealDoamin = true;//todo: Code here that confirms whether domain exists. if (!isRealDoamin) { result.Success = false; result.AddMessage("email does not include a valid domain."); } } return result; }
این بار خروجی متد مذکور از نوع OperationResult ای میباشد که هم موفقیت آمیز بودن یا عدم آن را مشخص میکند و همچنین امکان دسترسی به لیست پیغامهای مرتبط با اعتبارسنجیهای انجام شده، وجود دارد.
استفاده از Exception برای نمایش پیغام برای کاربر نهایی
با صدور یک استثناء و مدیریت سراسری آن در بالاترین (خارجی ترین) لایه و نمایش پیغام مرتبط با آن به کاربر نهایی، میتوان از آن به عنوان ابزاری برای ارسال هر نوع پیغامی به کاربر نهایی استفاده کرد. اگر قوانین تجاری با موفقیت برآورده نشدهاند یا لازم است به هر دلیلی یک پیغام مرتبط با یک اعتبارسنجی تجاری را برای کاربر نمایش دهید، این روش بسیار کارساز میباشد و با یکبار وقت گذاشتن برای توسعه زیرساخت برای این موضوع به عنوان یک Cross Cutting Concern تحت عنوان Exception Management آزادی عمل زیادی در ادامه توسعه سیستم خود خواهید داشت.
به عنوان مثال داشتن یک کلاس Exception سفارشی تحت عنوان UserFriendlyException در این راستا یک الزام میباشد.
[Serializable] public class UserFriendlyException : Exception { public string Details { get; private set; } public int Code { get; set; } public UserFriendlyException() { } public UserFriendlyException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public UserFriendlyException(string message) : base(message) { } public UserFriendlyException(int code, string message) : this(message) { Code = code; } public UserFriendlyException(string message, string details) : this(message) { Details = details; } public UserFriendlyException(int code, string message, string details) : this(message, details) { Code = code; } public UserFriendlyException(string message, Exception innerException) : base(message, innerException) { } public UserFriendlyException(string message, string details, Exception innerException) : this(message, innerException) { Details = details; } }
و همچنین لازم است در بالاترین لایه سیستم خود به عنوان مثال برای یک پروژه ASP.NET MVC یا ASP.NET Core MVC میتوان یک ExceptionFilter سفارشی نیز تهیه کرد که هم به صورت سراسری استثناءهای سفارشی شما را مدیریت کند و همچنین خروجی مناسب Json برای استفاده در سمت کلاینت را نیز مهیا کند. به عنوان مثال برای درخواستهای Ajax ای لازم است در سمت کلاینت نیز پاسخهای رسیده از سمت سرور به صورت سراسری مدیریت شوند و برای سایر درخواستها همان نمایش صفحات خطای پیغام مرتبط با استثناء رخ داده شده کفایت میکند.
یک مدل پیشنهادی برای تهیه خروجی مناسب برای ارسال جزئیات استثنا رخ داده در درخواستهای Ajax ای
[Serializable] public class MvcAjaxResponse : MvcAjaxResponse<object> { public MvcAjaxResponse() { } public MvcAjaxResponse(bool success) : base(success) { } public MvcAjaxResponse(object result) : base(result) { } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) : base(error, unAuthorizedRequest) { } } [Serializable] public class MvcAjaxResponse<TResult> : MvcAjaxResponseBase { public MvcAjaxResponse(TResult result) { Result = result; Success = true; } public MvcAjaxResponse() { Success = true; } public MvcAjaxResponse(bool success) { Success = success; } public MvcAjaxResponse(ErrorInfo error, bool unAuthorizedRequest = false) { Error = error; UnAuthorizedRequest = unAuthorizedRequest; Success = false; } /// <summary> /// The actual result object of AJAX request. /// It is set if <see cref="MvcAjaxResponseBase.Success" /> is true. /// </summary> public TResult Result { get; set; } } public class MvcAjaxResponseBase { public string TargetUrl { get; set; } public bool Success { get; set; } public ErrorInfo Error { get; set; } public bool UnAuthorizedRequest { get; set; } public bool __mvc { get; } = true; }
[Serializable] public class ErrorInfo { public int Code { get; set; } public string Message { get; set; } public string Detail { get; set; } public Dictionary<string, string> ValidationErrors { get; set; } public ErrorInfo() { } public ErrorInfo(string message) { Message = message; } public ErrorInfo(int code) { Code = code; } public ErrorInfo(int code, string message) : this(message) { Code = code; } public ErrorInfo(string message, string details) : this(message) { Detail = details; } public ErrorInfo(int code, string message, string details) : this(message, details) { Code = code; } }
public async Task CheckIsDeactiveAsync(long id) { if (await _organizationalUnits.AnyAsync(a => a.Id == id && !a.IsActive).ConfigureAwait(false)) throw new UserFriendlyException("واحد سازمانی جاری غیرفعال میباشد."); }
روش نام گذاری متدهایی که امکان بازگشت خروجی Null را دارند
public User GetById(long id);
[Serializable] public class EntityNotFoundException : Exception { public Type EntityType { get; set; } public object Id { get; set; } public EntityNotFoundException() { } public EntityNotFoundException(string message) : base(message) { } public EntityNotFoundException(string message, Exception innerException) : base(message, innerException) { } public EntityNotFoundException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context) { } public EntityNotFoundException(Type entityType, object id) : this(entityType, id, null) { } public EntityNotFoundException(Type entityType, object id, Exception innerException) : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException) { EntityType = entityType; Id = id; } }
یک مثال واقعی
public async Task<UserOrganizationalUnitInfo> GetCurrentOrganizationalUnitInfoOrNullAsync(long userId) { return (await _setting.GetSettingValueForUserAsync( UserSettingNames.CurrentOrganizationalUnitInfo, userId).ConfigureAwait(false)) .FromJsonString<UserOrganizationalUnitInfo>(); }
فرض کنید یک برنامه ASP.Net نوشتهاید که کار آن نمایش یک سری فید از سایتهای مختلف است یا دریافت وضعیت آب و هوا از یک وب سرویس و نمایش آن در سایت میباشد و امثال آن (استفاده از WebRequest ، WebClient ، XmlDom/Reader و ...).
اگر همین عملیات را یکبار با ASP.Net 1.1 و بار دیگر با ASP.Net 2.0 به بالا انجام دهید، متوجه تفاوت سرعت دریافت قابل تاملی خواهید شد (ASP.Net 2.0 به بعد حدودا تا 10 ثانیه کندتر عمل میکند). علت چیست؟
در دات نت 1.1 ، تنظیمات پروکسی پیش فرض در کتابخانههای مربوطه وجود نداشت و به نال تنظیم شده بود. در دات نت 2 به بعد این مورد به پروکسی پیش فرض سیستم، تنظیم شده است. پروکسی پیش فرض سیستم همان تنظیماتی است که در internet explorer صورت میگیرد.
کاربر پیش فرض ASP.Net (مثلا NETWORK SERVICE) دسترسی خواندن این اطلاعات را از رجیستری ویندوز ندارد. علت این وقفه هم همین مورد است! (حتی اگر برنامه ویندوزی شما هم دسترسی خواندن اطلاعات کلیدهای HKLM رجیستری ویندوز را نداشته باشد، باز هم این مساله رخ خواهد داد)
دات نت فریم ورک سعی میکند تا این تنظیمات را از رجیستری یا مکانهای میسر دیگر بخواند و در آخر پس از شکست کلیه حالات مختلف، کلاینت را به صورت مستقیم متصل خواهد کرد.
خوشبختانه این عملکرد پیش فرض قابل تغییر است. تنها کافی است چند سطر زیر را به فایل config برنامه خود اضافه کنید:
<system.net>
<defaultProxy>
<proxy bypassonlocal="true" usesystemdefault="false" />
</defaultProxy>
</system.net>
راه دیگر انجام آن، نسبت دادن نال به خاصیت Proxy شیء HttpWebRequest است که همین اثر را خواهد داشت.