As stated in this article, Windows 10, version 1909 is a scoped set of features for select performance improvements, enterprise features and quality enhancements. Developers should be aware of this release, but no action is necessary at this time.
Bootstrap 3.4.0 منتشر شد
اندرباب اهمیت به اشتراک گذاری اطلاعات
در مورد stackOverflow یه موضوعی هست. من بعد از مدت ها خواستم یک سوال بزارم توی این سایت اما پیغام خطای زیر رو میده :
Oops! Your question couldn't be submitted becauseIt does not meet our quality standards
میتونید راهنمایی کنید؟ ممنون میشم
npm install @aspnet/signalr --save
var connection = new signalR.HubConnectionBuilder().withUrl('/message').build();
پس از اعمال این تغییرات، کدهای به روز شدهی انتهای مطلب را از اینجا میتوانید دریافت کنید: SignalRCore2Sample-SDK-2.2.104.zip
بررسی روش ارتقاء به NET Core 1.1.
<PublishFramework>netcoreapp1.1</PublishFramework>
- وابستگی ویندوز سرور آن علاوه بر نصب NET Core.، نصب Visual C++ Redistributable for Visual Studio 2015 است. بستهای را که در اینجا لینک دادند، قدیمی است. آدرس بستهی به روز و جدید آن (با شماره نگارش 14.0.24215 ^)
This post shows how to import and export .xls or .xlsx (Excel files) in ASP.NET Core. And when thinking about dealing with excel with .NET, we always look for third-party libraries or component. And one of the most popular .net library that reads and writes Excel 2007/2010 files using the Open Office Xml format (xlsx) is EPPlus. However, at the time of writing this post, this library is not updated to support .NET Core. But there exists an unofficial version of this library EPPlus.Core which can do the job of import and export xlsx in ASP.NET Core. This works on Windows, Linux and Mac.
نصب کتابخانهی FluentValidation در پروژه
فرض کنید پروژهی ما از سه پوشهی FluentValidationSample.Web، FluentValidationSample.Models و FluentValidationSample.Services تشکیل شدهاست که اولی یک پروژهی MVC است و دو مورد دیگر classlib هستند.
در پروژهی FluentValidationSample.Models، بستهی نیوگت کتابخانهی FluentValidation را به صورت زیر نصب میکنیم:
dotnet add package FluentValidation.AspNetCore
جایگزین کردن سیستم اعتبارسنجی مبتنی بر DataAnnotations با FluentValidation
اکنون فرض کنید در پروژهی Models، مدل ثبتنام زیر را اضافه کردهایم که از همان data annotations توکار و استاندارد ASP.NET Core برای اعتبارسنجی اطلاعات استفاده میکند:
using System.ComponentModel.DataAnnotations; namespace FluentValidationSample.Models { public class RegisterModel { [Required] [Display(Name = "User name")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } [DataType(DataType.EmailAddress)] [Display(Name = "Email")] [EmailAddress] public string Email { get; set; } [Range(18, 60)] [Display(Name = "Age")] public int Age { get; set; } } }
using FluentValidation; namespace FluentValidationSample.Models { public class RegisterModelValidator : AbstractValidator<RegisterModel> { public RegisterModelValidator() { RuleFor(x => x.UserName).NotNull(); RuleFor(x => x.Password).NotNull().Length(6, 100); RuleFor(x => x.ConfirmPassword).Equal(x => x.Password); RuleFor(x => x.Email).EmailAddress(); RuleFor(x => x.Age).InclusiveBetween(18, 60); } } }
در اینجا در ابتدا به ازای هر خاصیت کلاس مدل مدنظر، یک RuleFor تعریف میشود که با استفاده از static reflection، امکان تعریف strongly typed آنها وجود دارد. سپس ویژگی Required به متد NotNull تبدیل میشود و ویژگی StringLength توسط متد Length قابل تعریف خواهد بود و یا ویژگی Compare توسط متد Equal به صورت strongly typed به خاصیت دیگری متصل میشود.
پس از این تعاریف، میتوان ویژگیهای اعتبارسنجی اطلاعات را از مدل ثبت نام حذف کرد و تنها ویژگیهای خاص Viewهای MVC را در صورت نیاز باقی گذاشت:
using System.ComponentModel.DataAnnotations; namespace FluentValidationSample.Models { public class RegisterModel { [Display(Name = "User name")] public string UserName { get; set; } [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] public string ConfirmPassword { get; set; } [DataType(DataType.EmailAddress)] [Display(Name = "Email")] public string Email { get; set; } [Display(Name = "Age")] public int Age { get; set; } } }
تعریف پیامهای سفارشی اعتبارسنجی
روش تعریف پیامهای سفارشی شکست اعتبارسنجی اطلاعات را توسط متد WithMessage در ادامه مشاهده میکنید:
using FluentValidation; namespace FluentValidationSample.Models { public class RegisterModelValidator : AbstractValidator<RegisterModel> { public RegisterModelValidator() { RuleFor(x => x.UserName) .NotNull() .WithMessage("Your first name is required.") .MaximumLength(20) .WithMessage("Your first name is too long!") .MinimumLength(3) .WithMessage(registerModel => $"Your first name `{registerModel.UserName}` is too short!"); RuleFor(x => x.Password) .NotNull() .WithMessage("Your password is required.") .Length(6, 100); RuleFor(x => x.ConfirmPassword) .NotNull() .WithMessage("Your confirmation password is required.") .Equal(x => x.Password) .WithMessage("The password and confirmation password do not match."); RuleFor(x => x.Email).EmailAddress(); RuleFor(x => x.Age).InclusiveBetween(18, 60); } } }
روش تعریف اعتبارسنجیهای سفارشی خواص مدل
فرض کنید میخواهیم یک کلمهی عبور وارد شدهی معتبر، حتما از جمع حروف کوچک، بزرگ، اعداد و symbols تشکیل شده باشد. برای این منظور میتوان از متد Must استفاده کرد:
using System.Text.RegularExpressions; using FluentValidation; namespace FluentValidationSample.Models { public class RegisterModelValidator : AbstractValidator<RegisterModel> { public RegisterModelValidator() { RuleFor(x => x.Password) .NotNull() .WithMessage("Your password is required.") .Length(6, 100) .Must(password => hasValidPassword(password)); //... } private static bool hasValidPassword(string password) { var lowercase = new Regex("[a-z]+"); var uppercase = new Regex("[A-Z]+"); var digit = new Regex("(\\d)+"); var symbol = new Regex("(\\W)+"); return lowercase.IsMatch(password) && uppercase.IsMatch(password) && digit.IsMatch(password) && symbol.IsMatch(password); } } }
البته lambda expression نوشته شده را میتوان توسط method groups، به صورت زیر نیز خلاصه نوشت:
RuleFor(x => x.Password) .NotNull() .WithMessage("Your password is required.") .Length(6, 100) .Must(hasValidPassword);
اگر نیاز به استفادهی از متد hasValidPassword در کلاسهای دیگری نیز وجود دارد، میتوان اینگونه اعتبارسنجیهای سفارشی را به کلاسهای مجزایی نیز تبدیل کرد. برای مثال فرض کنید که میخواهیم ایمیل دریافت شده، فقط از یک دومین خاص قابل قبول باشد.
using System; using FluentValidation; using FluentValidation.Validators; namespace FluentValidationSample.Models { public class EmailFromDomainValidator : PropertyValidator { private readonly string _domain; public EmailFromDomainValidator(string domain) : base("Email address {PropertyValue} is not from domain {domain}") { _domain = domain; } protected override bool IsValid(PropertyValidatorContext context) { if (context.PropertyValue == null) return false; var split = context.PropertyValue.ToString().Split('@'); return split.Length == 2 && split[1].Equals(_domain, StringComparison.OrdinalIgnoreCase); } } }
PropertyValidatorContext امکان دسترسی به نام و مقدار خاصیت در حال اعتبارسنجی را میسر میکند. همچنین مقدار کل model جاری را نیز به صورت یک object در اختیار ما قرار میدهد.
اکنون برای استفادهی از آن میتوان از متد SetValidator استفاده کرد:
RuleFor(x => x.Email) .SetValidator(new EmailFromDomainValidator("gmail.com"));
public static class CustomValidatorExtensions { public static IRuleBuilderOptions<T, string> EmailAddressFromDomain<T>( this IRuleBuilder<T, string> ruleBuilder, string domain) { return ruleBuilder.SetValidator(new EmailFromDomainValidator(domain)); } }
RuleFor(x => x.Email).EmailAddressFromDomain("gmail.com");
فرض کنید به RegisterModel این قسمت، دو خاصیت آدرس و شماره تلفنها نیز اضافه شدهاست که یکی به شیء آدرس و دیگری به مجموعهای از آدرسها اشاره میکند:
public class RegisterModel { // ... public Address Address { get; set; } public ICollection<Phone> Phones { get; set; } } public class Phone { public string Number { get; set; } public string Description { get; set; } } public class Address { public string Location { get; set; } public string PostalCode { get; set; } }
public class PhoneValidator : AbstractValidator<Phone> { public PhoneValidator() { RuleFor(x => x.Number).NotNull(); } } public class AddressValidator : AbstractValidator<Address> { public AddressValidator() { RuleFor(x => x.PostalCode).NotNull(); RuleFor(x => x.Location).NotNull(); } }
public class RegisterModelValidator : AbstractValidator<RegisterModel> { public RegisterModelValidator() { // ... RuleFor(x => x.Address).SetValidator(new AddressValidator()); RuleForEach(x => x.Phones).SetValidator(new PhoneValidator()); }
در قسمت بعد، روشهای مختلف استفادهی از قواعد اعتبارسنجی تعریف شده را در یک برنامهی ASP.NET Core بررسی میکنیم.
برای مطالعهی بیشتر
- «FluentValidation #1»
مروری بر روشهای موجود
همواره روشهای مختلفی برای پیاده سازی یک ایده در دنیای نرم افزار وجود دارد که هر روش را میتوان بر حسب نیاز مورد استفاده قرار داد. در برنامههای مبتنی بر WPF معمولا از دو روش عمده برای این منظور استفاده میشود:
1-استفاده از فایلهای resx
در این روش که برای Win App نیز استفاده میشود، اطلاعات مورد نیاز برای هر زبان به شکل جدول هایی دارای کلید و مقدار در داخل یک فایل .resx نگهداری میشود و در زمان اجرای برنامه بر اساس انتخاب کاربر اطلاعات زبان مورد نظر از داخل فایل resx خوانده شده و نمایش داده میشود. یکی از ضعف هایی که این روش در عین ساده بودن دارد این است که همه اطلاعات مورد نیاز داخل assembly اصلی برنامه قرار میگیرد و امکان افزودن زبانهای جدید بدون تغییر دادن برنامه اصلی ممکن نخواهد بود.
2-استفاده از فایلهای csv که به فایلهای dll تبدیل میشوند
در این روش با استفاده از ابزارهای موجود در کامپایلر WPF برای هر کنترل یک property به نام Uid ایجاد شده و مقدار دهی میشود. سپس با ابزار دیگری ( که جزو ابزارهای کامپایلر محسوب نمیشود ) از فایل csproj پروژه یک خروجی اکسل با فرمت csv ایجاد میشود که شامل Uidهای کنترلها و مقادیر آنها است. پس از ترجمه متون مورد نظر به زبان مقصد با کمک ابزار دیگری فایل اکسل مورد نظر به یک net assembly تبدیل میشود و داخل پوشه ای با نام culture استاندارد ذخیره میشود. ( مثلا برای زبان فارسی نام پوشه fa-IR خواهد بود ). زمانی که برنامه اجرا میشود بر اساس culture ای که در سیستم عامل انتخاب شده است و در صورتی که برای آن culture فایل dll ای موجود باشد، زبان مربوط به آن culture را load خواهد کرد. با وجود این که این روش مشکل روش قبلی را ندارد و بیشتر با ویژگیهای WPF سازگار است اما پروسه ای طولانی برای انجام کارها دارد و به ازای هر تغییری باید کل مراحل هر بار تکرار شوند. همچنین مشکلاتی در نمایش برخی زبانها ( از جمله فارسی ) در این روش مشاهده شده است.
روش سوم!
روش سوم اما کاملا بر پایه WPF و در اصطلاح WPF-Native میباشد. ایده از آنجا ناشی شده است که برای ایجاد skin در برنامههای WPF استفاده میشود. در ایجاد برنامههای Skin-Based به این شیوه عمل میشود که skinهای مورد نظر به صورت style هایی در داخل ResourceDictionary ها قرار میگیرند. سپس آن ResourceDictionary به شکل dll کامپایل میشود. در برنامه اصلی نیز همه کنترلها style هایشان را به شکل dynamic resource از داخل یک ResourceDictionary مشخص شده load میکنند. حال کافی است برای تغییر skin فعلی، ResourceDictionary مورد نظر از dll مشخص load شود و ResourceDictionary ای که در حال حاضر در برنامه از آن استفاده میشود با ResourceDictionary ای که load شده جایگزین شود. کنترلها مقادیر جدید را از ResourceDictionary جدید به شکل کاملا خودکار دریافت خواهند کرد.
به سادگی میتوان از این روش برای تغییر زبان برنامه نیز استفاده کرد با این تفاوت که این بار، به جای Style ها، Stringهای زبانهای مختلف را درون resourceها نگهداری خواهیم کرد.
یک مثال ساده
در این قسمت نحوه پیاده سازی این روش با ایجاد یک نمونه برنامه ساده که دارای دو زبان انگلیسی و فارسی خواهد بود آموزش داده میشود.
ابتدا یک پروژه WPF Application در Visual Studio 2010 ایجاد کنید. در MainWindow سه کنترل Button قرار دهید و یک ComboBox که قرار است زبانهای موجود را نمایش دهد و با انتخاب یک زبان، نوشتههای درون Buttonها متناسب با آن تغییر خواهند کرد.
توجه داشته باشید که برای Buttonها نباید به صورت مستقیم مقداری به Content شان داده شود. زیرا مقدار مورد نظر از داخل ResourceDictionary که خواهیم ساخت به شکل dynamic گرفته خواهد شد. پس در این مرحله یک ResourceDictionary به پروژه اضافه کرده و در آن resource هایی به شکل string ایجاد میکنیم. هر resource دارای یک Key میباشد که بر اساس آن، Button مورد نظر، مقدار آن Resource را load خواهد کرد. فایل ResourceDictionary را
Culture_en-US.xaml نامگذاری کنید و مقادیر مورد نظر را به آن اضافه نمایید.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"> <system:String x:Key="button1">Hello!</system:String> <system:String x:Key="button2">How Are You?</system:String> <system:String x:Key="button3">Are You OK?</system:String> </ResourceDictionary>
دقت کنید که namespace ای که کلاس string در آن قرار دارد به فایل xaml اضافه شده است و پیشوند system به آن نسبت داده شده است.
با افزودن یک ResourceDictionary به پروژه، آن ResourceDictionary به MergedDictionary کلاس App اضافه میشود. بنابراین فایل App.xaml به شکل زیر خواهد بود:
<Application x:Class="BeRMOoDA.WPF.LocalizationSample.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Culture_en-US.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>
برای اینکه بتوانیم محتوای Buttonهای موجود را به صورت داینامیک و در زمان اجرای برنامه، از داخل Resourceها بگیریم، از DynamicResource استفاده میکنیم.
<Button Content="{DynamicResource ResourceKey=button1}" /> <Button Content="{DynamicResource ResourceKey=button2}" /> <Button Content="{DynamicResource ResourceKey=button3}" />
بسیار خوب! اکنون باید شروع به ایجاد یک ResourceDictionary برای زبان فارسی کنیم و آن را به صورت یک فایل dll کامپایل نماییم.
برای این کار یک پروژه جدید در قسمت WPF از نوع User control ایجاد میکنیم و نام آن را Culture_fa-IR_Farsi قرار میدهیم. لطفا شیوه نامگذاری را رعایت کنید چرا که در ادامه به آن نیاز خواهیم داشت.
پس از ایجاد پروژه فایل UserControl1.xaml را از پروژه حذف کنید و یک ResourceDictionary با نام Culture_fa-IR.xaml اضافه کنید. محتوای آن را پاک کنید و محتوای فایل Culture_en-US.xaml را از پروژه قبلی به صورت کامل در فایل جدید کپی کنید. دو فایل باید ساختار کاملا یکسانی از نظر key برای Resourceهای موجود داشته باشند. حالا زمان ترجمه فرا رسیده است! رشتههای دلخواه را ترجمه کنید و پروژه را build نمایید.
پس از ترجمه فایل Culture_fa-IR.xaml به شکل زیر خواهد بود:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Culture_fa-IR_Farsi.xaml"/> </ResourceDictionary.MergedDictionaries> <system:String x:Key="button1">سلام!</system:String> <system:String x:Key="button2">حالت چطوره؟</system:String> <system:String x:Key="button3">خوبی؟</system:String> </ResourceDictionary>
در ادامه میخواهیم راهکاری ارئه دهیم تا بتوان فایلهای dll مربوط به زبانها را در زمان اجرای برنامه اصلی، load کرده و نام زبانها را در داخل ComboBox ای که داریم نشان دهیم. سپس با انتخاب هر زبان در ComboBox، محتوای Buttonها بر اساس زبان انتخاب شده تغییر کند.
برای سهولت کار، نام فایلها را به گونه ای انتخاب کردیم که بتوانیم سادهتر به این هدف برسیم. نام هر فایل از سه بخش تشکیل شده است:
پوشه ای با نام Languages در کنار فایل اجرایی برنامه اصلی ایجاد کنید و فایل Culture_fa-IR_Farsi.dll را درون آن کپی کنید. تصمیم داریم همه dllهای مربوط به زبانها را داخل این پوشه قرار دهیم تا مدیریت آنها سادهتر شود.
برای مدیریت بهتر فایلهای مربوط به زبانها یک کلاس با نام CultureAssemblyModel خواهیم ساخت که هر instance از آن نشانگر یک فایل زبان خواهد بود. یک کلاس با این نام به پروژه اضافه کنید و propertyهای زیر را در آن تعریف نمایید:
public class CultureAssemblyModel { //the text will be displayed to user as language name (like Farsi) public string DisplayText { get; set; } //name of .dll file (like Culture_fa-IR_Farsi.dll) public string Name { get; set; } //standar notation of this culture (like fa-IR) public string Culture { get; set; } //name of resource dictionary file inside the loaded .dll (like Culture_fa-IR.xaml) public string XamlFileName { get; set; } }
برای خواندن لیست cultureهای موجود، لیستی از CultureAssmeblyModelها ایجاد کرده و با استفاده از متد LoadCultureAssmeblies، آن را پر میکنیم.
//will keep information about loaded assemblies public List<CultureAssemblyModel> CultureAssemblies { get; set; } //loads assmeblies in languages folder and adds their info to list void LoadCultureAssemblies() { //we should be sure that list is empty before adding info (do u want to add some cultures more than one? of course u dont!) CultureAssemblies.Clear(); //creating a directory represents applications directory\languages DirectoryInfo dir = new DirectoryInfo(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\languages"); //getting all .dll files in the language folder and its sub dirs. (who knows? maybe someone keeps each culture file in a seperate folder!) var assemblies = dir.GetFiles("*.dll", SearchOption.AllDirectories); //for each found .dll we will create a model and set its properties and then add to list for (int i = 0; i < assemblies.Count(); i++) {
string name = assemblies[i].Name;
CultureAssemblyModel model = new CultureAssemblyModel() { DisplayText = name.Split('.', '_')[2], Culture = name.Split('.', '_')[1], Name = name , XamlFileName =name.Substring(0, name.LastIndexOf(".")) + ".xaml" }; CultureAssemblies.Add(model); } }
comboboxLanguages.ItemsSource = CultureAssemblies;
<ComboBox HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" MinWidth="100" Name="comboboxLanguages"> <ComboBox.ItemTemplate> <DataTemplate> <Label Content="{Binding DisplayText}"/> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox>
در مرحله بعد، قرار است متدی بنویسیم که اطلاعات زبان انتخاب شده را گرفته و با جابجایی ResourceDictionary ها، زبان برنامه را تغییر دهیم.
متدی با نام LoadCulture در کلاس App ایجاد میکنیم که یک CultureAssemblyModel به عنوان ورودی دریافت کرده و ResourceDictionary داخل آن را load میکند و آن را با ResourceDictionary فعلی موجود در App.xaml جابجا مینماید.
با این کار، Button هایی که قبلا مقدار Content خود را از Resourceهای موجود دریافت میکردند، اکنون از Resourceهای جابجا شده خواهند گرفت و به این ترتیب زبان انتخاب شده بر روی برنامه اعمال میشود.
//loads selected culture public void LoadCulture(CultureAssemblyModel culture) { //creating a FileInfo object represents .dll file of selected cultur FileInfo assemblyFile = new FileInfo("languages\\" + culture.Name); //loading .dll into memory as a .net assembly var assembly = Assembly.LoadFile(assemblyFile.FullName); //getting .dll file name var assemblyName = assemblyFile.Name.Substring(0, assemblyFile.Name.LastIndexOf(".")); //creating string represents structure of a pack uri (something like this: /{myassemblyname;component/myresourcefile.xaml} string packUri = string.Format(@"/{0};component/{1}", assemblyName, culture.XamlFileName); //creating a pack uri Uri uri = new Uri(packUri, UriKind.Relative); //now we have created a pack uri that represents a resource object in loaded assembly //and its time to load that as a resource dictionary (do u remember that we had resource dictionary in culture assemblies? don't u?) var dic = Application.LoadComponent(uri) as ResourceDictionary; dic.Source = uri; //here we will remove current merged dictionaries in our resource dictionary and add recently-loaded resource dictionary as e merged dictionary var mergedDics = this.Resources.MergedDictionaries; if (mergedDics.Count > 0) mergedDics.Clear(); mergedDics.Add(dic); }
void comboboxLanguages_SelectionChanged(object sender, SelectionChangedEventArgs e) { var selectedCulture = (CultureAssemblyModel)comboboxLanguages.SelectedItem; App app = Application.Current as App; app.LoadCulture(selectedCulture); }
کار انجام شد!
از مزیتهای این روش میتوان به WPF-Native بودن، سادگی در پیاده سازی، قابلیت load کردن هر زبان جدیدی در زمان اجرا بدون نیاز به کوچکترین تغییر در برنامه اصلی و همچنین پشتیبانی کامل از نمایش زبانهای مختلف از جمله فارسی اشاره کرد.
report.DataSources.Add(new ReportDataSource("Header", "از نوع لیست باشد"));