static void Main(string[] args) { foreach (var arg in args) { var x = Convert.ToInt32(arg); Console.WriteLine("{0} ^ 2 = {1}", x, x * x); } Console.ReadKey(); }
اما نکته اصلی این مطلب در مورد Debugging این گونه برنامهها است.
static void Main(string[] args) { foreach (var arg in args) { var x = Convert.ToInt32(arg); Console.WriteLine("{0} ^ 2 = {1}", x, x * x); } Console.ReadKey(); }
var list = db.Documents.Where(x => x.Code.CompareTo(Start) >= 0 && x.Code.CompareTo(End) <= 0);
int minValue = 1; int maxValue = 10; var list = db.Documents.Where(x => (int)x.Code >= minValue && (int)x.Code <= maxValue);
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
using System;
using System.Collections.Generic;
//دو فضای نامی که برای این منظور اضافه شدهاند
using Microsoft.CSharp;
using System.CodeDom.Compiler;
namespace compilerTest
{
class Program
{
static void compileIt1()
{
//سورس کد ما جهت کامپایل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
//تعیین نگارش کامپایلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعیین اینکه کد ما سی شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
//تعیین اینکه خروجی یک فایل اجرایی است بعلاوه مشخص سازی محل ذخیره سازی فایل نهایی
CompilerParameters compilerParams = new CompilerParameters
{
OutputAssembly = "D:\\Foo.EXE",
GenerateExecutable = true
};
//عملیات کامپایل در اینجا صورت میگیرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
//اگر خطایی وجود داشته باشد نمایش داده خواهد شد
Console.WriteLine("Number of Errors: {0}", results.Errors.Count);
foreach (CompilerError err in results.Errors)
{
Console.WriteLine("ERROR {0}", err.ErrorText);
}
}
static void Main(string[] args)
{
compileIt1();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
namespace compilerTest
{
class Program
{
static void compileIt2()
{
//سورس کد ما جهت کامپایل
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
}
}
}
";
//تعیین نگارش کامپایلر مورد استفاده
Dictionary<string, string> providerOptions = new Dictionary<string, string>
{
{"CompilerVersion", "v3.5"}
};
//تعیین اینکه کد ما سی شارپ است
CSharpCodeProvider provider = new CSharpCodeProvider(providerOptions);
//نحوه تعیین مشخص سازی کامپایل در حافظه
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false
};
//عملیات کامپایل در اینجا صورت میگیرد
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, source);
// اگر خطایی در کامپایل وجود نداشت متد دلخواه را فراخوانی میکنیم
if (results.Errors.Count == 0)
{
//استفاده از ریفلکشن برای دسترسی به متد و فراخوانی آن
Type type = results.CompiledAssembly.GetType("Foo.Bar");
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(null, null);
}
}
static void Main(string[] args)
{
compileIt2();
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
}
string source =
@"
namespace Foo
{
public class Bar
{
static void Main(string[] args)
{
Bar.SayHello();
}
public static void SayHello()
{
System.Console.WriteLine(""Hello World"");
var r = new System.Drawing.Rectangle(0,0,100,100);
System.Console.WriteLine(r);
}
}
}
";
Number of Errors: 1
ERROR The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)
compilerParams.ReferencedAssemblies.Add("System.Drawing.Dll");
public static class Cmd { public static int Execute(string filename, string arguments) { var startInfo = new ProcessStartInfo { CreateNoWindow = true, FileName = filename, Arguments = arguments, }; using (var process = new Process { StartInfo = startInfo }) { try { process.Start(); process.WaitForExit(30000); return process.ExitCode; } catch (Exception exception) { if (!process.HasExited) { process.Kill(); } return (int)ExitCode.Exception; } } } }
یکی از چالشهایی که در طراحی زیرساخت برای Domain هایی که تعداد زیادی عملیات CRUD را در back office سیستم خود دارند، داشتن مکانیزمی برای ذخیره سازی اطلاعات Master-Detail یا چه بسا Master-Detail-DetailOfDetail میباشد. در ادامه نحوه برخورد با چنین سناریوهایی را در EF Core و همچنین با استفاده از AutoMapper و FluentValidation بررسی خواهیم کرد.
موجودیتهای فرضی
public abstract class Entity : IHaveTrackingState { public long Id { get; set; } [NotMapped] public TrackingState TrackingState { get; set; } } public class Master : Entity { public string Title { get; set; } public ICollection<Detail> Details { get; set; } } public class Detail : Entity { public string Title { get; set; } public ICollection<DetailOfDetail> Details { get; set; } public Master Master { get; set; } public long MasterId { get; set; } } public class DetailOfDetail : Entity { public string Title { get; set; } public Detail Detail { get; set; } public long DetailId { get; set; } }
DbContext برنامه
public class ProjectDbContext : DbContext { public DbSet<Master> Masters { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseInMemoryDatabase("SharedDatabaseName"); } }
public interface IHaveTrackingState { TrackingState TrackingState { get; set; } //ICollection<string> ModifiedProperties { get; set; } } public enum TrackingState { Unchanged = 0, Added = 1, Modified = 2, Deleted = 3 }
با استفاده از پراپرتی TrackingState بالا، امکان مشخص کردن صریح State رکورد ارسالی توسط کلاینت مهیا میشود. قبلا نیز مطلبی در راستای STE یا همان Self-Tracking Entity تهیه شده است؛ و همچنین نظرات ارسالی این مطلب نیز میتواند مفید واقع شود.
DTOهای متناظر با موجودیتهای فرضی
public abstract class Model : IHaveTrackingState { public long Id { get; set; } public TrackingState TrackingState { get; set; } } public class MasterModel : Model { public string Title { get; set; } public ICollection<DetailModel> Details { get; set; } } public class DetailModel : Model { public string Title { get; set; } public ICollection<DetailOfDetailModel> Details { get; set; } } public class DetailOfDetailModel : Model { public string Title { get; set; } }
Mapper.Initialize(expression => { expression.CreateMap<MasterModel, Master>(MemberList.None).ReverseMap(); expression.CreateMap<DetailModel, Detail>(MemberList.None).ReverseMap(); expression.CreateMap<DetailOfDetailModel, DetailOfDetail>(MemberList.None).ReverseMap(); });
البته بهتر است این تنظیمات در درون Profileهای مرتبط با AutoMapper کپسوله شوند و در زمان مورد نیاز نیز برای انجام نگاشتها، واسط IMapper تزریق شده و استفاده شود.
تهیه داده ارسالی فرضی توسط کلاینت
var masterModel = new MasterModel { Title = "Master-Title", TrackingState = TrackingState.Added, Details = new List<DetailModel> { new DetailModel { Title = "Detail-Title", TrackingState = TrackingState.Added, Details = new List<DetailOfDetailModel> { new DetailOfDetailModel { Title = "DetailOfDetail-Title", TrackingState = TrackingState.Added, } } } } };
ذخیره سازی اطلاعات
در EF Core، متد جدید context.ChangeTracker.TrackGraph برای به روز رسانی وضعیت یک گراف از اشیاء مشابه به اطلاعات ارسالی ذکر شده در بالا، اضافه شده است. این مکانیزم مفهوم کاملا جدیدی در EF Core میباشد که امکان کنترل نهایی برروی اشیایی را که قرار است توسط Context ردیابی شوند، مهیا میکند. با پیمایش یک گراف، امکان اجرای عملیات مورد نظر شما را برروی تک تک اشیاء، مهیا میسازد.
using (var context = new ProjectDbContext()) { Console.WriteLine("################ Create Master and Details and DetailsOfDetail ##################"); Print(masterModel); var masterEntity = Mapper.Map<Master>(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }
در تکه کد بالا، پس از انجام عملیات نگاشت، توسط متد TrackGraph به صورت صریح، وضعیت موجودیتها مشخص شده است؛ این کار با تغییر State ارسالی توسط کلاینت به State قابل فهم توسط EF انجام شدهاست. برای این منظور دو متد الحاقی زیر را میتوان در نظر گرفت:
public static class TrackingStateExtensions { public static EntityState ToEntityState(this TrackingState trackingState) { switch (trackingState) { case TrackingState.Added: return EntityState.Added; case TrackingState.Modified: return EntityState.Modified; case TrackingState.Deleted: return EntityState.Deleted; case TrackingState.Unchanged: return EntityState.Unchanged; default: return EntityState.Unchanged; } } public static TrackingState ToTrackingState(this EntityState state) { switch (state) { case EntityState.Added: return TrackingState.Added; case EntityState.Modified: return TrackingState.Modified; case EntityState.Deleted: return TrackingState.Deleted; case EntityState.Unchanged: return TrackingState.Unchanged; default: return TrackingState.Unchanged; } } }
//GetForEditAsync var masterModel = context.Masters .ProjectTo<MasterModel>() .AsNoTracking().Single(a => a.Id == 1); //Client var detail1 = masterModel.Details.First(); detail1.Title = "Details-EditedTitle"; detail1.TrackingState = TrackingState.Modified; foreach (var detail in detail1.Details) { detail.TrackingState = TrackingState.Deleted; //detail.Title = "DetailOfDetails-EditedTitle"; }
متدی تحت عنوان GetForEditAsync که یک MasterModel را بازگشت میدهد، در نظر بگیرید؛ کلاینت از طریق API، این Object Graph را دریافت میکند و تغییرات خود را اعمال کرده و همانطور که مشخص میباشد به دلیل اینکه تنظیمات نگاشت بین Detail و DetailModel در ابتدای بحث نیز انجام شده است، این بار دیگر نیاز به استفاده از متد Include نمیباشد و این عملیات توسط متد ProjectTo خودکار میباشد. در نهایت داده ارسالی توسط کلاینت را دریافت کرده و به شکل زیر عملیات به روز رسانی انجام میشود:
using (var context = new ProjectDbContext()) { Console.WriteLine( "################ Unchanged Master and Modified Details and Deleted DetailsOfDetail ##################"); Print(masterModel); var masterEntity = Mapper.Map<Master>(masterModel); context.ChangeTracker.TrackGraph( masterEntity, n => { var entity = (IHaveTrackingState) n.Entry.Entity; n.Entry.State = entity.TrackingState.ToEntityState(); }); context.SaveChanges(); }
برای بحث اعتبارسنجی هم میتوان به شکل زیر عمل کرد:
public class MasterValidator : AbstractValidator<MasterModel> { public MasterValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailValidator()); } } public class DetailValidator : AbstractValidator<DetailModel> { public DetailValidator() { RuleFor(a => a.Title).NotEmpty(); RuleForEach(a => a.Details).SetValidator(new DetailOfDetailValidator()); } } public class DetailOfDetailValidator : AbstractValidator<DetailOfDetailModel> { public DetailOfDetailValidator() { RuleFor(a => a.Title).NotEmpty(); } }
با استفاده از متد RuleForEach و SetValidator موجود در کتابخانه FluentValidation، امکان مشخص کردن اعتبارسنج برای Detail موجود در شیء Master را خواهیم داشت.
همچنین با توجه به این که برای عملیات Create و Edit از یک مدل (DTO) استفاده خواهیم کرد، شاید لازم باشد اعتبارسنجی خاصی را فقط در زمان ویرایش لازم داشته باشیم، که در این صورت میتوان از امکانات RuleSet استفاده کنید. در مطلب «طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها» با استفاده ValidateWithRuleAttribute امکان مشخص کردن RuleSet مورد نظر برای اعتبارسنجی ورودی متد سرویس نیز در نظر گرفته شده است.
منابع تکمیلی
(void Add(object | افزودن المان به آخر لیست |
(void Remove(object | حذف یک المان خاص از لیست |
()void Clear | حذف کلیه المانها |
( bool Contains(object | شامل این داده میشود یا خیر؟ |
( void RemoveAt(int | حذف یک المان بر اساس جایگاه یا اندیسش |
(void Insert(int, object | افزودن یک المان در جایگاهی (اندیس) خاص بر اساس مقدار position |
(int IndexOf(object | اندیس یا جایگاه یک عنصر را بر میگرداند |
[this[int | ایندکسر ، برای دستریس به عنصر در اندیس مورد نظر |
public class CustomArrayList<T> { private T[] arr; private int count; public int Count { get { return this.count; } } private const int INITIAL_CAPACITY = 4; public CustomArrayList(int capacity = INITIAL_CAPACITY) { this.arr = new T[capacity]; this.count = 0; }
public void Add(T item) { GrowIfArrIsFull(); this.arr[this.count] = item; this.count++; } public void Insert(int index, T item) { if (index > this.count || index < 0) { throw new IndexOutOfRangeException( "Invalid index: " + index); } GrowIfArrIsFull(); Array.Copy(this.arr, index, this.arr, index + 1, this.count - index); this.arr[index] = item; this.count++; } private void GrowIfArrIsFull() { if (this.count + 1 > this.arr.Length) { T[] extendedArr = new T[this.arr.Length * 2]; Array.Copy(this.arr, extendedArr, this.count); this.arr = extendedArr; } } public void Clear() { this.arr = new T[INITIAL_CAPACITY]; this.count = 0; }
برای پیاده سازی آن به دو کلاس نیاز داریم. کلاس ListNode برای نگهداری هر المان و اطلاعات المان بعدی به کار میرود که از این به بعد به آن Node یا گره میگوییم و دیگری کلاس <DynamicList<T برای نگهداری دنباله ای از گرهها و متدهای پردازشی آن.
public class DynamicList<T> { private class ListNode { public T Element { get; set; } public ListNode NextNode { get; set; } public ListNode(T element) { this.Element = element; NextNode = null; } public ListNode(T element, ListNode prevNode) { this.Element = element; prevNode.NextNode = this; } } private ListNode head; private ListNode tail; private int count; // … }
از آن جا که نیازی نیست کاربر با کلاس ListNode آشنایی داشته باشد و با آن سر و کله بزند، آن را داخل همان کلاس اصلی به صورت خصوصی استفاده میکنیم. این کلاس دو خاصیت دارد؛ یکی برای المان اصلی و دیگر گره بعدی. این کلاس دارای دو سازنده است که اولی تنها برای عنصر اول به کار میرود. چون اولین بار است که یک گره ایجاد میشود، پس باید خاصیت NextNode یعنی گره بعدی در آن Null باشد و سازندهی دوم برای گرههای شماره 2 به بعد به کار میرود که همراه المان داده شده، گره قبلی را هم ارسال میکنیم تا خاصیت NextNode آن را به گره جدیدی که میسازیم مرتبط سازد. سه خاصیت کلاس اصلی به نامهای Count,Tail,Head به ترتیب برای اشاره به اولین گره، آخرین گره و تعداد گرهها، به کار میروند که در ادامه کد آنرا در زیر میبینیم:
public DynamicList() { this.head = null; this.tail = null; this.count = 0; } public void Add(T item) { if (this.head == null) { this.head = new ListNode(item); this.tail = this.head; } else { ListNode newNode = new ListNode(item, this.tail); this.tail = newNode; } this.count++; }
سازنده مقدار دهی پیش فرض را انجام میدهد. در متد Add المان جدیدی باید افزوده شود؛ پس چک میکند این المان ارسالی قرار است اولین گره باشد یا خیر؟ اگر head که به اولین گره اشاره دارد Null باشد، به این معنی است که این اولین گره است. پس اولین سازندهی کلاس ListNode را صدا میزنیم و آن را در متغیر Head قرار میدهیم و چون فقط همین گره را داریم، پس آخرین گره هم شناخته میشود که در tail نیز قرار میگیرد. حال اگر فرض کنیم المان بعدی را به آن بدهیم، اینبار دیگر Head برابر Null نخواهد بود. پس دومین سازندهی ListNode صدا زده میشود که به غیر از المان جدید، باید آخرین گره قبلی هم با آن ارسال شود و گره جدیدی که ایجاد میشود در خاصیت NextNode آن نیز قرار بگیرد و در نهایت گره ایجاد شده به عنوان آخرین گره لیست در متغیر Tail نیز قرار میگیرد. در خط پایانی هم به هر مدلی که المان جدید به لیست اضافه شده باشد متغیر Count به روز میشود.
public T RemoveAt(int index) { if (index >= count || index < 0) { throw new ArgumentOutOfRangeException( "Invalid index: " + index); } int currentIndex = 0; ListNode currentNode = this.head; ListNode prevNode = null; while (currentIndex < index) { prevNode = currentNode; currentNode = currentNode.NextNode; currentIndex++; } RemoveListNode(currentNode, prevNode); return currentNode.Element; } private void RemoveListNode(ListNode node, ListNode prevNode) { count--; if (count == 0) { this.head = null; this.tail = null; } else if (prevNode == null) { this.head = node.NextNode; } else { prevNode.NextNode = node.NextNode; } if (object.ReferenceEquals(this.tail, node)) { this.tail = prevNode; } }
برای حذف یک گره شماره اندیس آن گره را دریافت میکنیم و از Head، گره را بیرون کشیده و با خاصیت nextNode آنقدر به سمت جلو حرکت میکنیم تا متغیر currentIndex یا اندیس داده شده برابر شود و سپس گره دریافتی و گره قبلی آن را به سمت تابع RemoveListNode ارسال میکنیم. کاری که این تابع انجام میدهد این است که مقدار NextNode گره فعلی که قصد حذفش را داریم به خاصیت Next Node گره قبلی انتساب میدهد. پس به این ترتیب پیوند این گره از لیست از دست میرود و گره قبلی به جای اشاره به این گره، به گره بعد از آن اشاره میکند. مابقی کد از قبیل جست و برگردان اندیس یک عنصر و ... را به خودتان وگذار میکنم.
در روشهای بالا ما خودمان 2 عدد ADT را پیاده سازی کردیم و متوجه شدیم برای دخیره دادهها در حافظه روشهای متفاوتی وجود دارند که بیشتر تفاوت آن در مورد استفاده از حافظه و کارآیی این روش هاست.
لیستهای پیوندی دو طرفه Doubly Linked_List
لیستهای پیوندی بالا یک طرفه بودند و اگر ما یک گره را داشتیم و میخواستیم به گره قبلی آن رجوع کنیم، اینکار ممکن نبود و مجبور بودیم برای رسیدن به آن از ابتدای گره حرکت را آغاز کنیم تا به آن برسیم. به همین منظور مبحث لیستهای پیوندی دو طرفه آغاز شد. به این ترتیب هر گره به جز حفظ ارتباط با گره بعدی از طریق خاصیت NextNode، ارتباطش را با گره قبلی از طریق خاصیت PrevNode نیز حفظ میکند.
این مبحث را در اینجا میبندیم و در قسمت بعدی آن را ادامه میدهیم.
using System.Threading.Tasks; namespace Async05 { class Program { static void Main(string[] args) { var res = doSomethingAsync().Result; } static async Task<int> doSomethingAsync() { await Task.Delay(1); return 1; } } }
using System; using System.Collections.Generic; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Async05 { class Program { static void Main(string[] args) { var task = showTitlesAsync(new[] { "http://www.google.com", "https://www.dntips.ir" }); task.Wait(); Console.WriteLine(); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } static async Task showTitlesAsync(IEnumerable<string> urls) { foreach (var url in urls) { var title = await getTitleAsync(url); Console.WriteLine(title); } } static async Task<string> getTitleAsync(string url) { var data = await new WebClient().DownloadStringTaskAsync(url); return getTitle(data); } private static string getTitle(string data) { const string patternTitle = @"(?s)<title>(.+?)</title>"; var regex = new Regex(patternTitle); var mc = regex.Match(data); return mc.Groups.Count == 2 ? mc.Groups[1].Value.Trim() : string.Empty; } } }
static async Task showTitlesAsync(IEnumerable<string> urls) { foreach (var url in urls) { try { var title = await getTitleAsync(url); Console.WriteLine(title); } catch (Exception ex) { Console.WriteLine(ex); } } }
System.Net.WebException: The remote server returned an error: (502) Bad Gateway. System.Net.WebException: The remote server returned an error: (502) Bad Gateway. Press any key to exit...
public async Task<double> GetSum2Async() { try { var task1 = GetNumberAsync(); var task2 = GetNumberAsync(); var compositeTask = Task.WhenAll(task1, task2); await compositeTask.ContinueWith(x => { }); return compositeTask.Result[0] + compositeTask.Result[1]; } catch (Exception ex) { //todo: log ex throw; } }
public async Task<double> GetSumAsync() { var leftOperand = await GetNumberAsync(); var rightOperand = await GetNumberAsync(); return leftOperand + rightOperand; }
using System; using System.Threading.Tasks; namespace Async01 { class Program { static void Main(string[] args) { Test2(); Test(); Console.ReadLine(); GC.Collect(); GC.WaitForPendingFinalizers(); Console.ReadLine(); } public static async Task Test() { throw new Exception(); } public static async void Test2() { throw new Exception(); } } }
Warning 1 Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
using System; using System.Threading.Tasks; namespace Async01 { class Program { static void Main(string[] args) { TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; //Test2(); Test(); Console.ReadLine(); GC.Collect(); GC.WaitForPendingFinalizers(); Console.ReadLine(); } private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { e.SetObserved(); Console.WriteLine(e.Exception); } public static async Task Test() { throw new Exception(); } public static async void Test2() { throw new Exception(); } } }
Task.Factory.StartNew(() => { throw new Exception(); }); Thread.Sleep(100); GC.Collect(); GC.WaitForPendingFinalizers();
<configuration> <runtime> <ThrowUnobservedTaskExceptions enabled="true"/> </runtime> </configuration>
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState) { try { ClickMeButton.Tapped += async (sender, args) => { throw new Exception(); }; } catch (Exception ex) { // This won’t catch exceptions! TextBlock1.Text = ex.Message; } }
public delegate void TappedEventHandler(object sender, TappedRoutedEventArgs e);
اجرای این نوع صفحات کار سختی نیست؛ با کمی جستجو در اینترنت مثلا در اینجا میتوانید چیزهای خوبی پیدا کنید. اما متاسفانه اکثر مثالها چیزی شبیه قرار دادن پارشال "ورود اعضا" در کنار پارشال "ثبت نام" هستند. حتما متوجه شدهاید که معمولا این دو صفحه پس از PostBack به صفحهای جدید هدایت میشوند و یا در بهترین حالت به کمک Ajax ، پس از انجام عملیات، پیامی به کاربر نمایش میدهیم.
در این مقاله سعی شده روشی برای ایجاد چند فرم در یک View توضیح داده شود با این شرط که:
اولا : از Ajax یا هلپر ایجکسی استفاده نکنیم.
ثانیا : پس از post-back، عملیات Redirect را انجام ندهیم و صفحه جاری را حفظ کنیم؛ چه قرار باشد همه چیز درست انجام شده باشد و چه مشکلی پیش آمده باشد و پیام خطایی در کنار فیلدها نمایش داده شود.
در این روش به این نکته توجه شده که هر مدل پس از Post-back حفظ شود و مستقل از دیگری رفتار کند. مثلا
اگر یکی از فرمها ناقص پر شد و دکمهی ارسال آن فشرده شد، پس از Post-back، فقط و فقط اجزای همین فرم Validate شود و فرم دوم بدون تغییر باقی بماند.
ویوی زیر را در نظر بگیرید. در layout، دو پارشال، به
کمک اکشنمتد فراخوانی شدهاند:
ViewModelهای مرتبط با این دو بخش به شکل زیر هستند :
ContactVM .cs
public class ContactVM { [Display(Name = "نام")] [Required(ErrorMessage = "لطفا {0} را وارد کنید")] public string Name { get; set; } [EmailAddress(ErrorMessage = "آدرس ایمیل صحیح نیست")] [DataType(DataType.EmailAddress)] [Display(Name = "آدرس ایمیل")] [Required(ErrorMessage = "لطفا {0} را وارد کنید")] public string EmailAddress { get; set; } [Display(Name = "متن پیام")] [Required(ErrorMessage = "حرفی برای گفتن ندارید؟")] public string Description { get; set; } [Required(ErrorMessage = "لطفا {0} را وارد کنید")] [Display(Name = "حاصل جمع")] public string Captcha { get; set; } }
SubscriberVM .cs
public class SubscriberVM { /*[RegularExpression("^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "آدرس ایمیل صحیح نیست")]*/ [EmailAddress(ErrorMessage = "آدرس ایمیل صحیح نیست")] /*.Net4.5*/ [Display(Name = "ایمیل")] [Required(ErrorMessage = "لطفا {0} را وارد کنید")] public string Email { get; set; } [Display(Name = "وضعیت")] public bool IsActive { get; set; } }
در Layout، دو اکشن متد صدا زده شدهاند که وظیفه ارسال ویوهای هر کدام به Layout را به عهده دارند :
<div class="row footerclass"> <div class="col-md--6"> @Html.Action("Subscribers", "Home") </div> <div class="col-md-6"> @Html.Action("Contact", "Home") </div> </div>
اکشن متدهای این دو پارشال به شکل زیر هستند :
public ActionResult Contact() { return PartialView("_Contact", model); } [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult Contact(ContactVM model) { if (ModelState.IsValid) { //Do Something } return PartialView("_Contact", model); } public ActionResult Subscribers() { return PartialView("_Subscribers"); } [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult Subscribers(SubscriberVM model) { if (ModelState.IsValid) { //Do Something } } return PartialView("_Subscribers",model); }
و اما ویوهایی که قرار است نمایش داده شوند:
Contact.Cshtml _
@model IrsaShop.Models.ViewModel.ContactVM <span></span><span>تماس با ما</span> <hr /> @using (Html.BeginForm()) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <div> @Html.TextBoxFor(m => m.Name, new { @class = "form-control", @id = "name", @name = "name", placeholder = "نام" }) @Html.ValidationMessageFor(m => m.Name) </div> <div> @Html.TextBoxFor(m => m.EmailAddress, new { @class = "form-control", @id = "email", @name = "email", placeholder = "ایمیل", @style = "direction: ltr" }) @Html.ValidationMessageFor(m => m.EmailAddress) </div> <div> @Html.TextAreaFor(model => model.Description, new { @class = "form-control", @id = "message", @name = "message", placeholder = "پیام", @style = "max-width: 100%;height: 90px;" }) </div> <div> <input type="button" value="" id="refresh" /> <img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" /> </div> <div> @Html.TextBoxFor(model => model.Captcha, new { @class = "form-control", placeholder = "حاصل جمع؟" }) @Html.ValidationMessageFor(model => model.Captcha) </div> <div> <input type="submit" value="ارسال" name="submitValue" /> </div> }
_Subscriber.Csh tml
@model IrsaShop.Models.SubscriberVM <span></span><span>خبرنامه</span> <hr/> @using (Html.BeginForm()) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <div> <div> @Html.TextBoxFor(m => m.Email, new { @class = "form-control right-buffer top-buffer pull-right", @id = "email", @name = "email", placeholder = "ایمیل", @style = "direction: ltr;width: 50%", @required = "required" }) @* <button type="submit" name="submitValue">ثبت ایمیل</button>*@ <input type="submit" value="ثبت ایمیل" name="submitValue" /> </div> </div> @Html.ValidationMessageFor(m => m.Email,"",new { @class = "right-buffer pull-right"}) }
نکته اول : هیچ نوع ورودی برای Html.BeginForm در نظر گرفته نشده است.
اگر اکشن متدی را برای صدا زدن در این بخش در نظر بگیرید، هنگام Postback به مشکل
برخورد خواهید کرد؛ چون آدرس آن اکشن متد به شکل صریح در آدرس مرورگر فراخوانی میشود و
پارشال ما پس از Post-back به تنهایی
و بدون Layout نمایش داده خواهد شد. اسم بردن از اکشن متد وقتی کارساز است که آن اکشن متد
قرار باشد یک Redirect انجام دهد ولی هدف ما این
است که صفحه را از دست ندهیم و پیامهای خطای ModelState را در همان صفحه قبل و پس
از Post-back ببینیم و همچنین پس از انجام عملیات (مثلا
ارسال پیام) همین صفحه نمایش داده شود.
نکته دوم : نکته اول یک مشکل دارد! اگر به شکل صریح اکشن متد مربوط به Post-back مشخص نشود، بطور اتوماتیک تمامی اکشن متدهایی که ویژگی [HttpPost] دارند اجرا خواهند شد. این یعنی هر دو اکشن متد Contact و Subscriber اجرا میشوند و بنابر آنچه در اکشن متدها نوشتهایم هر دو ModelState بررسی میشود که این هدف ما نیست. مثلا فرم سمت چپ را تکمیل کرده ایم و دکمه "ثبت ایمیل" را فشار دادهایم و صفحه Postback میشود و با اینکه ایمیل در بانک ثبت شده اما فرم سمت راستی با خطا ظاهر میشود که چرا فیلدها خالی هستند!؟
برای حل این مشکل کافیست خاصیت name مربوط به دکمهها را به شکل یک ورودی برای اکشن متدها بفرستیم و بر اساس وضعیت آن تنها state مدل مورد نظر خودمان را بررسی کنیم. پس اصلاح زیر را برای اکشن متدهای دارای ویژگی [HttpPost] انجام میدهیم.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult Contact(ContactVM model, , string submitValue) { if (submitValue == "ارسال") { if (ModelState.IsValid) { //Do Something } } else { ModelState.Clear(); } return PartialView("_Subscribers", model); } [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult Subscribers(SubscriberVM model, string submitValue) { if (submitValue == "ثبت ایمیل") { if (ModelState.IsValid) { //Do Something } } else { ModelState.Clear(); } return PartialView("_Subscribers"); }
نکته سوم : در این روش سعی کنید از ViewModel استفاده کنید و سعی کنید ویو مدلها پراپرتیهای با نام یکسان نداشته باشند. مثلا پراپرتی Email در ویو مدلها نامهای متفاوتی داشته باشند (مثل EmailAddress ، Email ، ContactMail و ...). با اینکار در زمان Postback احتمال اینکه فیلدهای مشترک اتوماتیک پر شده به ما نمایش داده باشند صفر خواهد شد.
نکته چهارم : حواستان باشد پس از انجام عملیات مرتبط با هر فرم در اکشن متد مربوط به آن (مثلا ارسال ایمیل، ثبت در بانک یا ...) در صورتی که عملیات با موفقیت انجام شد حتما ModelState را clear کنید. با اینکار پس از Post-back فیلدهای پارشالها خالی میشوند.
نکته پنجم : میتوانید به سادگی مدیریت خطا را به کمک جی کوئری انجام دهید؛ مثلا فرض کنید میخواهیم اگر ایمیل کاربر برای دریافت خبرنامه با موفقیت ثبت شد، پیامی مبنی بر موفقیت برای وی بفرستیم؛ اکشن متد HttPost مربوط به Subscriber را به شکل زیر تکمیل میکنیم :
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult Subscribers(SubscriberVM model, string submitValue) { if (submitValue == "ثبت ایمیل") { if (ModelState.IsValid) { Subscriber mail = new Subscriber() { Email = model.EmailSubscriber, IsActive = true }; context.Subscribers.Add(mail); context.SaveChanges(); ViewBag.info = "ایمیل شما با موفقیت ثبت شد."; ViewBag.color = "alert-success"; ModelState.Clear(); } } else { ModelState.Clear(); } return PartialView("_Subscribers "); }
در انتهای پارشال _Subscriber هم چند خط کد زیر را مینویسیم :
@if (!String.IsNullOrEmpty(ViewBag.info)) { <div id="info" style="position: fixed; bottom: 0; right: 0; margin-right: 1%;"> <div class="alert @ViewBag.color alert-dismissable"> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> <strong> @ViewBag.info</strong> </div> </div> <script> $(function () { $("#info").fadeOut(15000); }); </script> }
نتیجه این خواهد بود که پس از PostBack در صورت موفقیت تصویر زیر را خواهیم دید و 15 ثانیه المان سبزرنگ بوت استرپِ زیر نمایش داده خواهد شد.
این روش نوعی مدیریت میان اکشن متدهای دارای ویژگی HttpPost است و همانطور که گفتیم به علت اینکه پس از Post-Back نیاز به ساختار به هم نخوردهی صفحهی قبلی داریم، نمیتوانستیم به شکل صریح، اکشن متد برای Html.BeginForm تعریف کنیم تا این دردسرها را نداشته باشیم.
حین نوشتن این مقاله به علت وجود if های تو در تو، امیدوار بودم که روشهای بهتری برای اینکار موجود باشند و هنوز هم امیدوارم نظرات شما چنین چیزی را نشان دهد.
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())); } }
'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>
class MyService { public int CalculateXYZ() { // Tons of work to do in here! for (int i = 0; i != 10000000; ++i) ; return 42; } }
class MyService { public async Task<int> CalculateXYZAsync() { return await Task.Run(() => { // Tons of work to do in here! for (int i = 0; i != 10000000; ++i) ; return 42; }); } }
private async void MyButton_Click(object sender, EventArgs e) { await Task.Run(() => myService.CalculateXYZ()); }
await Task.Run(() => { for (int i = 0; i != 10000000; ++i) ; });