Mapper.CreateMap<Kala, KalaViewModel>() .ForMember(des => des.Brand_Name, op => op.MapFrom(src => src.Brand.Brand_Name)); var kalas = new[] { new Kala { Kala_id = 1, Brand = new Brand {Brand_id = 1, Brand_Name = "Nike"}, Fee_Kharid = 150000, Name = "Shoes" },new Kala { Kala_id = 2, Brand = new Brand {Brand_id = 1, Brand_Name = "Nike"}, Fee_Kharid = 12000, Name = "Shirt" } }; var kalaviewmodel = Mapper.Map<Kala[], KalaViewModel[]>(kalas);
این مورد انعطاف پذیری زیادی را به همراه خواهد داشت؛ اما ... پس از مدتی این سؤالات مطرح میشوند: آیا میشود در این ستونهای خودکار، فیلدهای DateTime، با تاریخ شمسی نمایش داده شوند؟ آیا امکانپذیر است که ستونهای عددی، جمع پایین صفحه داشته باشند؟ و مواردی از این دست که در مورد نحوه مدیریت این نوع ستونهای خودکار در ادامه بحث خواهد شد.
ابتدا سورس کامل مثال جاری را در ادامه ملاحظه خواهید کرد. تقریبا همان مثال قسمت قبل است که تعاریف ستونهای آن حذف شده است:
using System; using PdfRpt; using PdfRpt.Core.Contracts; using PdfRpt.Core.Helper; using PdfRpt.FluentInterface; namespace PdfReportSamples.AdHocColumns { public class AdHocColumnsPdfReport { public IPdfReportData CreatePdfReport() { return new PdfReport().DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.RightToLeft); doc.Orientation(PageOrientation.Portrait); doc.PageSize(PdfPageSize.A4); doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" }); }) .DefaultFonts(fonts => { fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf", Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf"); }) .PagesFooter(footer => { footer.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy")); }) .PagesHeader(header => { header.DefaultHeader(defaultHeader => { defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png"); defaultHeader.Message("گزارش جدید ما"); }); }) .MainTableTemplate(template => { template.BasicTemplate(BasicTemplate.SilverTemplate); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.Relative); }) .MainTableDataSource(dataSource => { dataSource.GenericDataReader( providerName: "System.Data.SQLite", connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite", sql: @"SELECT [url] as 'آدرس', [name] as 'نام', [NumberOfPosts] as 'تعداد مطالب', [AddDate] as 'تاریخ ارسال' FROM [tblBlogs] WHERE [NumberOfPosts]>=@p1", parametersValues: new object[] { 10 } ); }) .MainTableSummarySettings(summary => { summary.OverallSummarySettings("جمع کل"); summary.PreviousPageSummarySettings("نقل از صفحه قبل"); summary.PageSummarySettings("جمع صفحه"); }) .MainTableAdHocColumnsConventions(adHocColumns => { //We want sum of the int columns adHocColumns.AddTypeAggregateFunction( typeof(Int64), new AggregateProvider(AggregateFunction.Sum) { DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj) }); //We want to dispaly all of the dateTimes as ShamsiDateTime adHocColumns.AddTypeDisplayFormatFormula( typeof(DateTime), data => { return PersianDate.ToPersianDateTime((DateTime)data); } ); adHocColumns.ShowRowNumberColumn(true); adHocColumns.RowNumberColumnCaption("ردیف"); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "There is no data available to display."); }) .Export(export => { export.ToExcel(); export.ToXml(); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\AdHocColumnsSampleRpt.pdf")); } } }
توضیحات:
- با توجه به اینکه تعاریف ستونها را حذف کردهایم و به این ترتیب ستونها به صورت خودکار بر اساس فیلدهای معرفی شده در منبع داده تشکیل میشوند، نیاز است سر ستونها را بتوانیم فارسی نمایش دهیم. به همین جهت اینبار کوئری SQL ما با استفاده از aliasها، نامی فارسی را جهت فیلدها تدارک دیده است:
SELECT [url] as 'آدرس', [name] as 'نام', [NumberOfPosts] as 'تعداد مطالب', [AddDate] as 'تاریخ ارسال' FROM [tblBlogs] WHERE [NumberOfPosts]>=@p1
adHocColumns.ShowRowNumberColumn(true); adHocColumns.RowNumberColumnCaption("ردیف");
adHocColumns.AddTypeDisplayFormatFormula( typeof(DateTime), data => { return PersianDate.ToPersianDateTime((DateTime)data); } );
adHocColumns.AddTypeAggregateFunction( typeof(Int64), new AggregateProvider(AggregateFunction.Sum) { DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj) });
public class Customer { public int Id { set; get; } public string FirstName { get; set; } public string LastName { get; set; } public string Bio { get; set; } public virtual ICollection<Order> Orders { get; set; } [Computed] [NotMapped] public string FullName { get { return FirstName + ' ' + LastName; } } } public class Order { public int Id { set; get; } public string OrderNo { get; set; } public DateTime PurchaseDate { get; set; } public bool ShipToHomeAddress { get; set; } public virtual ICollection<OrderItem> OrderItems { get; set; } [ForeignKey("CustomerId")] public virtual Customer Customer { get; set; } public int CustomerId { get; set; } [Computed] [NotMapped] public decimal Total { get { return OrderItems.Sum(x => x.TotalPrice); } } } public class OrderItem { public int Id { get; set; } public decimal Price { get; set; } public string Name { get; set; } public int Quantity { get; set; } [ForeignKey("OrderId")] public virtual Order Order { get; set; } public int OrderId { get; set; } [Computed] [NotMapped] public decimal TotalPrice { get { return Price * Quantity; } } }
در ادامه میخواهیم اطلاعات حاصل از این کلاسها را با شرایط خاصی به ViewModelهای مشخصی جهت نمایش در برنامه نگاشت کنیم.
نمایش اطلاعات مشتریها
میخواهیم اطلاعات مشتریها را مطابق فرمت کلاس ذیل بازگشت دهیم:
public class CustomerViewModel { public string Bio { get; set; } public string CustomerName { get; set; } }
- اگر Bio نال بود، بجای آن N/A نمایش داده شود.
- CustomerName از خاصیت محاسباتی FullName کلاس مشتری تامین گردد.
برای حل این مساله، نیاز است نگاشت زیر را تهیه کنیم:
this.CreateMap<Customer, CustomerViewModel>() .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(entity => entity.FullName)) .ForMember(dest => dest.Bio, opt => opt.MapFrom(entity => entity.Bio ?? "N/A"));
همچنین در اینجا مطابق نگاشت فوق، خاصیت CustomerName از خاصیت FullName کلاس مشتری دریافت میشود.
کوئری نهایی استفاده کنندهی از این اطلاعات به شکل زیر خواهد بود:
using (var context = new MyContext()) { var viewCustomers = context.Customers .Project() .To<CustomerViewModel>() .Decompile() .ToList(); // don't use // var viewCustomers = Mapper.Map<IEnumerable<Customer>, IEnumerable<CustomerViewModel>>(dbCustomers); foreach (var customer in viewCustomers) { Console.WriteLine("{0} - {1}", customer.CustomerName, customer.Bio); } }
نمایش اطلاعات سفارشهای مشتریها
در ادامه قصد داریم اطلاعات سفارشها را با فرمت ViewModel ذیل نمایش دهیم:
public class OrderViewModel { public string CustomerName { get; set; } public decimal Total { get; set; } public string OrderNumber { get; set; } public IEnumerable<OrderItemsViewModel> OrderItems { get; set; } } public class OrderItemsViewModel { public string Name { get; set; } public int Quantity { get; set; } public decimal Price { get; set; } }
- CustomerName از خاصیت محاسباتی FullName کلاس مشتری تامین گردد.
- خاصیت OrderNumber آن از خاصیت OrderNo تهیه گردد.
به همین جهت کار را با تهیهی نگاشت ذیل ادامه میدهیم:
this.CreateMap<Order, OrderViewModel>() .ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.OrderNo)) .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
using (var context = new MyContext()) { var viewOrders = context.Orders .Project() .To<OrderViewModel>() .Decompile() .ToList(); // don't use // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderViewModel>>(dbOrders); foreach (var order in viewOrders) { Console.WriteLine("{0} - {1} - {2}", order.OrderNumber, order.CustomerName, order.Total); } }
using (var context = new MyContext()) { var viewOrders = context.Orders .Project() .To<OrderViewModel>() .Decompile() .ToList(); // don't use // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderViewModel>>(dbOrders); foreach (var order in viewOrders) { Console.WriteLine("{0} - {1} - {2}", order.OrderNumber, order.CustomerName, order.Total); foreach (var item in order.OrderItems) { Console.WriteLine("({0}) {1} - {2}", item.Quantity, item.Name, item.Price); } } }
this.CreateMap<Order, OrderViewModel>() .ForMember(dest => dest.OrderNumber, opt => opt.MapFrom(src => src.OrderNo)) .ForMember(dest => dest.OrderItems, opt => opt.Ignore()) .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
نمایش اطلاعات یک سفارش، با فرمتی خاص
تا اینجا نگاشتهای انجام شده بر روی لیستی از اشیاء صورت گرفتند. در ادامه میخواهیم اولین سفارش ثبت شده را با فرمت ذیل نمایش دهیم:
public class OrderDateViewModel { public int PurchaseHour { get; set; } public int PurchaseMinute { get; set; } public string CustomerName { get; set; } }
this.CreateMap<Order, OrderDateViewModel>() .ForMember(dest => dest.PurchaseHour, opt => opt.MapFrom(src => src.PurchaseDate.Hour)) .ForMember(dest => dest.PurchaseMinute, opt => opt.MapFrom(src => src.PurchaseDate.Minute)) .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
پس از این تنظیمات، کوئری نهایی به شکل ذیل خواهد بود:
using (var context = new MyContext()) { var viewOrder = context.Orders .Project() .To<OrderDateViewModel>() .Decompile() .FirstOrDefault(); // don't use // var viewOrder = Mapper.Map<Order, OrderDateViewModel>(dbOrder); if (viewOrder != null) { Console.WriteLine("{0}, {1}:{2}", viewOrder.CustomerName, viewOrder.PurchaseHour, viewOrder.PurchaseMinute); } }
فرمت کردن سفارشی اطلاعات در حین نگاشتها
در مورد فرمت کنندههای سفارشی و تبدیلگرها پیشتر بحث کردهایم. اما اغلب آنها را در حالت خاص LINQ to Entities نمیتوان بکار برد، زیرا قابلیت تبدیل به SQL را ندارند. برای مثال فرض کنید میخواهیم خاصیت ShipToHomeAddress کلاس Order را به خاصیت ShipHome کلاس ذیل نگاشت کنیم:
public class OrderShipViewModel { public string ShipHome { get; set; } public string CustomerName { get; set; } }
this.CreateMap<Order, OrderShipViewModel>() .ForMember(dest => dest.ShipHome, opt => opt.MapFrom(src=>src.ShipToHomeAddress? "Yes": "No")) .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.FullName));
using (var context = new MyContext()) { var viewOrders = context.Orders .Project() .To<OrderShipViewModel>() .Decompile() .ToList(); // don't use // var viewOrders = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderShipViewModel>>(dbOrders); foreach (var order in viewOrders) { Console.WriteLine("{0} - {1}", order.CustomerName, order.ShipHome); } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.
پیاده سازی UnitOfWork به وسیله MEF
کلاس کانتکسهای من
public class VegaContext : DbContext, IUnitOfWork, IDbContext { #region Constructors (2) /// <summary> /// Initializes the <see cref="VegaContext" /> class. /// </summary> static VegaContext() { Database.SetInitializer<VegaContext>(null); } /// <summary> /// Initializes a new instance of the <see cref="VegaContext" /> class. /// </summary> public VegaContext() : base("LocalSqlServer") { } #endregion Constructors #region Properties (2) /// <summary> /// Gets or sets the languages. /// </summary> /// <value> /// The languages. /// </value> public DbSet<Language> Languages { get; set; } /// <summary> /// Gets or sets the resources. /// </summary> /// <value> /// The resources. /// </value> public DbSet<Resource> Resources { get; set; } #endregion Properties #region Methods (2) // Public Methods (1) /// <summary> /// Setups the specified model builder. /// </summary> /// <param name="modelBuilder">The model builder.</param> public void Setup(DbModelBuilder modelBuilder) { //todo modelBuilder.Configurations.Add(new ResourceMap()); modelBuilder.Configurations.Add(new LanguageMap()); modelBuilder.Entity<Resource>().ToTable("Vega_Languages_Resources"); modelBuilder.Entity<Language>().ToTable("Vega_Languages_Languages"); //base.OnModelCreating(modelBuilder); } // Protected Methods (1) /// <summary> /// This method is called when the model for a derived context has been initialized, but /// before the model has been locked down and used to initialize the context. The default /// implementation of this method does nothing, but it can be overridden in a derived class /// such that the model can be further configured before it is locked down. /// </summary> /// <param name="modelBuilder">The builder that defines the model for the context being created.</param> /// <remarks> /// Typically, this method is called only once when the first instance of a derived context /// is created. The model for that context is then cached and is for all further instances of /// the context in the app domain. This caching can be disabled by setting the ModelCaching /// property on the given ModelBuidler, but note that this can seriously degrade performance. /// More control over caching is provided through use of the DbModelBuilder and DbContextFactory /// classes directly. /// </remarks> protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new ResourceMap()); modelBuilder.Configurations.Add(new LanguageMap()); modelBuilder.Entity<Resource>().ToTable("Vega_Languages_Resources"); modelBuilder.Entity<Language>().ToTable("Vega_Languages_Languages"); base.OnModelCreating(modelBuilder); } #endregion Methods #region IUnitOfWork Members /// <summary> /// Sets this instance. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <returns></returns> public new IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } #endregion }
در طرف دیگر برای لود کردن کلاس زیر نوشتم
public class LoadContexts { public LoadContexts() { var directoryPath = HttpRuntime.BinDirectory;//AppDomain.CurrentDomain.BaseDirectory; //"Dll folder path"; var directoryCatalog = new DirectoryCatalog(directoryPath, "*.dll"); var aggregateCatalog = new AggregateCatalog(); aggregateCatalog.Catalogs.Add(directoryCatalog); var container = new CompositionContainer(aggregateCatalog); container.ComposeParts(this); } //[Import] //public IPlugin Plugin { get; set; } [ImportMany] public IEnumerable<IDbContext> Contexts { get; set; } }
public class MainContext : DbContext, IUnitOfWork { public MainContext() : base("LocalSqlServer") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); var contextList = new LoadContexts(); //ObjectFactory.GetAllInstances<IDbContext>(); foreach (var context in contextList.Contexts) context.Setup(modelBuilder); Database.SetInitializer(new MigrateDatabaseToLatestVersion<MainContext, Configuration>()); //Database.SetInitializer(new DropCreateDatabaseAlways<MainContext>()); } /// <summary> /// Sets this instance. /// </summary> /// <typeparam name="TEntity">The type of the entity.</typeparam> /// <returns></returns> public IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } }
+ خروجی serializer.Serialize از نوع XElement است. بنابراین در قسمت آرگومان جنریک JsonConvert.DeserializeObject باید XElement ذکر شود. مرحله بعدی آن فراخوانی serializer.Deserialize روی این خروجی است.
Expression<Func<Book, bool>> expression = x => x.Code > 2 && x.Code < 5; var expressionSerializer = new Common.ExpressionSerializer(); var xml = expressionSerializer.Serialize(expression); var xmlToJson = JsonConvert.SerializeObject(xml); var xmlObject = JsonConvert.DeserializeObject<XElement>(xmlToJson); var exp2 = expressionSerializer.Deserialize(xmlObject) as Expression<Func<Book, bool>>;
پیرو مطلب قلمهایی حاوی آیکون که خصوصا در برنامههای مترو بیشتر مرسوم شدهاند، شاید بد نباشد کار برنامه Character Map ویندوز را با WPF شبیه سازی کنیم.
ابتدا Model و ViewModel این برنامه را درنظر بگیرید:
namespace CrMap.Models { public class Symbol { public char Character { set; get; } public string CharacterCode { set; get; } } }
using System; using System.Collections.Generic; using System.Windows.Media; using CrMap.Models; namespace CrMap.ViewModels { public class CrMapViewModel { public IList<Symbol> Symbols { set; get; } public int GridRows { set; get; } public int GridCols { set; get; } public CrMapViewModel() { fillDataSource(); } private void fillDataSource() { Symbols = new List<Symbol>(); GridCols = 15; var fontFamily = new FontFamily(new Uri("pack://application:,,,/"), "/Fonts/#whhglyphs"); GlyphTypeface glyph = null; foreach (var typeface in fontFamily.GetTypefaces()) { if (typeface.TryGetGlyphTypeface(out glyph) && (glyph != null)) break; } if (glyph == null) throw new InvalidOperationException("Couldn't find a GlyphTypeface."); GridRows = (glyph.CharacterToGlyphMap.Count / GridCols) + 1; foreach (var item in glyph.CharacterToGlyphMap) { var index = item.Key; Symbols.Add(new Symbol { Character = Convert.ToChar(index), CharacterCode = string.Format("&#x{0:X}", index) }); } } } }
یک سری قابلیت جالب در WPF برای استخراج اطلاعات قلمها وجود دارند که در فضای نام System.Windows.Media اسمبلی PresentationCore.dll قرار گرفتهاند. برای نمونه پس از وهله سازی FontFamily بر اساس یک قلم مدفون شده در برنامه، امکان استخراج تعداد گلیفهای موجود در این قلم وجود دارد که نحوه انجام آنرا در متد fillDataSource ملاحظه میکنید.
این اطلاعات استخراج شده و لیست Symbols برنامه را تشکیل میدهند. در نهایت برای نمایش این اطلاعات، از ترکیب ItemsControl و UniformGrid استفاده خواهیم کرد:
<Window x:Class="CrMap.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:CrMap.ViewModels" Title="MainWindow" WindowStartupLocation="CenterScreen" WindowState="Maximized" Height="350" Width="525"> <Window.Resources> <vm:CrMapViewModel x:Key="vmCrMapViewModel" /> </Window.Resources> <ScrollViewer VerticalScrollBarVisibility="Visible"> <ItemsControl DataContext="{StaticResource vmCrMapViewModel}" ItemsSource="{Binding Symbols}" Name="MainItemsControl" VerticalAlignment="Top" HorizontalAlignment="Center" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="4"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid HorizontalAlignment="Center" VerticalAlignment="Center" Columns="{Binding GridCols}" Rows="{Binding GridRows}"> </UniformGrid> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <ContentControl> <Border BorderBrush="SlateGray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="1" CornerRadius="3" Margin="3"> <StackPanel Margin="3" Orientation="Vertical"> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Fonts/#whhglyphs" Foreground="DarkRed" FontSize="17" Text="{Binding Character}" /> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding CharacterCode}" /> </StackPanel> </Border> </ContentControl> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Window>
دریافت مثال این مطلب
CrMap.zip
جایگزین کردن یک Subgrid با یک jqGrid کامل
خلاصهی عملیات جایگزینی یک Subgrid را توسط یک jqGrid کامل، در ذیل مشاهده میکنید:
$('#list').jqGrid({ caption: "آزمایش دوازدهم", //..........مانند قبل subGrid: true, subGridRowExpanded: grid1RowExpanded }); function grid1RowExpanded(subGridId, rowId) { var subgridTableId = subGridId + "_t"; var pagerId = "p_" + subgridTableId; var container = 'g_' + subGridId; $("#" + subGridId).html('<div dir="rtl" id="' + container + '" style="width:100%; height: 100%">' + '<table id="' + subgridTableId + '" class="scroll"></table><div id="' + pagerId + '" class="scroll"></div>'); var url = '@Url.Action("GetOrderDetails", "Home", routeValues: new { id = "js-id" })' .replace("js-id", encodeURIComponent(rowId)); // تزریق اطلاعات سمت کاربر به خروجی سمت سرور $("#" + subgridTableId).jqGrid({ caption: "ریز اقلام سفارش " + rowId, autoencode: true, //security - anti-XSS url: url, //..........مانند قبل }); }
امضای متد grid1RowExpanded، شامل id یک div است که گرید جدید در آن قرار خواهد گرفت، به همراه Id ردیفی که اطلاعات زیرگرید آن نیاز است از سرور واکشی شود.
بر مبنای subGridId، مانند قبل، یک جدول و یک div را برای نمایش jgGrid و pager آن به صفحه به صورت پویا اضافه میکنیم.
سپس تعاریف jqGrid آن مانند قبل است و نکتهی خاصی ندارد. بدیهی است گرید جدید نیز میتواند در صورت نیاز یک subgrid دیگر داشته باشد.
در اینجا تنها نکتهی مهم آن نحوهی ارسال اطلاعات rowId به سرور است. اکشن متدی که قرار است اطلاعات زیر گرید را تامین کند، یک چنین امضایی دارد:
public ActionResult GetOrderDetails(int id, JqGridRequest request)
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
jqGrid12.zip
FluentValidation #1
public class Customer { public int Id { get; set; } public string Surname { get; set; } public string Forename { get; set; } public decimal Discount { get; set; } public string Address { get; set; } }
using FluentValidation; public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator { RuleFor(customer => customer.Surname).NotNull(); } }
اعتبارسنجی زنجیره ای برای یک خاصیت
RuleFor(customer => customer.Surname).NotNull().NotEqual("foo");
Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); ValidationResult results = validator.Validate(customer);
خروجی متد Validate، یک ValidationResult است که شامل دو خاصیت زیر میباشد:
- IsValid: از نوع bool برای تعیین اینکه اعتبارسنجی موفقیت آمیز بوده یا خیر.
- Errors: یک مجموعه از ValidationFailure که جزئیات تمام اعتبارسنجیهای ناموفق را شامل میشود.
Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); ValidationResult results = validator.Validate(customer); if(! results.IsValid) { foreach(var failure in results.Errors) { Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " + failure.ErrorMessage); } }
پرتاب استثناها (Throwing Exceptions)
Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); validator.ValidateAndThrow(customer);
public class Customer { public string Name { get; set; } public Address Address { get; set; } } public class Address { public string Line1 { get; set; } public string Line2 { get; set; } public string Town { get; set; } public string County { get; set; } public string Postcode { get; set; } } public class AddressValidator : AbstractValidator<Address> { public AddressValidator() { RuleFor(address => address.Postcode).NotNull(); //etc } } public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(customer => customer.Name).NotNull(); RuleFor(customer => customer.Address).SetValidator(new AddressValidator()) } }
استفاده از Validatorها برای مجموعهها (Collections)
public class Customer { public IList<Order> Orders { get; set; } } public class Order { public string ProductName { get; set; } public decimal? Cost { get; set; } } var customer = new Customer(); customer.Orders = new List<Order> { new Order { ProductName = "Foo" }, new Order { Cost = 5 } };
public class OrderValidator : AbstractValidator<Order> { public OrderValidator() { RuleFor(x => x.ProductName).NotNull(); RuleFor(x => x.Cost).GreaterThan(0); } }
این Validator میتواند داخل CustomerValidator مورد استفاده قرار بگیرد (با استفاده از متد SetCollectionValidator):
public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(x => x.Orders).SetCollectionValidator(new OrderValidator()); } }
می توان با استفاده از متد Where یا Unless روی اعتبارسنجی شرط گذاشت:
RuleFor(x => x.Orders).SetCollectionValidator(new OrderValidator()).Where(x => x.Cost != null);
گروه بندی قوانین اعتبارسنجی
public class PersonValidator : AbstractValidator<Person> { public PersonValidator() { RuleSet("Names", () => { RuleFor(x => x.Surname).NotNull(); RuleFor(x => x.Forename).NotNull(); }); RuleFor(x => x.Id).NotEqual(0); } }
var validator = new PersonValidator(); var person = new Person(); var result = validator.Validate(person, ruleSet: "Names");
using System.Drawing; using System.Windows.Forms; namespace Navasser.Theme.VisualStudio { public class BlueMenuStrip:ToolStripProfessionalRenderer { protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) { var borderColor = ColorTranslator.FromHtml("#E5C365");//Menu Item Border var selectedMenuBackColor = ColorTranslator.FromHtml("#FDF4BF");//Menu Item Background var menuOpenedBackColor = ColorTranslator.FromHtml("#EAF0FF"); var borderPen = new Pen(borderColor); if (e.Item.Selected)//اگر آیتمی از منوها انتخاب شد { var selectedMenuBrush = new SolidBrush(selectedMenuBackColor); var selectedItemBounds = new Rectangle(Point.Empty, e.Item.Size);//اقدام به پر کردن یک مستطیل به اندازه ابعاد آیتم انتخاب شده میکند e.Graphics.FillRectangle(selectedMenuBrush,selectedItemBounds); e.Graphics.DrawRectangle(borderPen,0,0,selectedItemBounds.Width-1,selectedItemBounds.Height-1);// بوردر آیتم را رسم میکند e.Item.BackColor = menuOpenedBackColor; } else { base.OnRenderMenuItemBackground(e); e.ToolStrip.BackColor = ColorTranslator.FromHtml("#EAF0FF"); } } } }
using System.Drawing; using System.Windows.Forms; namespace Navasser.Theme.VisualStudio { public class BlueToolStrip : ToolStripProfessionalRenderer { protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) { var toolStripBackColor = ColorTranslator.FromHtml("#D6DBE9"); var toolStripBrush = new SolidBrush(toolStripBackColor); e.Graphics.FillRectangle(toolStripBrush,0,0,e.AffectedBounds.Width + 10,e.AffectedBounds.Height); } protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e) { var borderColor = ColorTranslator.FromHtml("#E5C365");//For border var selectedToolItemBackColor = ColorTranslator.FromHtml("#FDF4BF"); var selectedItemBrush = new SolidBrush(selectedToolItemBackColor); var pressedItemBackColor = ColorTranslator.FromHtml("#FFF29D"); var pressedItemBrush = new SolidBrush(pressedItemBackColor); var borderPen = new Pen(borderColor); if (e.Item.Selected) { e.Graphics.FillRectangle(selectedItemBrush,0,0,e.Item.Width,e.Item.Height); e.Graphics.DrawRectangle(borderPen,0,0,e.Item.Width-1,e.Item.Height-1); } if (e.Item.Pressed) { e.Graphics.FillRectangle(pressedItemBrush, 0, 0, e.Item.Width, e.Item.Height); e.Graphics.DrawRectangle(borderPen, 0, 0, e.Item.Width - 1, e.Item.Height - 1); } } } }
همچنین میتوانید برای انتخاب رنگهای دلخواه خودتان از ابزار ColorSchemer Studio استفاده کنید.
public IActionResult Get() { var userData = new User { Name = "Farhad", Family = "Zamani" }; _logger.LogInformation($"ValuesController called. Name:{userData.Name}, Family:{userData.Family}"); return Ok(); }
اگر بخواهیم مرتب سازی و یا فیلتری را بر روی Name و یا Family انجام دهیم، کار آسانی نخواهد بود و وقتگیر خواهد بود؛ زیرا باید با یک فیلد رشته ( message )، که تمامی لاگ درون آن قرار دارد، کار کنیم. اما با استفاده از قابلیت Structured Logging مربوط به Serilog، میتوانیم آبجکت userData را به ILogger پاس دهیم و پراپرتیهای آن را به صورت فیلدهای جدا نمایش دهیم.
برای این کار باید آبجکت موردنظر خود را درون {} قرار دهیم و قبل از نام متغییر آن یک @ قرار دهیم. بدین صورت: {userData@} و سپس دیتای موردنظر را در پارامتر دوم logger قرار دهیم.
_logger.LogInformation("ValuesController called. {@userData}", userData);
اگر کد نوشته شده مربوط به ثبت لاگ را به صورت زیر اصلاح کنیم:
public IActionResult Get() { var userData = new User { Name = "Farhad", Family = "Zamani" }; _logger.LogInformation("ValuesController called. {@userData}", userData); return Ok(); }
در پنل کیبانا به راحتی میتوان عملیات مرتب سازی و یا فیلتر را بر روی پراپرتیهای Name و Family انجام دهیم. اکنون اگر پنل کیبانا را تماشا کنید چنین لاگی را مشاهده میکنیم:
هرکدام از پراپرتیهای userData به صورت یک فیلد جدا ارسال شدهاست که به راحتی میتوانید مرتب سازی، فیلتر و... را بر روی هرکدام از فیلدها انجام دهید.
تنظیمات مربوط به Serilog:
public static void Main(string[] args) { var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .Build(); Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .Enrich.WithMachineName() .Filter.ByExcluding(Matching.FromSource("Serilog")) .Filter.ByExcluding(Matching.FromSource("System.Net.Http")) .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore")) .WriteTo.Console() .ReadFrom.Configuration(configuration) .CreateLogger(); CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSerilog() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
فایل appsettings.json:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "Serilog": { "WriteTo": [ { "Name": "Elasticsearch", "Args": { "nodeUris": "http://127.0.0.1:9200;", "indexFormat": "structuredlogging-{0:yyyy.MM}", "templateName": "structuredlogging" } } ] } }
فایل docker-compose برای اجرای Elasticsearch و Kibana:
version: '3' services: elasticsearch: container_name: elasticsearch image: elasticsearch:7.14.2 environment: - discovery.type=single-node ports: - 9200:9200 - 9300:9300 - 8200:8200 kibana: container_name: kibana image: kibana:7.14.2 ports: - 5601:5601
منابع استفاده شده: