مقایسه عملکرد HTTP/3 با HTTP/2
- سماموس - مدلسازی | somamos.blogfa.com
- Figaro Embedded Native XML Database for .NET | bdbxml.net
- Mono for Android 4.0 is Here! | blog.xamarin.com
- WCF Community Site - Download: WCF Web API Preview 6 | wcf.codeplex.com
- What is Functional Programming? - Christopher Bennage | dev.bennage.com
- Microsoft Exchange Server 2010 Service Pack 2 (SP2) | microsoft.com
این پروژه در 12 بخش گوناگون تقسیم بندی شدهاست که هر کدام در قالب یک فایل HTML میباشد و تمامی اسکریپتهای مورد نیاز به آن افزوده شدهاست. هر بخش به صورت مجزا به شرح یک ویژگی کاربردی در angular-translate میپردازد.
ex1_basic_usage
<script src="Scripts/angular.js"></script> <script src="Scripts/angular-translate.js"></script>
angular.module('app', ['pascalprecht.translate']) .config([ '$translateProvider', function ($translateProvider) { // Adding a translation table for the English language $translateProvider.translations('en_US', { "TITLE": "How to use", "HEADER": "You can translate texts by using a filter.", "SUBHEADER": "And if you don't like filters, you can use a directive.", "HTML_KEYS": "If you don't like an empty elements, you can write a key for the translation as an inner HTML of the directive.", "DATA_TO_FILTER": "Your translations might also contain any static ({{staticValue}}) or random ({{randomValue}}) values, which are taken directly from the model.", "DATA_TO_DIRECTIVE": "And it's no matter if you use filter or directive: static is still {{staticValue}} and random is still {{randomValue}}.", "RAW_TO_FILTER": "In case you want to pass a {{type}} data to the filter, you have only to pass it as a filter parameter.", "RAW_TO_DIRECTIVE": "This trick also works for {{type}} with a small mods.", "SERVICE": "Of course, you can translate your strings directly in the js code by using a $translate service.", "SERVICE_PARAMS": "And you are still able to pass params to the texts. Static = {{staticValue}}, random = {{randomValue}}." }); // Adding a translation table for the Russian language $translateProvider.translations('ru_RU', { "TITLE": "Как пользоваться", "HEADER": "Вы можете переводить тексты при помощи фильтра.", "SUBHEADER": "А если Вам не нравятся фильтры, Вы можете воспользоваться директивой.", "HTML_KEYS": "Если вам не нравятся пустые элементы, Вы можете записать ключ для перевода в как внутренний HTML директивы.", "DATA_TO_FILTER": "Ваши переводы также могут содержать любые статичные ({{staticValue}}) или случайные ({{randomValue}}) значения, которые берутся прямо из модели.", "DATA_TO_DIRECTIVE": "И совершенно не важно используете ли Вы фильтр или директиву: статическое значение по прежнему {{staticValue}} и случайное - {{randomValue}}.", "RAW_TO_FILTER": "Если вы хотите передать \"сырые\" ({{type}}) данные фильтру, Вам всего лишь нужно передать их фильтру в качестве параметров.", "RAW_TO_DIRECTIVE": "Это также работает и для директив ({{type}}) с небольшими модификациями.", "SERVICE": "Конечно, Вы можете переводить ваши строки прямо в js коде при помощи сервиса $translate.", "SERVICE_PARAMS": "И вы все еще можете передавать параметры в тексты. Статическое значение = {{staticValue}}, случайное = {{randomValue}}." }); // Tell the module what language to use by default $translateProvider.preferredLanguage('en_US'); }])
.controller('ctrl', ['$scope', '$translate', function ($scope, $translate) { $scope.tlData = { staticValue: 42, randomValue: Math.floor(Math.random() * 1000) }; $scope.jsTrSimple = $translate.instant('SERVICE'); $scope.jsTrParams = $translate.instant('SERVICE_PARAMS', $scope.tlData); $scope.setLang = function (langKey) { // You can change the language during runtime $translate.use(langKey); // A data generated by the script have to be regenerated $scope.jsTrSimple = $translate.instant('SERVICE'); $scope.jsTrParams = $translate.instant('SERVICE_PARAMS', $scope.tlData); }; }]);
<p> <a href="#" ng-click="setLang('en_US')">English</a> | <a href="#" ng-click="setLang('ru_RU')">Русский</a> </p> <!-- Translation by a filter --> <h1>{{'HEADER' | translate}}</h1> <!-- Translation by a directive --> <h2 translate="SUBHEADER">Subheader</h2> <!-- Using inner HTML as a key for translation --> <p translate>HTML_KEYS</p> <hr> <!-- Passing a data object to the translation by the filter --> <p>{{'DATA_TO_FILTER' | translate: tlData}}</p> <!-- Passing a data object to the translation by the directive --> <p translate="DATA_TO_DIRECTIVE" translate-values="{{tlData}}"></p> <hr> <!-- Passing a raw data to the filter --> <p>{{'RAW_TO_FILTER' | translate:'{ type: "raw" }' }}</p> <!-- Passing a raw data to the filter --> <p translate="RAW_TO_DIRECTIVE" translate-values="{ type: 'directives' }"></p> <hr> <!-- Using a $translate service --> <p>{{jsTrSimple}}</p> <!-- Passing a data to the $translate service --> <p>{{jsTrParams}}</p>
ex2_remember_language_cookies
<script src="Scripts/angular-cookies.js"></script> <script src="Scripts/angular-translate-storage-cookie.js"></script>
// Tell the module to store the language in the cookie $translateProvider.useCookieStorage();
ex3_remember_language_local_storage
این مثال همانند مثال قبل رفتار میکند، با این تفاوت که به جای اینکه کلید زبان کنونی را درون کوکی ذخیره کند، آن را درون Local Storage با نام NG_TRANSLATE_LANG_KEY قرار میدهد. برای اجرا کافیست اسکریپتها و تکه کد زیر را با موارد مثال قبل جایگزین کنید.
<script src="Scripts/angular-translate-storage-local.js"></script> // Tell the module to store the language in the local storage $translateProvider.useLocalStorage();
مثال های ex4_set_a_storage_key و ex5_set_a_storage_prefix نام کلیدی که برای ذخیره سازی زبان کنونی در کوکی یا Local Storage قرار میگیرد را تغییر میدهد که به دلیل سادگی از شرح آن میگذریم.
ex6_namespace_support
translate table در angular-translate قابلیت مفید namespacing را نیز داراست. این قابلیت به ما کمک میکند که جهت کپسوله کردن بخشهای مختلف، ترجمه آنها را با namespaceهای خاص خود نمایش دهیم. به مثال زیر توجه کنید:
$translateProvider.translations('en_US', { "TITLE": "How to use namespaces", "ns1": { "HEADER": "A translations table supports namespaces.", "SUBHEADER": "So you can to structurize your translation table well." }, "ns2": { "HEADER": "Do you want to have a structured translations table?", "SUBHEADER": "You can to use namespaces now." } });
همانطور که توجه میکنید بخش ns1 خود شامل زیر مجموعههایی است و ns2 نیز به همین صورت. هر کدام دارای کلید HEADER و SUBHEADER میباشند. فرض کنید هر کدام از این بخشها میخواهند اطلاعات درون یک section را نمایش دهند. حال به نحوهی فراخوانی این translate tableها دقت کنید:
<!-- section 1: Translate Table Called by ns1 namespace --> <h1 translate>ns1.HEADER</h1> <h2 translate>ns1.SUBHEADER</h2> <!-- section 2: Translate Table Called by ns2 namespace --> <h1 translate>ns2.HEADER</h1> <h2 translate>ns2.SUBHEADER</h2>
به همین سادگی میتوان تمامی بخشها را با namespaceهای مختلف در translate table قرار داد.
در بخش بعدی (پایانی) شش قابلیت دیگر angular translate که شامل فراخوانی translate table از یک فایل JSON، فراخوانی فایلهای translate table به صورت lazy load و تغییر زبان بخشی از صفحه به صورت پویا هستند، بررسی خواهند شد.
فایل پروژه: AngularJs-Translate-BestPractices.zip
ObservableCollection در Entity Framework
public abstract class BaseEntity { [ColumnInfo("کد",pWidth:70)] public int Id { get; set; } [ColumnInfo("",pIsVisible:false,pIsEditable:false)] [NotMapped] public bool IsDeleted { get; set; } }
در این قسمت مدلهای مربوط به بخش انجمن را تکمیل کرده و همچنین سیستم نظرسنجی را نیز بررسی خواهیم کرد.
همکاران این قسمت:
سلمان معروفی
سید مجبتی حسینی
مدل پستهای انجمن
/// <summary> /// Represents The Post of Forum /// </summary> public class ForumPost : AuditBaseEntity { #region Ctor /// <summary> /// create one instance of <see cref="ForumPost"/> /// </summary> public ForumPost() { CreatedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets body of this post /// </summary> public virtual string Body { get; set; } /// <summary> /// gets or sets Count of this post's reports /// </summary> public virtual int ReportsCount { get; set; } /// <summary> /// gets or sets information of User-Agent /// </summary> public virtual string Agent { get; set; } /// <summary> /// gets or sets rating values /// <remarks>is a complex type</remarks> /// </summary> public virtual Rating Rating { get; set; } /// <summary> /// gets or sets author's ip address /// </summary> public virtual string CreatorIp { get; set; } /// <summary> /// gets or sets status of this post /// </summary> public virtual ForumPostStatus Status { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets ParentPost of this post /// </summary> public virtual ForumPost Reply { get; set; } /// <summary> /// gets or sets ParentPost's Id of this post /// </summary> public virtual long? ReplyId { get; set; } /// <summary> /// gets or sets /// </summary> public virtual ICollection<ForumPost> Children { get; set; } /// <summary> /// gets or sets Topic That Associated with this Post /// </summary> public virtual ForumTopic Topic { get; set; } /// <summary> /// gets or sets Id of Topic That Associated with this Post /// </summary> public virtual long TopicId { get; set; } /// <summary> /// get or sets Histories of this Post's Updates /// </summary> public virtual ICollection<ForumPostHistory> Histories { get; set; } /// <summary> /// gets or sets Forum that this post created in it . used for retrive posts count /// </summary> public virtual Forum Forum { get; set; } /// <summary> /// gets or sets id of Forum that this post created in it . used for retrive posts count /// </summary> public virtual long ForumId { get; set; } #endregion } public enum ForumPostStatus { /* 0 - approved, 1 - pending, 2 - spam, -1 - trash */ [Display(Name = "تأیید شده")] Approved = 0, [Display(Name = "در انتظار بررسی")] Pending = 1, [Display(Name = "جفنگ")] Spam = 2, [Display(Name = "زباله دان")] Trash = -1 }
مدل بالا مشخص کنندهی پستهایی که در پاسخ به تاپیکها ارسال میشوند، میباشد. ساختار درختی آن به منظور امکان پاسخ به پستها در نظر گرفته شده است. در هر تاپیک چندین پست ارسال میشود که اولین پست ارسال شده، همان محتوای اصلی تاپیک میباشد. بدین منظور خصوصیت Topic را در مدل بالا تعریف کردهایم. برای این پستهای ارسالی امکان امتیاز دهی و اخطار دادن نیز خواهیم داشت که به ترتیب خصوصیات Rating و ReportsCount (بحث شده در مقالات قبل) را در مدل بالا تعریف کردهایم. خصوصیت Status به منظور اعمال مدیریتی در نظر گرفته شده است که از نوع ForumPostStatus میباشد و در بالا تعریف آن نیز آمده است.
نکته : خصوصیتی از نوع مدل Forum نیز در مدل بالا تعریف شده است. هدف از آن افزایش سرعت ویرایش خصوصیات ApprovedPostsCount و UnApprovedPostsCount موجود در مدل Forum میباشد. در واقع هنگام درج پست جدید یا حذف پستی و یا ... ، لازم است خصوصیات مذکور به روز شوند.
علاوه بر این موارد ، لازم است تاریخچهی تغییرات پستهای ارسالی را هم نگهداری کرد تا در صورت نیاز به آنها استناد کنیم. از طرفی پستهای ارسالی را میتوان چندین بار ویرایش کرد. به همین دلیل خصوصیت Histories را که لیستی از مدل ForumPostHistory میباشد، در مدل بالا تعریف کردهایم.
مدل تاریخچهی تغییرات پست
/// <summary> /// Represents History Of Post's Updates /// </summary> public class ForumPostHistory { #region Ctor /// <summary> /// create one instance of <see cref="ForumPostHistory"/> /// </summary> public ForumPostHistory() { Id = SequentialGuidGenerator.NewSequentialGuid(); CreatedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets Identifier of this history /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets Reason of update /// </summary> public virtual string Reason { get; set; } /// <summary> /// gets or sets DateTime that this record added /// </summary> public virtual DateTime CreatedOn { get; set; } /// <summary> /// gets or sets body of this post /// </summary> public virtual string Body { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets Post /// </summary> public virtual ForumPost Post { get; set; } /// <summary> /// gets or sets Id Of Post /// </summary> public virtual long PostId { get; set; } /// <summary> /// gets or sets User that modified this Record /// </summary> public virtual User Modifier { get; set; } /// <summary> /// gets or sets if of User that modified this Record /// </summary> public virtual long ModifierId { get; set; } #endregion }
اگر خصوصیت ModifyLocked مربوط به مدل ForumPost که آن را از کلاس پایه AuditBaseEntity به ارث برده است، دارای مقدار true باشد، این امکان وجود خواهد داشت تا بتوان پست مورد نظر را ویرایش کرده و اطلاعات قبلی، در قالب یک رکورد در جدول حاصل از مدل بالا ثبت شوند.
- Reason : دلیل این ویرایش به عمل آماده
- Body : محتوای پست یا تاپیک
- Modifier : کاربر انجام دهندهی این ویرایش
- CreatedOn : زمانی که این ویرایش انجام شده است
مدل ردیابی انجمن ها
public class ForumTracker { #region Ctor /// <summary> /// create one instance of <see cref="ForumTracker"/> /// </summary> public ForumTracker() { LastMarkedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets DateTime Of Las Visit by User /// </summary> public virtual DateTime LastMarkedOn { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets Forum that Tracked /// </summary> public virtual Forum Forum { get; set; } /// <summary> /// gets or sets Id of Forum tath Tracked /// </summary> public virtual long ForumId { get; set; } /// <summary> /// gets or sets User that tracked The forum /// </summary> public virtual User Tracker { get; set; } /// <summary> /// gets or sets Id Of User that Tracked the forum /// </summary> public virtual long TrackerId { get; set; } #endregion } public class ForumTopicTracker { #region Ctor /// <summary> /// create one instance of <see cref="ForumTopicTracker"/> /// </summary> public ForumTopicTracker() { LastVisitedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets DateTime Of Las Visit by User /// </summary> public virtual DateTime LastVisitedOn { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets topc that Tracked /// </summary> public virtual ForumTopic Topic { get; set; } /// <summary> /// gets or sets Id of topic that Tracked /// </summary> public virtual long TopicId { get; set; } /// <summary> /// gets or sets User that tracked The topic /// </summary> public virtual User Tracker { get; set; } /// <summary> /// gets or sets Id Of User that Tracked the topic /// </summary> public virtual long TrackerId { get; set; } /// <summary> /// gets or sets Forum /// </summary> public virtual Forum Forum { get; set; } /// <summary> /// gets or sets Identifier of Forum . used for delete /// </summary> public virtual long ForumId { get; set; } #endregion }
از مدل ForumTopicTracker هم برای مشخص کردن اینکه کاربر کدام تاپیک را و در چه تاریخی آخرین بار مشاهده کرده است، کمک میگیریم. برای این منظور از خصوصیت LastVisitedOn استفاده میشود.
البته نیاز است هنگام واکشی انجمنها و تاپیکها، یکسری بررسیهایی را بر اساس این جداول انجام داد که تشریح این بررسیها را قصد دارم هنگام پیاده سازی سیستم انجام دهم.
این قسمت از کار کمی پیچیده است و برای خودم نیز چالش داشت. سعی کردم انجمنهای سورس باز PHP را بررسی کنم تا در نهایت به تحلیل بالا دست یافتم. مدلهای ارائه شده انجمن تا این قسمت، نیازهای مورد نظر ما را برآورده خواهند کرد.
مدل سیستم نظرسنجی
public class Poll : BaseContent { #region Ctor /// <summary> /// create one instance of <see cref="Poll"/> /// </summary> public Poll() { Rating = new Rating(); PublishedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or set Date that this Poll will Expire /// </summary> public virtual DateTime? ExpireOn { get; set; } /// <summary> ///indicating this poll allow to select multi item /// </summary> public virtual bool IsMultiSelect { get; set; } /// <summary> /// gets or sets Count of this poll's votes /// </summary> public virtual long VotesCount { get; set; } /// <summary> /// indicate this Poll is approved by admin if Poll.Moderate==true /// </summary> public virtual bool IsApproved { get; set; } #endregion #region NavigationProperties /// <summary> /// get or set comments of this poll /// </summary> public virtual ICollection<PollComment> Comments { get; set; } /// <summary> /// get or set Options Of Poll For selection /// </summary> public virtual ICollection<PollOption> Options { get; set; } /// <summary> /// get or set Users List That vote for this poll /// </summary> public virtual ICollection<User> Voters { get; set; } #endregion }
- ExpireOn : زمان اتمام فرصت رای دهی که اگر نال باشد در آن صورت زمان انقضا نخواهد داشت.
- IsMultiSelect : اگر انتخاب چندگزینهای مجاز باشد، این خصوصیت، با مقدار true مقدار دهی میشود.
- VotesCount : به منظور افزایش کارآیی در نظر گرفته شده است و تعداد کل رایهای داده شدهی به نظرسنجی را در بر میگیرد.
- Voters : برای جلوگیری از رای دهی چند بارهی کاربر به یک نظرسنجی، یک ارتباط چند به چند بین کاربر و نظرسنجی برقرار کردهایم. هر کاربر به چند نظر سنجی میتواند پاسخ دهد و به هر نظرسنجی توسط چندین کاربر رای داده میشود.
- PollOptions : هر نظر سنجی تعدادی گزینهی انتخابی هم خواهد داشت که برای همین منظور و اعمال ارتباط یک به چند بین نظرسنجی و گزینههای انتخابی، لیستی از PollOption را در مدل بالا تعریف کردهایم.
مدل گزینههای نظرسنجی
public class PollOption { #region Properties /// <summary> /// gets or sets identifier of this polloption /// </summary> public virtual long Id { get; set; } /// <summary> /// gets or sets Title of this polloption /// </summary> public virtual string Title { get; set; } /// <summary> /// gets or sets count of votes /// </summary> public virtual long VotesCount { get; set; } /// <summary> /// gets or sets Description of this Option for more details /// </summary> public virtual string Description { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets the poll that assosiated with this Polloption /// </summary> public virtual Poll Poll { get; set; } /// <summary> /// gets or sets the id of poll that assosiated with this Polloption /// </summary> public virtual long PollId { get; set; } #endregion }
- Title: عنوان گزینهی مورد نظر
- Description: توضیح بیشتر برای گزینهی مورد نظر
- VotesCount: تعداد باری که یک گزینه در نظر سنجی انتخاب شده است.
مدل نظرات سیستم نظرسنجی
public class PollComment : BaseComment { #region Ctor public PollComment() { CreatedOn = DateTime.Now; Rating = new Rating(); } #endregion #region NavigationProperties /// <summary> /// gets or sets body of blog poll's comment /// </summary> public virtual long? ReplyId { get; set; } /// <summary> /// gets or sets body of blog poll's comment /// </summary> public virtual PollComment Reply { get; set; } /// <summary> /// gets or sets body of blog poll's comment /// </summary> public virtual ICollection<PollComment> Children { get; set; } /// <summary> /// gets or sets poll that this comment sent to it /// </summary> public virtual Poll Poll { get; set; } /// <summary> /// gets or sets poll'Id that this comment sent to it /// </summary> public virtual long PollId { get; set; } #endregion }
در مقالهی بعد به بررسی سیستم پیام رسانی و همچنین بخشی از سیستم تحت عنوان Collections (امکان ساخت گروههای شخصی برای انتشار مطالب خود (توسط کاربران) با اعمال دسترسیهای مختلف) خواهیم پرداخت.
نتیجه تا این قسمت
متدی تحت عنوان 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>(); }