- کنترل بهتر روی قوانین اعتبارسنجی.
- جداسازی قوانین اعتبارسنجی از کلاسهای ViewModel یا Model.
- پشتیبانی خوب از Client Side Validation.
- UnitTesting سادهتر نسبت به DataAnnotations.
- سادهتر بودن نوشتن Custom Validator برای موارد خاص.
- اعمال اعتبارسنجی شرطی با FluentValidation راحتتر است.
- امکان اعتبارسنجی گروهی و ...
چک لیست تهیه یک برنامه ASP.NET MVC
- در گزارشات ... «بهتر» است اعمال شود. مثلا با کم کردن تعداد فیلدها به تعدادی که نمایش داده میشوند، میتوان مصرف حافظه برنامه را کاهش داد. شاید یک جدول شما 20 خاصیت داشته باشد و در یک گزارش فقط 5 مورد آن نمایش داده شود. 15 مورد دیگر صرفا مصرف کننده حافظه خواهند شد اگر از viewModel استفاده نکنید. (ولی ... در کل بهتر است اینگونه باشد)
مثال عملی از view model در mvc
من از شما استادان عزیز میخوام این تاپیک رو هرچه زودتر ایجاد و در اختیار عموم قرار بدید
با تشکر
reCAPTCHA چیست؟
استفاده آسان و امنیت بالا، جملهای میباشد که گوگل در سرتیتر تعریف آن جای داده که البته عنوان «من روبات نیستم» در سرویس استفاده شدهاست. reCAPTCHA یک سرویس رایگان برای وب سایتهای شما در جهت حفظ آن در برابر روباتهای مخرب است و از موتور تجزیه و تحلیل پیشرفتهی تشخیص انسان در برابر روباتها استفاده مینماید. reCAPTCHA را میتوان به صورت ماژول در بلاگ و یا فرمهای ثبت نام و ... جای داد که فقط با یک کلیک هویت سنجی انجام خواهد شد. گاها ممکن است بجای کلیک از شما سوالی پرسیده شود که در این صورت میبایستی تصاویر مرتبط با آن سوال را تیک زده باشید. دلیل استفاده از reCAPTCHA:
- گزارش روزانه از وضعیت موفقیت آمیز بودن هویت سنجی
- سهولت استفاده برای کاربران
- سهولت استفاده جهت برنامه نویسان
- دسترسی پذیری مناسب بدلیل وجود سؤالات تصویری و تلفظ و پخش عبارت بصورت صوتی
- امنیت بالا
آیا میتوان قالب reCAPTCHA را تغییر داد؟
جواب این سوال بله میباشد. این سرویس در دو قالب سفید و مشکی ارائه شدهاست که به صورت پیش فرض قالب سفید آن انتخاب میشود. در تصویر زیر قالبهای این سرویس را مشاهده خواهید کرد.
زبانهای پشتیبانی شده در این سرویس:
اضافه نمودن reCAPTCHA به سایت:
اگر قبلا در گوگل ثبت نام نمودهاید کافیست وارد این سایت شوید و بر روی Get reCAPTCHA کلیک نمائید؛ در غیر اینصورت میبایستی یک حساب کاربری ایجاد نماید. بعد از ورود، به کنترل پنل هدایت خواهید شد. در نمای اول به تصویر زیر برخورد خواهید کرد که از شما ثبت سایت جدید را خواستار است:
نام، دامنه سایت و مالک را وارد و ثبت نام نماید.
پس از آنکه بر روی دکمهی ثبت نام کلیک نمودید، برای شما دو کلید جدید را ثبت مینماید که منحصر به سایت شماست. Site Key رشته ای را داراست که در کدهای HTML قرار خواهد گرفت و کلید بعدی Secret Key میباشد. ارتباط سایت شما با گوگل میبایستی به صورت محرمانه محفوظ بماند.
گامهای لازم جهت نمایش سرویس در سایت:
- دستورات سمت کاربر
- دستورات سمت سرور
دستورات سمت کاربر:
کد زیر را در قبل از بسته شدن تک <head/> قرار دهید:
<script src='https://www.google.com/recaptcha/api.js'></script>
<div data-sitekey="6LdHGgwTAAAAAClKFhGthRrjBXh5AUGd4eWNCQq7"></div>
نکته: مقدار data-sitekey برابر است با رشته Site Key که گوگل برای شما ثبت نمود.
دستورات سمت سرور:
وقتی کاربر فرم حاوی کپچا را که به صورت صحیح هویت سنجی آن انجام شده باشد به سمت سرور ارسال کند، به عنوان بخشی از دادهی ارسال شده، یک رشته با نام g-recaptcha-response با دستور Request دریافت خواهید کرد که به منظور بررسی اینکه آیا گوگل تایید کرده است که کاربر، یک درخواست POST ارسال نموداست. با این پارامترها یک مقدار json برگشت داده خواهد شد که میبایستی کلاسی متناظر با آن جهت خواندن ساخته شود.
با استفاده از کد زیر مقدار برگشتی Json را در کلاس مپ مینمائیم:using System.Collections.Generic; using Newtonsoft.Json; namespace BaseConfig.Security.Captcha { public class RepaptchaResponse { [JsonProperty("success")] public bool Success { get; set; } [JsonProperty("error-codes")] public List<string> ErrorCodes { get; set; } } }
با استفاده از کلاس زیر درخواستی به گوگل ارسال شده و در صورتیکه با خطا مواجه شود با استفاده از دستور switch به آن دسترسی خواهیم یافت.
using System.Configuration; using System.Net; using Newtonsoft.Json; namespace BaseConfig.Security.Captcha { public class ReCaptcha { public static string _secret; static ReCaptcha() { _secret = ConfigurationManager.AppSettings["ReCaptchaGoogleSecretKey"]; } public static bool IsValid(string response) { //secret that was generated in key value pair var client = new WebClient(); var reply = client.DownloadString($"https://www.google.com/recaptcha/api/siteverify?secret={_secret}&response={response}"); var captchaResponse = JsonConvert.DeserializeObject<RepaptchaResponse>(reply); // when response is false check for the error message if (!captchaResponse.Success) { //if (captchaResponse.ErrorCodes.Count <= 0) return View(); //var error = captchaResponse.ErrorCodes[0].ToLower(); //switch (error) //{ // case ("missing-input-secret"): // ViewBag.Message = "The secret parameter is missing."; // break; // case ("invalid-input-secret"): // ViewBag.Message = "The secret parameter is invalid or malformed."; // break; // case ("missing-input-response"): // ViewBag.Message = "The response parameter is missing."; // break; // case ("invalid-input-response"): // ViewBag.Message = "The response parameter is invalid or malformed."; // break; // default: // ViewBag.Message = "Error occured. Please try again"; // break; //} return false; } // Captcha is valid return true; } } }
تابع IsValid از نوع برگشتی Boolean بوده و خطایی برگشت داده نخواهد شد و از این جهت به صورت کامنت برای شما گذاشته شده که میتوان متناظر با کد نویسی آن را تغییر دهید.
در اکشن زیر مقدار response برسی میشود تا خالی نباشد و همچنین مقدار آن را میتوان با استفاده از تابع IsValid در کلاس ReCaptcha به سمت گوگل فرستاد.
// // POST: /Account/Login [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public virtual async Task<ActionResult> Login(LoginPageModel model, string returnUrl) { var response = Request["g-recaptcha-response"]; if (response != null && ReCaptcha.IsValid(response)) { // } }
گاها اتفاق میافتد که از دستورات Ajax برای ارسال اطلاعات به سمت سرور استفاده میشود که در این صورت لازم است بعد از پایان عملیات، کپچا ریفرش گردد. برای این کار میتوان از دستور جاوا اسکریپتی زیر استفاده نمود. در صورتیکه صفحه Postback شود، کپچا مجددا ریفرش خواهد شد.
/** * * @param {} data * @returns {} */ function Success(data) { grecaptcha.reset(); }
تا اینجا موفق شدیم تا فرم ارسالی همراه کپچا را به سمت سرور ارسال کنیم. اما ممکن است در یک صفحه از چند کپچا استفاده شود که در این صورت میبایستی دستورات سمت کاربر تغییر نمایند.
برای این کار دستور
<div data-sitekey="6LdHGgwTAAAAAClKFhGthRrjBXh5AUGd4eWNCQq7"></div>
<script> var recaptcha1; var recaptcha2; var myCallBack = function () { //Render the recaptcha1 on the element with ID "recaptcha1" recaptcha1 = grecaptcha.render('recaptcha1', { 'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9', 'theme': 'light' }); //Render the recaptcha2 on the element with ID "recaptcha2" recaptcha2 = grecaptcha.render('recaptcha2', { 'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9', 'theme': 'light' }); //Render the recaptcha3 on the element with ID "recaptcha3" recaptcha2 = grecaptcha.render('recaptcha3', { 'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9', 'theme': 'light' }); }; </script>
برای نمایش کپچا، تگهای div با id متناظر با recaptcha1, recaptcha2, recaptcha3 ( در این مثال از سه کپچا در صفحه استفاده شده است ) در صفحه قرار خواهند گرفت.
<div id="recaptcha1"></div> <div id="recaptcha2"></div> <div id="recaptcha3"></div>
کار ما تمام شد. حال اگر پروژه را اجرا نمائید، در صفحه سه کپچا مشاهده خواهید کرد.
چند زبانه کردن کپچا:
برای چند زبانه کردن کافیست با مراجعه به این لینک و یا استفاده از تصویر بالا ( زبانهای پشتیبانی ) مقدار آن زبان را برابر با پراپرتی hl که به صورت کوئری استرینگ برای گوگل ارسال میگردد، استفاده نمود. کد زیر نمونهی استفاده شده برای زبانهای انگلیسی و فارسی میباشد که با ریسورس مقدار دهی میشود.<script src='https://www.google.com/recaptcha/api.js?hl=@(App_GlobalResources.CP.CurrentAbbrivation)'></script>
در صورتی که از فایل ریسوس استفاده نمیکنید میتوان به صورت مستقیم مقدار دهی نمائید:
<script src='https://www.google.com/recaptcha/api.js?hl=fa'></script>
برای دوستانی که با تکنولوژی ASP.Net کار میکنند، این روال هم برای آنها هم صادق میباشد.
برای دریافت پروژه اینجا کلیک نمائید.
اهمیت code review
من به برخی از مشکلات کد شما اشاره می کنم:
1. ترکیب کدهای DAL و کد UI.
2. عدم جداسازی Business Object ها.
3. ایجاد کلاسی به اسم SqlHelper و قرار دادن N تا متود static در این کلاس، نشون میده که برنامه از هیچ یک از معیارهای موجود پیروی نمیکنه. مطلقا دلیلی نداره که متودها static تعریف بشن و ... حتی اگر قرار باشه چنین کلاسی تعریف کنیم، (که من کاملا باهاش مخالفم)، باید متودهای اونو بر اساس Property ی Singleton ای از کلاس به بیرون Expose کنیم. نه اینکه همه متودهاشو static بذاریم و کلاس رو هم sealed کنیم و ... چنین کلاسی هرگز قابل توسعه نیست.
4. وقتی همه متودهای کلاس static هستش، چرا Constructor ای private برای اون کلاس تعریف کرده اید؟ تازه بهتر بود جای اینکه کلاس رو sealed تعریف می کردید، اونو static تعریف می کردید (اگر مجاب میشدیم که تعریف چنین کلاسی صحیح هستش).
5. catch کردن کلیه SqlException ها در متود CloseCnt.
6. عدم استفاده از Parametric Command ها (و در نتیجه فراهم اومدن امکان SQL Injection).
7. عدم اجرای StyleCop روی کد (و در نتیجه، نوشتن Comment های مربوط به هر تابع بر اساس Style ای من در آوردی، عدم رعایت Spacing، نوشتن if ها در یک خط و ...)
8. و ...
موفق باشید.
یک نکتهی تکمیلی: تاثیر فراخوانی متد AsNoTracking بر روی کوئریهای خود ارجاعی
همانطور که در مطلب «مباحث تکمیلی مدلهای خود ارجاع دهنده در EF Code first» هم مشاهده کردید، خود EF، قابلیت تشکیل درخت نهایی خود ارجاع دهنده را دارد و به این ترتیب کوئری گرفتن از نتیجهی آن، بسیار ساده میشود. اما ... اگر در این بین، از متد AsNoTracking برای بهینه سازی، کاهش میزان حافظه و حذف پروکسیهای ردیابی تغییرات EF استفاده شود، دیگر این درخت خودکار، تشکیل نخواهد شد. برای پوشش این حالت میتوان به صورت زیر عمل کرد:
الف) تشکیل یک کلاس پایه برای تعریف سادهتر و مشخص رابطههای خود ارجاعی
public abstract class BaseEntity { public int Id { get; set; } } public abstract class BaseSelfReferencingEntity<TSelfEntity> : BaseEntity where TSelfEntity : BaseEntity { public virtual TSelfEntity? Reply { set; get; } public int? ReplyId { get; set; } public virtual ICollection<TSelfEntity>? Children { get; set; } }
که ساختار معرفی شدهی در اینجا، با توضیحات موجود در متن، انطباق دارد.
ب) پر کردن درخت نهایی حاصل به صورت دستی:
چون دیگر EF این درخت را برای ما تشکیل نمیدهد، اکنون باید خودمان کار تشکیل آنرا به صورت زیر انجام دهیم:
public static class SelfReferencingExtensions { public static List<TEntity> ToSelfReferencingTree<TEntity>(this ICollection<TEntity>? originalList) where TEntity : BaseSelfReferencingEntity<TEntity> { var results = new List<TEntity>(); if (originalList is null || originalList.Count == 0) { return results; } foreach (var rootItem in originalList.Where(x => !x.ReplyId.HasValue)) { results.Add(rootItem); AppendChildren(originalList, rootItem); } return results; } private static void AppendChildren<TEntity>(ICollection<TEntity> originalList, TEntity parentItem) where TEntity : BaseSelfReferencingEntity<TEntity> { foreach (var kid in originalList.Where(x => x.ReplyId.HasValue && x.ReplyId.Value == parentItem.Id)) { parentItem.Children ??= new List<TEntity>(); parentItem.Children.Add(kid); AppendChildren(originalList, kid); } } }
در اینجا کار تشکیل درخت نهایی، با استفاده از یک متد بازگشتی، انجام میشود.
پس از این مقدمات، نحوهی استفاده از آن به صورت زیر است:
var comments = await _comments.AsNoTracking() .Where(x => x.ParentId == postId) .OrderBy(x => x.Id) .Take(count) .ToListAsync(); var commentsTree = comments.ToSelfReferencingTree();
کوئری نویسی ابتدایی آن، کاملا استاندارد و بدون هیچگونه نکتهی خاصی است. ابتدا تمام نظرات یک مطلب (به صورت AsNoTracking) بازگشت داده میشوند و سپس متد ToSelfReferencingTree کار اتصالات نهایی درخت پاسخها را به صورت خودکار انجام میدهد.
ASP.NET MVC #18
- سپس دو کلاس Role و User را باید تعریف کنید. این دو رابطه many-to-many با هم دارند؛ یعنی هر کدام با یک ICollection به دیگری ارتباط پیدا میکنند. سپس این دو کلاس را در کلاس Context برنامه مطابق معمول توسط DbSetها در معرض دید EF قرار میدهید. مابقی آن کارکردن معمولی با این دو جدول اضافه شده به برنامه است:
public class EfRolesService : IRolesService { readonly IUnitOfWork _uow; readonly IDbSet<Role> _roles; public EfRolesService(IUnitOfWork uow) { _uow = uow; _roles = _uow.Set<Role>(); } public IList<Role> FindUserRoles(int userId) { var query = from role in _roles from user in role.Users where user.Id == userId select role; return query.OrderBy(x => x.Name).ToList(); } public string[] GetRolesForUser(int userId) { var roles = FindUserRoles(userId); if (roles == null || !roles.Any()) { return new string[] { }; } return roles.Select(x => x.Name).ToArray(); } public bool IsUserInRole(int userId, string roleName) { var query = from role in _roles where role.Name == roleName from user in role.Users where user.Id == userId select role; var userRole = query.FirstOrDefault(); return userRole != null; } }
public class CustomRoleProvider : RoleProvider { public override bool IsUserInRole(string username, string roleName) { // Since the role provider, in this case the CustomRoleProvider is instantiated by // the ASP.NET framework the best solution is to use the service locator pattern. // The service locator pattern is normally considered to be an anti-pattern but // sometimes you have to be pragmatic and accept the limitation on the framework // that is being used (in this case the ASP.NET framework). var rolesService = ObjectFactory.GetInstance<IRolesService>(); return rolesService.IsUserInRole(username.ToInt(), roleName); } public override string[] GetRolesForUser(string username) { var rolesService = ObjectFactory.GetInstance<IRolesService>(); return rolesService.GetRolesForUser(username.ToInt()); } // مابقی نیازی نیست پیاده سازی شوند
Tuple در دات نت 4
نوع جدیدی در دات نت 4 به نام Tuple اضافه شده است که در این مطلب به بررسی آن خواهیم پرداخت.
در ریاضیات، Tuple به معنای لیست مرتبی از اعضاء با تعداد مشخص است. Tuple در زبانهای برنامه نویسی Dynamic مانند اف شارپ، Perl ، LISP و بسیاری موارد دیگر مطلب جدیدی نیست. در زبانهای dynamic برنامه نویسها میتوانند متغیرها را بدون معرفی نوع آنها تعریف کنند. اما در زبانهای Static مانند سی شارپ، برنامه نویسها موظفند نوع متغیرها را پیش از کامپایل آنها معرفی کنند که هر چند کار کد نویسی را اندکی بیشتر میکند اما به این صورت شاهد خطاهای کمتری نیز خواهیم بود (البته سی شارپ 4 این مورد را با معرفی واژهی کلیدی dynamic تغییر داده است).
برای مثال در اف شارپ داریم:
let data = (“John Doe”, 42)
که سبب ایجاد یک tuple که المان اول آن یک رشته و المان دوم آن یک عدد صحیح است میشود. اگر data را بخواهیم نمایش دهیم خروجی آن به صورت زیر خواهد بود:
printf “%A” data
// Output: (“John Doe”,42)
در دات نت 4 فضای نام جدیدی به نام System.Tuple معرفی شده است که در حقیقت ارائه دهندهی نوعی جنریک میباشد که توانایی در برگیری انواع مختلفی را دارا است :
public class Tuple<T1>
up to:
public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>
همانند آرایهها، اندازهی Tuples نیز پس از تعریف قابل تغییر نیستند (immutable). اما تفاوت مهم آن با یک آرایه در این است که اعضای آن میتوانند نوعهای کاملا متفاوتی داشته باشند. همچنین تفاوت مهم آن با یک ArrayList یا آرایهای از نوع Object، مشخص بودن نوع هر یک از اعضاء آن است که type safety بیشتری را به همراه خواهد داشت و کامپایلر میتواند در حین کامپایل دقیقا مشخص نماید که اطلاعات دریافتی از نوع صحیحی هستند یا خیر.
یک مثال کامل از Tuples را در کلاس زیر ملاحظه خواهید نمود:
using System;
using System.Linq;
using System.Collections.Generic;
namespace TupleTest
{
class TupleCS4
{
#region Methods (4)
// Public Methods (4)
public static Tuple<string, string> GetFNameLName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new NullReferenceException("name is empty.");
var nameParts = name.Split(',');
if (nameParts.Length != 2)
throw new FormatException("name must contain ','");
return Tuple.Create(nameParts[0], nameParts[1]);
}
public static void PrintSelectedTuple()
{
var list = new List<Tuple<string, int>>
{
new Tuple<string, int>("A", 1),
new Tuple<string, int>("B", 2),
new Tuple<string, int>("C", 3)
};
var item = list.Where(x => x.Item2 == 2).SingleOrDefault();
if (item != null)
Console.WriteLine("Selected Item1: {0}, Item2: {1}",
item.Item1, item.Item2);
}
public static void PrintTuples()
{
var tuple1 = new Tuple<int>(12);
Console.WriteLine("tuple1 contains: item1:{0}", tuple1.Item1);
var tuple2 = Tuple.Create("Item1", 12);
Console.WriteLine("tuple2 contains: item1:{0}, item2:{1}",
tuple2.Item1, tuple2.Item2);
var tuple3 = Tuple.Create(new DateTime(2010, 5, 6), "Item2", 20);
Console.WriteLine("tuple3 contains: item1:{0}, item2:{1}, item3:{2}",
tuple3.Item1, tuple3.Item2, tuple3.Item3);
}
public static void Tuple8()
{
var tup =
new Tuple<int, int, int, int, int, int, int, Tuple<int, int>>
(1, 2, 3, 4, 5, 6, 7, new Tuple<int, int>(8, 9));
Console.WriteLine("tup.Rest Item1: {0}, Item2: {1}",
tup.Rest.Item1,tup.Rest.Item2);
}
#endregion Methods
}
}
using System;
namespace TupleTest
{
class Program
{
static void Main()
{
var data = TupleCS4.GetFNameLName("Vahid, Nasiri");
Console.WriteLine("Data Item1:{0} & Item2:{1}",
data.Item1, data.Item2);
TupleCS4.PrintTuples();
TupleCS4.PrintSelectedTuple();
TupleCS4.Tuple8();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
توضیحات :
- روشهای متفاوت ایجاد Tuples را در متد PrintTuples میتوانید ملاحظه نمائید. همچنین نحوهی دسترسی به مقادیر هر کدام از اعضاء نیز مشخص شده است.
- کاربرد مهم Tuples در متد GetFNameLName نمایش داده شده است؛ زمانیکه نیاز است تا چندین خروجی از یک تابع داشته باشیم. به این صورت دیگر نیازی به تعریف آرگومانهایی به همراه واژه کلیدی out نخواهد بود یا دیگر نیازی نیست تا یک شیء جدید را ایجاد کرده و خروجی را به آن نسبت دهیم. به همان سادگی زبانهای dynamic در اینجا نیز میتوان یک tuple را ایجاد و استفاده کرد.
- بدیهی است از Tuples در یک لیست جنریک و یا حالات دیگر نیز میتوان استفاده کرد. مثالی از این دست را در متد PrintSelectedTuple ملاحظه خواهید نمود. ابتدا یک لیست جنریک از Tuple ایی با دو عضو تشکیل شده است. سپس با استفاده از امکانات LINQ ، عضوی که آیتم دوم آن مساوی 2 است یافت شده و سپس المانهای آن نمایش داده میشود.
- نکتهی دیگری را که حین کار با Tuples میتوان در نظر داشت این است که اعضای آن حداکثر شامل 8 عضو میتوانند باشند که عضو آخر باید یک Tuple تعریف گردد و بدیهی است این Tuple نیز میتواند شامل 8 عضو دیگر باشد و الی آخر که نمونهای از آن را در متد Tuple8 میتوان مشاهده کرد.
ERROR TypeError: "this.fileInput2 is undefined"
<ng-template #templateUploads> <div> <strong>{{titleModal}}</strong> <button type="button" aria-label="Close" (click)="closeModal()"> <span aria-hidden="true">×</span> </button> </div> <div> <form #upladForm="ngForm" (submit)="submitUpload()" novalidate> <fieldset> <div> <label>فایل</label> <div> <input required #fileInput2 type="file"> </div> </div> <div> <button (click)="upload()" type="button"> upload </button> <button type="submit" > close </button> </div> </fieldset> </form> </div> </ng-template>
@ViewChild('fileInput2') fileInput2: ElementRef; constructor(...... submitUpload(form: NgForm) { const fileInput2File: HTMLInputElement = this.fileInput2.nativeElement; .....