چون من اسم گروپ توی ویو مدل نوشتم نال برگردوند
RadioButtonList در ASP.NET MVC
چون من اسم گروپ توی ویو مدل نوشتم نال برگردوند
4. فشرده سازی HTTP را فعال کنید
5.تنظیم CacheControlMaxAge
6. استفاده از OutputCache
7. بهره برداری از ORM Profiler
var scheduler = new StdSchedulerFactory().GetScheduler();
scheduler.Shutdown(true);
1) یک وظیفه به چند trigger نسبت داده شده است.در صورتی که قصد دارید وظیفه از تمامی triggerها گرفته شود (معمولاً هم همین رفتار مد نظر است)، از متد DeleteJob استفاده کنید؛ و اگر قصد دارید تا اجرای وظیفه توسط یک trigger مشخص لغو شود و triggerهای دیگر مختص آن وظیفه به کار خود ادامه دهند، از متد UnscheduleJob استفاده کنید. اگر از متد DeleteJob استفاده میکنید، نام وظیفه را با ایجاد نمونه ای از کلاس JobKey برای آن مشخص کنید و در صورتی که از متد UnscheduleJob استفاده میکنید، نام trigger را با ایجاد نمونه ای از کلاس TriggerKey تعیین کنید.
2) هر وظیفه فقط یک trigger دارد.
scheduler.DeleteJob(new JobKey("job1")); // or scheduler.UnscheduleJob(new TriggerKey("trigger1"));
scheduler.Standby();
scheduler.PauseJob(new JobKey("job1"));
scheduler.ResumeJob(new JobKey("job1"));
IJobDetail job = JobBuilder.Create<NewJob>() .WithIdentity("job1") .Build(); scheduler.AddJob(job, true);
ITrigger trigger = TriggerBuilder.Create() .WithIdentity("newTrigger") .StartNow() .ForJob("job1") .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).WithRepeatCount(20)) .Build(); scheduler.RescheduleJob(new TriggerKey("oldTrigger"), trigger);
ITrigger trigger = TriggerBuilder.Create() .WithIdentity("trigger1") .StartAt(startTime) .ForJob("job1") .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()) .Build();
SchedulerMetaData metaData = scheduler.GetMetaData(); int numberOfJobsExecuted = metaData.NumberOfJobsExecuted;
SchedulerMetaData metaData = scheduler.GetMetaData(); DateTimeOffset? runningSince = metaData.RunningSince;
<!DOCTYPE html> <html ng-app> <head> <title>Sample2</title> </head> <body> <div> <input type="text" ng-model="greeting.text" /> <p>{{greeting.text}}, World!</p> </div> <script src="../Scripts/angular.js"></script> </body> </html>
<!DOCTYPE html> <html ng-app> <head> <title>Sample2</title> </head> <body> <div ng-controller="GreetingController"> <input type="text" ng-model="greeting.text" /> <p>{{greeting.text}}, World!</p> <button ng-click="showData()">Show</button> </div> <script src="../Scripts/angular.js"></script> <script> var GreetingController = function ($scope, $window) { $scope.greeting = { text: "Hello" }; $scope.showData = function () { $window.alert($scope.greeting.text); }; }; </script> </body> </html>
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; var FN_ARG_SPLIT = /,/; var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
using System; using System.Globalization; using System.Reflection; namespace System { public static class PersianDateExtensionMethods { private static CultureInfo _Culture; public static CultureInfo GetPersianCulture() { if (_Culture == null) { _Culture = new CultureInfo("fa-IR"); DateTimeFormatInfo formatInfo = _Culture.DateTimeFormat; formatInfo.AbbreviatedDayNames = new[] { "ی", "د", "س", "چ", "پ", "ج", "ش" }; formatInfo.DayNames = new[] { "یکشنبه", "دوشنبه", "سه شنبه", "چهار شنبه", "پنجشنبه", "جمعه", "شنبه" }; var monthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" }; formatInfo.AbbreviatedMonthNames = formatInfo.MonthNames = formatInfo.MonthGenitiveNames = formatInfo.AbbreviatedMonthGenitiveNames = monthNames; formatInfo.AMDesignator = "ق.ظ"; formatInfo.PMDesignator = "ب.ظ"; formatInfo.ShortDatePattern = "yyyy/MM/dd"; formatInfo.LongDatePattern = "dddd, dd MMMM,yyyy"; formatInfo.FirstDayOfWeek = DayOfWeek.Saturday; System.Globalization.Calendar cal = new PersianCalendar(); FieldInfo fieldInfo = _Culture.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance); if (fieldInfo != null) fieldInfo.SetValue(_Culture, cal); FieldInfo info = formatInfo.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance); if (info != null) info.SetValue(formatInfo, cal); _Culture.NumberFormat.NumberDecimalSeparator = "/"; _Culture.NumberFormat.DigitSubstitution = DigitShapes.NativeNational; _Culture.NumberFormat.NumberNegativePattern = 0; } return _Culture; } public static string ToPeString(this DateTime date,string format = "yyyy/MM/dd") { return date.ToString(format,GetPersianCulture()); } } }
var date1 = DateTime.Now.ToPeString("yyyy/MM/dd"); var date2 = DateTime.Now.ToPeString("dddd, dd MMMM,yyyy"); //Output: //1391/12/13 //یکشنبه, 13 اسفند,1391
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = PersianDateExtensionMethods.GetPersianCulture(); var d1 = DateTime.Now.ToString(); //Output : 1391/12/13 11:25:44 ب.ظ
Thread.CurrentThread.CurrentCulture = PersianCulture.Instance;
یکی از چالشهایی که در طراحی زیرساخت برای 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 مورد نظر برای اعتبارسنجی ورودی متد سرویس نیز در نظر گرفته شده است.
منابع تکمیلی