using System.Linq.Expressions; public class ContextWithExtensionExample { public void DoSomeContextWork(DbContext context) { var uni = new Unicorn(); context.Set<Unicorn>().AddIfNotExists(uni , x => x.Name == "James"); } } public static class DbSetExtensions { public static T AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class, new() { var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any(); return !exists ? dbSet.Add(entity) : null; } }
چگونگی رسیدگی به Null property در AutoMapper
public class TestModel { public byte[] MProperty { get; set; } }
public class TestViewModel { public byte[] VMProperty { get; set; } }
class Program { static void Main(string[] args) { Mapper.CreateMap<TestModel,TestViewModel>().ForMember(tv=>tv.VMProperty,m=>m.MapFrom(t=>t.MProperty)); TestModel tm = new TestModel(); tm.MProperty = new byte[] { 1, 2, 3 ,4}; TestViewModel tvm = Mapper.Map<TestModel, TestViewModel>(tm); foreach (var item in tvm.VMProperty) { Console.WriteLine(item.ToString()); } Console.ReadKey(); } }
نحوهی ارتقاء برنامههای SignalR 1.x به SignalR 2.x
2) حذف وابستگیهای قدیمی
Uninstall-Package Microsoft.AspNet.SignalR -RemoveDependencies
3) نصب فایلهای جدید SignalR
Install-Package Microsoft.AspNet.SignalR
4) به روز رسانی ارجاعات اسکریپتی
<script src="Scripts/jquery.signalR-2.0.0.min.js"></script>
5) حذف نحوهی تعریف مسیریابی هابهای SignalR از فایل global.asax برنامه.
protected void Application_Start(object sender, EventArgs e) { //RouteTable.Routes.MapHubs(); }
using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(SignalRChat.Startup))] namespace SignalRChat { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } }
اگر از آخرین نگارش VS.NET استفاده میکنید، این کلاس را توسط گزینه Add -> New Item -> Owin Startup Class نیز میتوانید اضافه نمائید.
راه اندازی StimulSoft Report در ASP.NET MVC
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Exec Command="xcopy /y /d $(ProjectDir)Packages\*.* $(OutDir)" /> </Target>
public static class StimulSoftLicense { public static void LoadLicense(IWebHostEnvironment environment) { var contentRoot = environment.ContentRootPath; var licenseFile = System.IO.Path.Combine(contentRoot,"Reports", "license.key"); Stimulsoft.Base.StiLicense.LoadFromFile(licenseFile); } }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { StimulSoftLicense.LoadLicense(env); }
public IActionResult RequestReport(int id) { return View(); } public IActionResult LoadReportData(int id) { StiReport report = new StiReport(); report.Load(StiNetCoreHelper.MapPath(this, "Reports/Requests/RequestInfo.mrt")); var landDetailsReport = GetOwnerReportData(id); report.RegBusinessObject("Land", landDetailsReport); return StiNetCoreViewer.GetReportResult(this, report); } public IActionResult ViewerEvent() { return StiNetCoreViewer.ViewerEventResult(this); }
<div style="direction: ltr"> @Html.StiNetCoreViewer(new StiNetCoreViewerOptions() { Theme = StiViewerTheme.Office2007Silver, Appearance = { RightToLeft = true, //ShowTooltips = false, ShowTooltipsHelp = false, }, Localization = "~/Reports/fa.xml", Actions = { GetReport = "LoadReportData", ViewerEvent = "ViewerEvent", }, Toolbar = { ShowAboutButton = false, ShowOpenButton = false, ShowSaveButton = true, ShowFindButton = false, ShowEditorButton = false, ShowDesignButton = false, ShowBookmarksButton = false, ShowResourcesButton = false, ShowParametersButton = false, ShowPinToolbarButton = false }, Exports = { DefaultSettings = { ExportToPdf = { CreatorString = "SANA" } }, ShowExportDialog = false, ShowExportToDocument = false, ShowExportToExcel = false, ShowExportToExcel2007 = false, ShowExportToHtml = false, ShowExportToHtml5 = false, ShowExportToImageBmp = false, ShowExportToImageJpeg = false, ShowExportToImagePcx = false, ShowExportToImagePng = false, ShowExportToImageMetafile = false, ShowExportToImageTiff = false, ShowExportToOpenDocumentCalc = false, ShowExportToMht = false, ShowExportToOpenDocumentWriter = false, ShowExportToXps = false, ShowExportToSylk = false, ShowExportToRtf = false, ShowExportToExcelXml = false, ShowExportToText = false, ShowExportToWord2007 = false, ShowExportToImageGif = false, ShowExportToCsv = false, ShowExportToDbf = false, ShowExportToDif = false, ShowExportToImageSvg = false, ShowExportToImageSvgz = false, ShowExportToPowerPoint = false, ShowExportToXml = false, ShowExportToJson = false } }) </div>
.stiJsViewerClearAllStyles { font-family: "IRANSans" !important; } //مخصوص دیالوگ تنظیمات خروجی در صورت فعال بودن .stiJsViewerGroupPanelContainer{ direction: rtl !important; } .stiJsViewerFormButtonDefault{ direction: rtl !important; } .stiJsViewerFormButtonOver{ direction: rtl !important; } .stiJsViewerFormButtonDefault>table>tbody>tr>td{ text-align: right !important; } .stiJsViewerFormButtonOver>table>tbody>tr>td{ text-align: right !important; } .stiJsViewerFormHeader{ direction: rtl; } .stiJsViewerFormHeader>table>tbody>tr>td{ text-align: right !important; } .stiJsViewerCheckBox{ direction:rtl !important; } .stiJsViewerDropdownPanel{ direction:rtl !important; } .stiJsViewerFormContainer td.stiJsViewerClearAllStyles{ direction:rtl !important; } .stiMvcViewerReportPanel table{ direction:ltr !important; }
EF Code First #12
public static class DataFactory { public static IUnitOfWork UnitOfWork { get { return ObjectFactory.GetInstance<IUnitOfWork>(); } } public static ICategoryService CategoryService { get { return ObjectFactory.GetInstance<IUnitOfWork>(); } } public static IProductService ProductService { get { return ObjectFactory.GetInstance<IUnitOfWork>(); } } } ... public HomeController() { _productService = DataFactory.ProductService; _categoryService = DataFactory.CategoryService; _uow = DataFactory.UnitOfWork; }
using System.Web.Compilation; namespace DbResourceProvider { public class DbResourceProviderFactory : ResourceProviderFactory { #region Overrides of ResourceProviderFactory public override IResourceProvider CreateGlobalResourceProvider(string classKey) { return new GlobalDbResourceProvider(classKey); } public override IResourceProvider CreateLocalResourceProvider(string virtualPath) { return new LocalDbResourceProvider(virtualPath); } #endregion } }
using System.Globalization; using System.Resources; using System.Web.Compilation; namespace DbResourceProvider { public abstract class BaseDbResourceProvider : IResourceProvider { private DbResourceManager _resourceManager; protected abstract DbResourceManager CreateResourceManager(); private void EnsureResourceManager() { if (_resourceManager != null) return; _resourceManager = CreateResourceManager(); } #region Implementation of IResourceProvider public object GetObject(string resourceKey, CultureInfo culture) { EnsureResourceManager(); if (_resourceManager == null) return null; if (culture == null) culture = CultureInfo.CurrentUICulture; return _resourceManager.GetObject(resourceKey, culture); } public virtual IResourceReader ResourceReader { get { return null; } } #endregion } }
using System; using System.Resources; namespace DbResourceProvider { public class GlobalDbResourceProvider : BaseDbResourceProvider { private readonly string _classKey; public GlobalDbResourceProvider(string classKey) { _classKey = classKey; } #region Implementation of BaseDbResourceProvider protected override DbResourceManager CreateResourceManager() { return new DbResourceManager(_classKey); } public override IResourceReader ResourceReader { get { throw new NotSupportedException(); } } #endregion } }
using System.Resources; namespace DbResourceProvider { public class LocalDbResourceProvider : BaseDbResourceProvider { private readonly string _virtualPath; public LocalDbResourceProvider(string virtualPath) { _virtualPath = virtualPath; } #region Implementation of BaseDbResourceProvider protected override DbResourceManager CreateResourceManager() { return new DbResourceManager(_virtualPath); } public override IResourceReader ResourceReader { get { return new DbResourceReader(_virtualPath); } } #endregion } }
using System.Globalization; using DbResourceProvider.Data; namespace DbResourceProvider { public class DbResourceManager { private readonly string _resourceName; public DbResourceManager(string resourceName) { _resourceName = resourceName; } public object GetObject(string resourceKey, CultureInfo culture) { var data = new ResourceData(); return data.GetResource(_resourceName, resourceKey, culture.Name).Value; } } }
using System.Collections; using System.Resources; using System.Security; using DbResourceProvider.Data; namespace DbResourceProvider { public class DbResourceReader : IResourceReader { private readonly string _resourceName; private readonly string _culture; public DbResourceReader(string resourceName, string culture = "") { _resourceName = resourceName; _culture = culture; } #region Implementation of IResourceReader public void Close() { } public IDictionaryEnumerator GetEnumerator() { return new DbResourceEnumerator(new ResourceData().GetResources(_resourceName, _culture)); } #endregion #region Implementation of IEnumerable IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region Implementation of IDisposable public void Dispose() { Close(); } #endregion } }
using System.Collections; using System.Collections.Generic; using DbResourceProvider.Models; namespace DbResourceProvider { public sealed class DbResourceEnumerator : IDictionaryEnumerator { private readonly List<Resource> _resources; private int _dataPosition; public DbResourceEnumerator(List<Resource> resources) { _resources = resources; Reset(); } public DictionaryEntry Entry { get { var resource = _resources[_dataPosition]; return new DictionaryEntry(resource.Key, resource.Value); } } public object Key { get { return Entry.Key; } } public object Value { get { return Entry.Value; } } public object Current { get { return Entry; } } public bool MoveNext() { if (_dataPosition >= _resources.Count - 1) return false; ++_dataPosition; return true; } public void Reset() { _dataPosition = -1; } } }
<system.web> ... <globalization resourceProviderFactoryType=" نام کامل اسمبلی مربوطه ,نام پرووایدر فکتوری به همراه فضای نام آن " /> ... </system.web>
<globalization resourceProviderFactoryType="DbResourceProvider.DbResourceProviderFactory, DbResourceProvider" />
گرفتن خروجی CamelCase از JSON.NET
یک سری از کتابخانههای جاوا اسکریپتی سمت کلاینت، به نامهای خواص CamelCase نیاز دارند و حالت پیش فرض اصول نامگذاری خواص در دات نت عکس آن است. برای مثال بجای UserName به userName نیاز دارند تا بتوانند صحیح کار کنند.
روش اول حل این مشکل، استفاده از ویژگی JsonProperty بر روی تک تک خواص و مشخص کردن نامهای مورد نیاز کتابخانهی جاوا اسکریپتی به صورت صریح است.
روش دوم، استفاده از تنظیمات ContractResolver میباشد که با تنظیم آن به CamelCasePropertyNamesContractResolver به صورت خودکار به تمامی خواص به صورت یکسانی اعمال میگردد:
var json = JsonConvert.SerializeObject(obj, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
درج نامهای المانهای یک Enum در خروجی JSON
اگر یکی از عناصر در حال تبدیل به JSON، از نوع enum باشد، به صورت پیش فرض مقدار عددی آن در JSON نهایی درج میگردد:
using Newtonsoft.Json; namespace JsonNetTests { public enum Color { Red, Green, Blue, White } public class Item { public string Name { set; get; } public Color Color { set; get; } } public class EnumTests { public string GetJson() { var item = new Item { Name = "Item 1", Color = Color.Blue }; return JsonConvert.SerializeObject(item, Formatting.Indented); } } }
{ "Name": "Item 1", "Color": 2 }
الف) مزین کردن خاصیت از نوع enum به ویژگی JsonConverter از نوع StringEnumConverter:
[JsonConverter(typeof(StringEnumConverter))] public Color Color { set; get; }
return JsonConvert.SerializeObject(item, new JsonSerializerSettings { Formatting = Formatting.Indented, Converters = { new StringEnumConverter() } });
تهیه خروجی JSON از مدلهای مرتبط، بدون Stack overflow
دو کلاس گروههای محصولات و محصولات ذیل را درنظر بگیرید:
public class Category { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Product> Products { get; set; } public Category() { Products = new List<Product>(); } } public class Product { public int Id { get; set; } public string Name { get; set; } public virtual Category Category { get; set; } }
با توجه به این دو کلاس، سعی کنید مثال ذیل را اجرا کرده و از آن، خروجی JSON تهیه کنید:
using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace JsonNetTests { public class SelfReferencingLoops { public string GetJson() { var category = new Category { Id = 1, Name = "Category 1" }; var product = new Product { Id = 1, Name = "Product 1" }; category.Products.Add(product); product.Category = category; return JsonConvert.SerializeObject(category, new JsonSerializerSettings { Formatting = Formatting.Indented, Converters = { new StringEnumConverter() } }); } } }
An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll Additional information: Self referencing loop detected for property 'Category' with type 'JsonNetTests.Category'. Path 'Products[0]'.
راه حل اول:
به تنظیمات JSON.NET، مقدار ReferenceLoopHandling = ReferenceLoopHandling.Ignore را اضافه کنید تا از حلقهی بازگشتی بیپایان جلوگیری شود:
return JsonConvert.SerializeObject(category, new JsonSerializerSettings { Formatting = Formatting.Indented, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Converters = { new StringEnumConverter() } });
به تنظیمات JSON.NET، مقدار PreserveReferencesHandling = PreserveReferencesHandling.Objects را اضافه کنید تا مدیریت ارجاعات اشیاء توسط خود JSON.NET انجام شود:
return JsonConvert.SerializeObject(category, new JsonSerializerSettings { Formatting = Formatting.Indented, PreserveReferencesHandling = PreserveReferencesHandling.Objects, Converters = { new StringEnumConverter() } });
{ "$id": "1", "Id": 1, "Name": "Category 1", "Products": [ { "$id": "2", "Id": 1, "Name": "Product 1", "Category": { "$ref": "1" } } ] }
در مقالهی قبل توانستیم یک سری
از مدلهای مربوط به وبلاگ را آماده کنیم. در ادامه به تکمیل آن و همچین
آغاز تهیهی مدلهای مربوط به اخبار و پیغام خصوصی میپردازیم.
همکاران این قسمت:
سلمان معروفی
مدل گزارش دهی
/// <summary> /// Repersents a Report template for every cms section /// </summary> public class Report { #region Ctor /// <summary> /// Create one instance for <see cref="Report"/> /// </summary> public Report() { ReportedOn = DateTime.Now; Id = SequentialGuidGenerator.NewSequentialGuid(); } #endregion #region Properties /// <summary> /// gets or sets identifier for Report /// </summary> public virtual Guid Id { get; set; } /// <summary> /// gets or sets reason of report /// </summary> public virtual string Reason { get; set; } /// <summary> /// gets or sets section that is reported /// </summary> public virtual ReportSection Section { get; set; } /// <summary> /// gets or sets sectionid that is reported /// </summary> public virtual long SectionId { get; set; } /// <summary> /// gets or sets type of report /// </summary> public virtual ReportType Type{ get; set; } /// <summary> /// gets or sets report's datetime /// </summary> public virtual DateTime ReportedOn { get; set; } /// <summary> /// indicate this report is read by admin /// </summary> public virtual bool IsRead { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets id of user that is reporter /// </summary> public virtual long ReporterId { get; set; } /// <summary> /// gets or sets id of user that is reporter /// </summary> public virtual User Reporter { get; set; } #endregion } /// <summary> /// Represents Report Section /// </summary> public enum ReportSection { News, Poll, Announcement, ForumTopic, BlogComment, BlogPost, NewsComment, PollComment, AnnouncementComment, ForumPost, User, ... } /// <summary> /// Represents Type of Report /// </summary> public enum ReportType { Spam, Abuse, Advertising, ... }
قصد داریم در این سیستم به کاربران خاصی دسترسی گزارش دادن در بخشهای مختلف را بدهیم. این دسترسیها در بخش تنظیمات سیستم قابل تغییر خواهند بود (برای مثال براساس امتیاز ، براساس تعداد پست و ... ) . این امکان میتواند برای مدیریت سیستم مفید باشد.
برای سیستم گزارش دهی به مانند سیستم امتیاز دهی عمل خواهیم کرد. در کلاس Report، خصوصیت ReportSection از نوع دادهی شمارشی میباشد که در بالا تعریف آن نیز آماده است و مشخص کنندهی بخشهایی میباشد که لازم است امکان گزارش دهی داشته باشند. خصوصیت Type هم که از نوع شمارشی ReportType میباشد، مشخص کنندهی نوع گزارشی است که داده شده است.
علاوه بر نوع گزارش، میتوان دلیل گزارش را هم ذخیره کرد که برای این منظور خصوصیت Reason در نظر گرفته شدهاست. خصوصیت IsRead هم برای مدیریت این گزارشات در پنل مدیریت در نظر گرفته شده است. اگر در مقالهی قبل دقت کرده باشید، متوجه وجود خصوصیتی به نام ReportsCount در کلاس BaseContent و BaseComment خواهید شد که برای نشان دادن تعداد گزارشهایی است که برای آن مطلب یا نظر داده شده است، استفاده میشود.
کلاس پایه فایلهای ضمیمه
/// <summary> /// Represents a base class for every attachment /// </summary> public abstract class BaseAttachment { #region Ctor public BaseAttachment() { Id = SequentialGuidGenerator.NewSequentialGuid(); AttachedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// sets or gets identifier for attachment /// </summary> public virtual Guid Id { get; set; } /// <summary> /// sets or gets name for attachment /// </summary> public virtual string FileName { get; set; } /// <summary> /// sets or gets type of attachment /// </summary> public virtual string ContentType { get; set; } /// <summary> /// sets or gets size of attachment /// </summary> public virtual long Size { get; set; } /// <summary> /// sets or gets Extention of attachment /// </summary> public virtual string Extension { get; set; } /// <summary> /// sets or gets bytes of data /// </summary> //public byte[] Data { get; set; } /// <summary> /// sets or gets Creation Date /// </summary> public virtual DateTime AttachedOn { get; set; } /// <summary> /// gets or sets counts of download this file /// </summary> public virtual long DownloadsCount { get; set; } /// <summary> /// gets or sets datetime that is modified /// </summary> public virtual DateTime ModifiedOn { get; set; } /// <summary> /// gets or sets section that this file attached there /// </summary> public virtual AttachmentSection Section { get; set; } /// <summary> /// gets or sets information of user agent /// </summary> public virtual string Agent { get; set; } #endregion #region NavigationProperties /// <summary> /// sets or gets identifier of attachment's owner /// </summary> public virtual long OwnerId { get; set; } /// <summary> /// sets or gets identifier of attachment's owner /// </summary> public virtual User Owner { get; set; } #endregion } public enum AttachmentSection { News, Announcement, ForumTopic, Conversation, BlogComment, NewsComment, PollComment, AnnouncementComment, ForumPost, BlogPost, Group, ... }
کلاس بالا اکثر خصوصیات لازم برای مدل Attachment ما را در خود دارد. قصد داریم از ارث بری TPH برای مدیریت فایلهای ضمیمه استفاده کنیم. در سیستم بستهی ما، تنها کاربران احراز هویت شده میتوانند فایل ضمیمه کنند و برای همین منظور OwnerId را که همان ارسال کنندهی فایل میباشد، به صورت Nullable در نظر نگرفتهایم.
یک سری از مشخصات که نیاز به توضیح اضافی ندارند، ولی خصوصیت AttachmentSection که از نوع شمارشی AttachmentSection است، برای دسترسی راحت کاربر به فایلهای ارسالی خود در پنل کاربری در نظر گرفته شده است. برای بخشهای (وبلاگ - اخبار - نظرسنجیها - آگهیها - انجمن) که نیاز به Privacy خاصی نیست و احراز هویت کفایت میکند، مدل زیر را در نظر گرفته ایم:
مدل فایلهای ضمیمه عمومی
/// <summary> /// Repersent the attachment for file /// </summary> public class Attachment : BaseAttachment { }
/// <summary> /// Represents one news item /// </summary> public class NewsItem : BaseContent { #region Ctor /// <summary> /// create one instance of <see cref="NewsItem"/> /// </summary> public NewsItem() { Rating = new Rating(); PublishedOn = DateTime.Now; } #endregion #region Properties /// <summary> /// indicating that this news show on sidebar /// </summary> public virtual bool ShowOnSideBar { get; set; } /// <summary> /// indicate this NewsItem is approved by admin if NewsItem.Moderate==true /// </summary> public virtual bool IsApproved { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets newsitem's Reviews /// </summary> public ICollection<NewsComment> Comments { get; set; } #endregion }
کلاس بالا نشان دهندهی اشتراکهای ما خواهند بود. این مدل ما هم از کلاس پایهی BaseContent بحث شده در مقالهی قبل، ارث بری کرده و علاوه بر آن دو خصوصیت دیگر تحت عنوان IsApproved برای اعمال مدیریتی در نظر گرفته شده است (اگر در بخش تنظیمات سیستم اخبار، مدیریت تصمیم گرفته باشد تا اخبار جدید به اشتراک گذاشته شده با تأیید مدیریتی منتشر شوند) و خصوصیت ShowOnSideBar هم به عنوان یک تنظیم مدیریتی برای خبر خاصی در نظر گرفته شده که لازم است به صورت sticky در سایدبار نمایش داده شود.
برای اخبار نیز امکان ارسال نظر خواهیم داشت که برای این منظور لیستی از مدل زیر (NewsComment) در مدل بالا تعریف شده است .
مدل نظرات اخبار
public class NewsComment : BaseComment { #region Ctor public NewsComment() { Rating = new Rating(); CreatedOn = DateTime.Now; } #endregion #region NavigationProperties /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual long? ReplyId { get; set; } /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual NewsComment Reply { get; set; } /// <summary> /// gets or sets body of blog NewsItem's comment /// </summary> public virtual ICollection<NewsComment> Children { get; set; } /// <summary> /// gets or sets NewsItem that this comment sent to it /// </summary> public virtual NewsItem NewsItem { get; set; } /// <summary> /// gets or sets NewsItem'Id that this comment sent to it /// </summary> public virtual long NewsItemId { get; set; } #endregion }
/// <summary> /// Indicate one conversation /// </summary> public class Conversation { #region Ctor /// <summary> /// create one instance of <see cref="Conversation"/> /// </summary> public Conversation() { Id = SequentialGuidGenerator.NewSequentialGuid(); SentOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets identifier of record /// </summary> public virtual Guid Id { get; set; } /// <summary> /// represents this conversaion is seen /// </summary> public virtual bool IsRead { get; set; } /// <summary> /// gets or sets subject of this conversation /// </summary> public virtual string Subject { get; set; } /// <summary> /// gets or sets Date that this record added /// </summary> public virtual DateTime SentOn { get; set; } /// <summary> /// indicate this record deleted by sender /// </summary> public virtual bool DeletedBySender { get; set; } /// <summary> /// indicate this record deleted by receiver /// </summary> public virtual bool DeletedByReceiver { get; set; } /// <summary> /// gets or sets Messagescount that Unread by sender of this conversation /// </summary> public virtual int UnReadSenderMessagesCount { get; set; } /// <summary> /// gets or sets Messagescount that Unread by receiver of this conversation /// </summary> public virtual int UnReadReceiverMessagesCount { get; set; } /// <summary> /// gets or sets Messagescount of this conversation for increase performance /// </summary> public virtual int MessagesCount { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets if of user that start this conversation /// </summary> public virtual long SenderId { get; set; } /// <summary> /// gets or sets user that start this conversation /// </summary> public virtual User Sender { get; set; } /// <summary> /// gets or sets id of user that is recipient /// </summary> public virtual long ReceiverId { get; set; } /// <summary> /// gets or sets user that is recipient /// </summary> public virtual User Receiver { get; set; } /// <summary> /// get or set Messages of this conversation /// </summary> public virtual ICollection<ConversationReply> Messages { get; set; } /// <summary> /// get or set Attachments that attached in this conversation /// </summary> public virtual ICollection<ConversationAttachment> Attachments { get; set; } #endregion
مدل بالا نشان دهندهی گفتگوی بین دو کاربر میباشد. هر گفتگو امکان دارد با موضوع خاصی ایجاد شود و مسلما یک کاربر بهعنوان دریافت کننده و کاربر دیگری بعنوان ارسال کننده خواهد بود. برای این منظور خصوصیات Receiver و Sender که از نوع User هستند را در این کلاس در نظر گرفتهایم.
خصوصیات DeletedBySender و DeletedByReceiver هم برای این در نظر گفته شدهاند که اگر یک طرف این گفتگو خواهان حذف آن باشد، برای آن کاربر حذف نرم انجام دهیم و فعلا برای کاربر مقابل قابل دسترسی باشد.
UnReadSenderMessagesCount و UnReadReceiverMessagesCount هم برای بالا بردن کارآیی سیستم در نظر گفته شدهاند و در واقع تعداد پیغامهای خوانده نشده در یک گفتگو به صورت متمایز برای هر دو طرف، ذخیره میشود. هر گفتگو شامل یکسری پیغام رد و بدل شده خواهد بود که بدین منظور لیستی از ConversationReplyها را در مدل بالا تعریف کردهایم.
در هر گفتگو یکسری فایل هم ممکن است ضمیمه شود ، برای این منظور هم یک لیستی از کلاس ConversationAttachment در مدل گفتگو تعریف شده است که در ادامه پیاده سازی کلاس ConversationAttachment را هم خواهیم دید.
مدل ConversationReply به شکل زیر میباشد:
/// <summary> /// Represents One Reply to Conversation /// </summary> public class ConversationReply { #region Ctor /// <summary> /// create one instance of <see cref="ConversationReply"/> /// </summary> public ConversationReply() { Id = SequentialGuidGenerator.NewSequentialGuid(); SentOn = DateTime.Now; } #endregion #region Properties /// <summary> /// gets or sets identifier of record /// </summary> public virtual Guid Id { get; set; } /// <summary> /// represents this conversaionReply is seen /// </summary> public virtual bool IsRead { get; set; } /// <summary> /// gets or sets body of this conversationReply /// </summary> public virtual string Body { get; set; } /// <summary> /// gets or sets Date that this record added /// </summary> public virtual DateTime SentOn { get; set; } #endregion #region NavigationProperties /// <summary> /// gets or sets Parent's Id Of this ConversationReply /// </summary> public virtual Guid? ParentId { get; set; } /// <summary> /// gets or sets Parent Of this ConversationReply /// </summary> public virtual ConversationReply Parent { get; set; } /// <summary> /// get or set Children Of this ConversationReply /// </summary> public virtual ICollection<ConversationReply> Children { get; set; } /// <summary> /// gets or sets if of user that start this conversationReply /// </summary> public virtual long SenderId { get; set; } /// <summary> /// gets or sets user that start this conversationReply /// </summary> public virtual User Sender { get; set; } /// <summary> /// gets or sets Conversation that this message sent in it /// </summary> public virtual Conversation Conversation{ get; set; } /// <summary> /// gets or sets Id of Conversation that this message sent in it /// </summary> public virtual Guid ConversationId { get; set; } #endregion }
مدل بالا نشان دهندهی پیغامهای داده شده در یک گفتگو با موضوعی خاص میباشد. ساختار درختی آن هم برای ایجاد امکان جواب دهی برای پیغامها در نظر گرفته شده است (الزامی نیست). هر پیغام در یک گفتگو ارسال شده و یک ارسال کننده نیز دارد که برای این منظور به ترتیب دو خصوصیت Conversation از نوع کلاس Conversation و Sender از نوع User در نظر گرفتهایم.
با توجه به وجود Privacy در گفتگو نیاز است تا مدل فایل ضمیمه بخش گفتگوها به شکل زیر باشد:
/// <summary> /// Represents the attachment That attached in Conversation /// </summary> public class ConversationAttachment : BaseAttachment { #region NavigationProperties public virtual Conversation Conversation { get; set; } public virtual Guid? ConversationId { get; set; } #endregion }
همانطور که کمی بالاتر بحث شد، قصد اعمال ارث بری TPH را برای مدیریت فایلهای ضمیمه داریم. برای این منظور مدل بالا نیز از کلاس BaseAttachment ارث بری کرده و دو خصوصیت اضافه هم برای اعمال ارتباط یک به چند با گفتگو خواهد داشت. توجه کنید که ConversationId به صورت Nullable تعریف شدهاست.
نتیجه این قسمت
قسمت اول : تبادل دادهها بین لایه ها- قسمت اول
روش دوم: Uniform(Entity classes)
روش دیگر پاس دادن دادهها، روش uniform است. در این روش کلاسهای Entity، یک سری کلاس ساده به همراه یکسری Property های Get و Set میباشند. این کلاسها شامل هیچ منطق کاری نمیباشند. برای مثال کلاس CustomerEntity که دارای دو Property ، Customer Name و Customer Code میباشد. شما میتوانید تمام Entity ها را به صورت یک پروژهی مجزا ایجاد کرده و به تمام لایهها رفرنس دهید.
public class CustomerEntity { protected string _CustomerName = ""; protected string _CustomerCode = ""; public string CustomerCode { get { return _CustomerCode; } set { _CustomerCode = value; } } public string CustomerName { get { return _CustomerName; } set { _CustomerName = value; } } }
خوب، اجازه دهید تا از CustomerDal شروع کنیم. این کلاس یک Collection از CustomerEntity را بر میگرداند و همچنین یک CustomerEntity را برای اضافه کردن به دیتابیس . توجه داشته باشید که لایه Data Access وظیفه دارد تا دیتای دریافتی از دیتابیس را به CustomerEntity تبدیل کند.
public class CustomerDal { public List<CustomerEntity> getCustomers() { // fetch customer records return new List<CustomerEntity>(); } public bool Add(CustomerEntity obj) { // Insert in to DB return true; } }
لایه Middle از CustomerEntity ارث بری میکند و یکسری operation را به entity class اضافه خواهد کرد. دادهها در قالب Entity Class به لایه Data Access ارسال میشوند و در همین قالب نیز بازگشت داده میشوند. این مسئله در کد ذیل به روشنی مشاهده میشود.
public class Customer : CustomerEntity { public List<CustomerEntity> getCustomers() { CustomerDal obj = new CustomerDal(); return obj.getCustomers(); } public void Add() { CustomerDal obj = new CustomerDal(); obj.Add(this); } }
لایه UI هم با تعریف یک Customer و فراخوانی operation های مربوط به آن، دادهی مد نظر خود را در قالب CustomerEntity بازیابی خواهد کرد. اگر بخواهیم عمکرد روش uniform را خلاصه کنیم باید بگوییم، در این روش دیتای رد و بدل شدهی مابین کلیه لایهها با یک ساختار استاندارد، یعنی Entity پاس داده میشوند.
مزایا و معایب روش uniform
مزایا
·Strongly typed به صورت در تمامی لایهها قابل دسترسی و استفاده میباشد.
· به دلیل اینکه از ساختار عمومی Entity استفاده میکند، بنابراین فقط یکبار نیاز به تبدیل دادهها وجود دارد. به این معنی که کافی است یک بار دیتای واکشی شده از دیتابیس را به یک ساختار Entity تبدیل کنید و در ادامه بدون هیچ تبدیل دیگری از این Entity استفاده کنید.
معایب
· تنها مشکلی که این روش دارد، مشکلی است به نام Double Loop . هنگامیکه شما در مورد کلاسهای entity بحث میکنید، ساختارهای دنیای واقعی را مدل میکنید. حال فرض کنید شما به دلیل یکسری مسایل فنی دیتابیس خود را Optimize کرده اید. بنابراین ساختار دنیای واقعی با ساختاری که شما در نرم افزار مدل کردهاید متفاوت میباشد. بگذارید یک مثال بزنیم؛ فرض کنید که یک customer دارید، به همراه یکسری Address. همان طور که ذکر کردیم، به دلیل برخی مسایل فنی ( denormalized ) به صورت یک جدول در دیتا بیس ذخیره شده است. بنابراین سرعت واکشی اطلاعات بیشتر است. اما خوب اگر ما بخواهیم این ساختار را در دنیای واقعی بررسی کنیم، ممکن است با یک ساختار یک به چند مانند شکل ذیل برخورد کنیم.
بنابراین مجبوریم یکسری کد جهت این تبدیل همانند کد ذیل بنویسیم.
foreach (DataRow o1 in oCustomers.Tables[0].Rows) { obj.Add(new CustomerEntyityAddress()); // Fills customer foreach (DataRow o in oAddress.Tables[0].Rows) { obj[0].Add(new AddressEntity()); // Fills address } }
در یک سیستم حسابداری یا هر سیستم مشابهی، موقعی که یک سند در سیستم درج میشود، نقشهای زیادی میتوانند درگیر این عمل باشند:
Create می تواند یک سند حسابداری را ایجاد کند.
Edit میتواند یک سند حسابداری را ویرایش کند.
کاربری که نقش edit را دارد توانایی ویرایش اسناد درج شده در سیستم را دارد ولی اسناد حسابداری تنها در همان روز ثبت، امکان ویرایش دارند و پس از پایان ساعاتی کاری یا از روز بعد دیگر قابل ویرایش نبوده و نباید کسی توانایی ویرایش آن را داشته باشد. به همین دلیل، در این نقطه زمانی، دیگر کاربر Edit نباید بتواند این اکشن متد را صدا بزند. به همین علت باید دسترسی او را بر روی سندهای ماقبل امروز بست.
با نگاهی بر روی بسیاری از کدهای نرم افزارهای مختلف که نوشته شدهاند میتواند دریافت بسیاری از افراد در اکشن مربوطه یا هر دو اکشن Get و Post یک شرط قرار داده و با بررسی اینکه سند متعلق به امروز نیست پیامی را به کاربر نمایش میدهند؛ ولی این نکته باعث میشود که اکشن متد مربوطه تا حدی اولین اصل از اصول Solid یعنی اصل تک مسئولیتی را زیر سوال ببرد. ما در اینجا قصد داریم این موضوع بررسی را به خارج از اکشن متد مربوطه برده و قبل از رسیدن به اکشن متد مورد نظر جلوی آن را بگیریم.
با گردآوری و جمع بندی مطالب سایت تاکنون میتوان تکه کد زیر را نوشت:
در سایت جاری مباحث فیلترها (^ و ^) به طور مکرر مورد بررسی قرار گرفتهاست. طبق منابع ذکر شده یکی از این اکشن متدها ActionFilter بوده که دو متد زیر را در اختیار ما قرار میدهد:
// Called after the action method executes void OnActionExecuted(ActionExecutedContext filterContext) // Called before an action method executes void OnActionExecuting(ActionExecutingContext filterContext)
public class BlockDocument : ActionFilterAttribute { public Func<IDocumentServices> _documentServices { get; set; }; public string ParamName; public override void OnActionExecuting(ActionExecutingContext filterContext) { var id = 0; if (filterContext.ActionParameters.ContainsKey(ParamName)) { id =(int) (filterContext.ActionParameters[ParamName]??0); } if (IsInvalid(id)) { return; } var document = _documentServices().GetDocument(id); if (document.InserTime.IsPassed()) { filterContext.Result = new HttpStatusCodeResult(403); } } }
[BlockDocument(ParamName = "id")] public ActionResult EditDocument(int id) { //... }