افزونهی jQuery Persian Datepicker
متدی تحت عنوان 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 Identity #2
در ASP.NET MVC 3 دو موتور نمایشی به صورت پیش فرض نصب هستند (WebForms and Razor). بنابراین اگر صرفا از Razor استفاده میکنید، میتوان موتور اول را کلا از سیستم پردازشی برنامه حذف کرد. برای اینکار تنها کافی است در فایل global.asax.cs برنامه بنویسیم:
protected void Application_Start() { ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new RazorViewEngine()); ... }
پس از نصب از طریق NuGet، به صورت خودکار اسمبلیهای لازم به پروژه اضافه شده و همچنین فایل web.config برنامه نیز ویرایش میشود. در انتهای این فایل سطر ذیل مشخص میکند که Glimpse فعال باشد یا خیر.
<glimpse enabled="true" />
به این ترتیب یک کوکی به مرورگر اضافه شده و اکنون پس از بازگشت به صفحه اصلی برنامه و refresh کامل صفحه، در کنار سمت راست پایین صفحه، آیکن آن ظاهر خواهد شد.
بر روی این آیکن کلیک نمائید تا در برگهی View آن، انواع Viewهایی که درگیر نمایش صفحه جاری بودهاند، مشخص شوند:
همانطور که ملاحظه میکنید در اینجا دو موتور پیش فرض فعال بوده و پس از سعی و خطای صورت گرفته، در انتهای کار Razor انتخاب شده است. اکنون اگر نکته حذف موتورهای نمایشی اضافی را اعمال کنیم به تصویر زیر خواهیم رسید:
هم تعداد سعی و خطاها کمتر شده و هم تعداد فایلهایی که بررسی شده است به حداقل رسیده (برای مثال در حالتیکه موتور WebForms فعال باشد، چهار فایل با پسوندهای مختلف در مکانهای پیش فرض نیز حتما جستجو خواهند شد).
{ "name": "dntcommon.web.core.testwebapp", "version": "1.0.0", "description": "", "scripts": {}, "author": "", "license": "ISC", "dependencies": { "bootstrap": "^3.3.7", "bootstrap-rtl": "^3.3.4", "components-font-awesome": "5.0.6", "jquery": "^3.3.1", "jquery-ajax-unobtrusive": "^3.2.4", "jquery-validation": "^1.17.0", "jquery-validation-unobtrusive": "^3.2.8", "samim-font": "1.0.2" } }
C:\Projects\ZagrosCore\src\Zagros>npm install npm WARN dntcommon.web.core.testwebapp@1.0.0 No description npm WARN dntcommon.web.core.testwebapp@1.0.0 No repository field. up to date in 0.181s
public class AccessControlService { private DbContext db; public AccessControlService() { db = new DbContext(); } public IEnumerable<Permission> GetUserPermissions(string userId) { var userRoles = this.GetUserRoles(userId); var userPermissions = new List<Permission>(); foreach (var userRole in userRoles) { foreach (var permission in userRole.Permissions) { // prevent duplicates if (!userPermissions.Contains(permission)) userPermissions.Add(permission); } } return userPermissions; } public IEnumerable<Role> GetUserRoles(string userId) { return db.Users.FirstOrDefault(x => x.UserId == userId).Roles.ToList(); } public bool HasPermission(string userId, string area, string control) { var found = false; var userPermissions = this.GetUserPermissions(userId); var permission = userPermissions.FirstOrDefault(x => x.Area == area && x.Control == control); if (permission != null) found = true; return found; } {
[RoutePrefix("َAuth/permissions")] public class PermissionsController : ApiController { private AccessControlService _AccessControlService = null; public PermissionsController() { _AccessControlService = new AccessControlService(); } [Route("GetUserPermissions")] public async Task<IHttpActionResult> GetUserPermissions() { if (!User.Identity.IsAuthenticated) { return Unauthorized(); } return Ok(_AccessControlService.GetPermissions(User.Identity.GetCurrentUserId())); } }
توسعه سرویسها و فرآیندهای سمت وب کلاینت AngularJS
'use strict'; angular.module('App').factory('permissionService', ['$http', '$q', function ($http, $q) { var _getUserPermissions = function () { return $http.get(serviceBaseUrl + '/api/permissions/GetUserPermissions/'); } var _isAuthorize = function (area, control) { return _.some($scope.permissions, { 'area': area, 'control': control }); } return { getUserPermissions: _getUserPermissions, isAuthorize: _isAuthorize }; }]);
$scope.login = function () { authService.login($scope.loginData).then(function (response) { savePermissions(); $location.path('/userPanel'); }, function (err) { $scope.message = err.error_description; }); }; var savePermissions = function () { permissionService.getUserPermissions().then(function (response) { $rootScope.permissions = response.data; }, function (err) { }); } }
App.controller('parentController', ['$rootScope', '$scope', 'authService', 'permissionService', function ($rootScope, $scope, authService, permissionService) { $scope.authentication = authService.authentication; // isAuthorize Method $scope.isAuthorize = permissionService.isAuthorize(); // rest of codes }]);
<div ng-controller="childController"> <div ng-if="isAuthorize('articles', 'edit')" > <!-- the block that we want to not see unauthorize person --> </div> </div>
اگر چک لیستهای SEO وب سایت ها را مشاهده کنیم، میتوانیم آنها را در دو دستهی کلی بهینه سازی درونی و برونی وب سایت در نظر بگیریم:
Off-Page Optimization یا برونی ، که بیشتر بر دوش مشاوران سئو و خود مدیران وب سایت است.(link building ، فعالیت در شبکه اجتماعی و ...)
و اما در حوزه On-Page Optimization یا درونی که بخشهای مهمی از آن وظیفهی مابرنامه نویسها است.(H1 Tag ، URL Naming ، Meta Tags ، عنوان صفحه و ...)
[البته عامل درونی بهینه سازی محتوا (Content Optimization) که مهمترین عامل در الگوریتمهای نسل جدید موتورهای جستجو و همچنین الگوریتم جدید گوگل (و +) به حساب میآید بر عهده مشاوران سئو و خود مدیران وب سایت میباشد]
در ادامه به ارائه چند راهکار جهت بهینه سازی برنامههای وب ASP.NET مان برای موتورهای جستجو میپردازیم:
1.متدی برای ایجاد عنوان سایت
private const string SeparatorTitle = " - "; private const int MaxLenghtTitle = 60; public static string GeneratePageTitle(params string[] crumbs) { var title = ""; for (int i = 0; i < crumbs.Length; i++) { title += string.Format ( "{0}{1}", crumbs[i], (i < crumbs.Length - 1) ? SeparatorTitle : string.Empty ); } title = title.Substring(0, title.Length <= MaxLenghtTitle ? title.Length : MaxLenghtTitle).Trim(); return title; }
- MaxLenghtTitle پیشنهادی برای عنوان سایت 60 میباشد.
2.متدی برای ایجاد متاتگ صفحات سایت
public enum CacheControlType { [Description("public")] _public, [Description("private")] _private, [Description("no-cache")] _nocache, [Description("no-store")] _nostore }
private const int MaxLenghtTitle = 60; private const int MaxLenghtDescription = 170; private const string FaviconPath = "~/cdn/ui/favicon.ico"; public static string GenerateMetaTag(string title, string description, bool allowIndexPage, bool allowFollowLinks, string author = "", string lastmodified = "", string expires = "never", string language = "fa", CacheControlType cacheControlType = CacheControlType._private) { title = title.Substring(0, title.Length <= MaxLenghtTitle ? title.Length : MaxLenghtTitle).Trim(); description = description.Substring(0, description.Length <= MaxLenghtDescription ? description.Length : MaxLenghtDescription).Trim(); var meta = ""; meta += string.Format("<title>{0}</title>\n", title); meta += string.Format("<link rel=\"shortcut icon\" href=\"{0}\"/>\n", FaviconPath); meta += string.Format("<meta http-equiv=\"content-language\" content=\"{0}\"/>\n", language); meta += string.Format("<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/>\n"); meta += string.Format("<meta charset=\"utf-8\"/>\n"); meta += string.Format("<meta name=\"description\" content=\"{0}\"/>\n", description); meta += string.Format("<meta http-equiv=\"Cache-control\" content=\"{0}\"/>\n", EnumExtensions.EnumHelper<CacheControlType>.GetEnumDescription(cacheControlType.ToString())); meta += string.Format("<meta name=\"robots\" content=\"{0}, {1}\" />\n", allowIndexPage ? "index" : "noindex", allowFollowLinks ? "follow" : "nofollow"); meta += string.Format("<meta name=\"expires\" content=\"{0}\"/>\n", expires); if (!string.IsNullOrEmpty(lastmodified)) meta += string.Format("<meta name=\"last-modified\" content=\"{0}\"/>\n", lastmodified); if (!string.IsNullOrEmpty(author)) meta += string.Format("<meta name=\"author\" content=\"{0}\"/>\n", author); //------------------------------------Google & Bing Doesn't Use Meta Keywords ... //meta += string.Format("<meta name=\"keywords\" content=\"{0}\"/>\n", keywords); return meta; }
-
MaxLenghtDescription پیشنهادی برای متاتگ توضیح سایت 170 می باشد.
- آشنایی با متاتگها (Meta tags) و کاربرد آنها در صفحات وب (HTML)
- برای کاربرد allowIndexPage و allowFollowLinks هم میتوانید به لینک بالا و بررسی متاتگ robots بپردازید.
- با توجه به اهمیت شبکههای اجتماعی متاتگهای شبکههای اجتماعی (+ و +) را هم نباید از قلم انداخت.
- برای دریافت Description نوع سفارشی CacheControlType از پروژه متدهای الحاقی علیرضا اسم رام استفاده کردم.
3.متدی برای ایجاد Slug ( اسلاگ آدرسی با مفهوم برای بکار بردن در URL ها است که دوستدار موتورهای جستجو میباشد)
private const int MaxLenghtSlug = 45; public static string GenerateSlug(string title) { var slug = RemoveAccent(title).ToLower(); slug = Regex.Replace(slug, @"[^a-z0-9-\u0600-\u06FF]", "-"); slug = Regex.Replace(slug, @"\s+", "-").Trim(); slug = Regex.Replace(slug, @"-+", "-"); slug = slug.Substring(0, slug.Length <= MaxLenghtSlug ? slug.Length : MaxLenghtSlug).Trim(); return slug; } private static string RemoveAccent(string text) { var bytes = Encoding.GetEncoding("UTF-8").GetBytes(text); return Encoding.UTF8.GetString(bytes); }
- MaxLenghtSlug پیشنهادی برای عنوان سایت 45 میباشد.
نمونه ای از کاربرد توابع :
Head.InnerHtml = SEO.GenerateMetaTag ( title: SEO.GeneratePageTitle(".NET Tips", "آرشیو مطالب", "ASP.NET MVC #1"), description: "چرا ASP.NET MVC با وجود فریم ورک پختهای به نام ASP.NET web forms، اولین سؤالی که حین سوئیچ به ASP.NET MVC مطرح میشود این است: «برای چی؟». بنابراین تا به این سؤال پاسخ داده نشود، هر نوع بحث فنی در این مورد بی فایده است.", allowIndexPage: true, allowFollowLinks: true, author: "وحید نصیری", cacheControlType: SEO.CacheControlType._private );
<title>.NET Tips - آرشیو مطالب - ASP.NET MVC #1</title> <link rel="shortcut icon" href="../../cdn/images/ui/favicon.ico"/> <meta http-equiv="content-language" content="fa"/> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <meta charset="utf-8"/> <meta name="description" content="چرا ASP.NET MVC ؟با وجود فریم ورک پختهای به نام ASP.NET web forms، اولین سؤالی که حین سوئیچ به ASP.NET MVC مطرح میشود این است: «برای چی؟». بن ..."/> <meta http-equiv="Cache-control" content="private"/> <meta name="robots" content="index, follow" /> <meta name="expires" content="never"/> <meta name="author" content="وحید نصیری"/>
Anti CSRF module for ASP.NET
مثال بالا رو میشه در حد یک شوخی قابل تامل درنظر گرفت! شخص با باز کردن این صفحه به صورت خودکار logout میشود. نمونه این تصاویر داینامیک رو شاید دیده باشید. مثلا نمایش IP و ISP شما در تصویر امضای اشخاص حاضر در یک فوروم. یا نمایش تصاویر اشعاری که هر بار به صورت پویا تغییر میکنند.
کلا src تصویر تزریق شده، توسط مرورگر فراخوانی میشود. مرورگر کاری نداره تصویر است یا قرار است یک تصویر پویا باشد. بنابراین این صفحه پویا با سطح دسترسی شما روی سرور و سایت اجرا میشود. یعنی به هر آنچه که یوزر شما دسترسی دارد، دسترسی خواهد داشت و یوزر بدون متوجه شدن مطلبی، صفحهای را با دسترسی بالا اجرا میکند.
حالت پیش فرض ارسال دیتا در فرمهای ASP.Net همان متد POST است. از GET گاهی از اوقات در اسکریپتهای Ajax استفاده میشود که خیلی باید مواظب بود. برای مطالعه بیشتر:
http://javascript.about.com/od/ajax/a/ajaxgp.htm