What’s new for performance in .NET Core and ASP.NET Core 3.0 – Ben Adams
One of the biggest advantages of using .NET Core (besides cross-platform support) is the drastic improvements in performance. Because the .NET Core team was able to make minor breaking changes in the runtime and Base Class Library (BCL), lots of stuff was implemented much more efficiently. In this session Ben will dive into the performance improvements in .NET Core in the 3.0 release: runtime changes, JIT changes, intrinsics and a deep dive into some of the improvements making it the best release yet!
ASP.NET Core, Angular2 with Webpack and Visual Studio
Webpack setup for Angular 2, Typescript, and Materialize
Using webpack to create bundles
Angular2 with NPM and Webpack
Choosing a Module Bundler for ng2
Automated Angular 2 Conventions with Webpack
Webpack SystemJS Registration Plugin
کار با اسکنر در برنامه های تحت وب (قسمت اول)
کتابخانهی « DNTScanner.Core » امکان کار با اسکنر را در برنامههای NET 4x. و همچنین NET Core. ویندوزی میسر میکند.
مثالها
- روش استفاده از آن در برنامههای کنسول
- روش استفادهی از آن در یک برنامهی وب ASP.NET Core که قسمت اسکنر آن به صورت یک کلاینت کنسول تهیه شدهاست و ارتباط بین این دو از طریق SignalR.Core برقرار میشود.
ویژگی های جدید dotNET Core 2.1
Earlier this week, Microsoft published the roadmap for .NET Core 2.1, ASP.NET Core 2.1 and EF Core 2.1, expected to be out in the first quarter of 2018. The team also talked about several new features with this new release. This release is more of a feedback-oriented release based on .NET Core 2.0 release. The.NET Core 2.0 is a huge success and already more than half a million developers are now using .NET Core 2.0. All thanks to .NET Standard 2.0 release . In this post find out about the new features of .NET Core 2.1.
الف) مقیاس پذیری سمت سرور
در اعمال سمت سرور متداول، تردهای متعددی جهت پردازش درخواستهای کلاینتها تدارک دیده میشوند. هر زمانیکه یکی از این تردها، یک عملیات blocking را انجام میدهد (مانند دسترسی به شبکه یا اعمال I/O)، ترد مرتبط با آن تا پایان کار این عملیات معطل خواهد شد. با بالا رفتن تعداد کاربران یک برنامه و در نتیجه بیشتر شدن تعداد درخواستهایی که سرور باید پردازش کند، تعداد تردهای معطل مانده نیز به همین ترتیب بیشتر خواهند شد. مشکل اصلی اینجا است که نمونه سازی تردها بسیار هزینه بر است (با اختصاص 1MB of virtual memory space) و منابع سرور محدود. با زیاد شدن تعداد تردهای معطل اعمال I/O یا شبکه، سرور مجبور خواهد شد بجای استفاده مجدد از تردهای موجود، تردهای جدیدی را ایجاد کند. همین مساله سبب بالا رفتن بیش از حد مصرف منابع و حافظه برنامه میگردد. یکی از روشهای رفع این مشکل بدون نیاز به بهبودهای سخت افزاری، تبدیل اعمال blocking نامبرده شده به نمونههای non-blocking است. به این ترتیب ترد پردازش کنندهی این اعمال Async بلافاصله آزاد شده و سرور میتواند از آن جهت پردازش درخواست دیگری استفاده کند؛ بجای اینکه ترد جدیدی را وهله سازی نماید.
ب) بالا بردن پاسخ دهی کلاینتها
کلاینتها نیز اگر مدام درخواستهای blocking را به سرور جهت دریافت پاسخی ارسال کنند، به زودی به یک رابط کاربری غیرپاسخگو خواهند رسید. برای رفع این مشکل نیز میتوان از توانمندیهای Async دات نت 4.5 جهت آزاد سازی ترد اصلی برنامه یا همان ترد UI استفاده کرد.
و ... تمام اینها یک شرط را دارند. نیاز است یک چنین API خاصی که اعمال Async واقعی را پشتیبانی میکنند، فراهم شده باشد. بنابراین صرفا وجود متد Task.Run، به معنای اجرای واقعی Async یک متد خاص نیست. برای این منظور ADO.NET 4.5 به همراه متدهای Async ویژه کار با بانکهای اطلاعاتی است و پس از آن Entity framework 6 از این زیر ساخت استفاده کردهاست که در ادامه جزئیات آنرا بررسی خواهیم کرد.
پیشنیازها
برای کار با امکانات جدید Async موجود در EF 6 نیاز است از VS 2012 به بعد که به همراه کامپایلری است که واژههای کلیدی async و await را پشتیبانی میکند و همچنین دات نت 4.5 استفاده کرد. چون ADO.NET 4.5 اعمال async واقعی را پشتیبانی میکند، دات نت 4 در اینجا قابل استفاده نخواهد بود.
متدهای الحاقی جدید Async در EF 6.x
جهت متدهای الحاقی متداول EF مانند ToList، Max، Min و غیره، نمونههای Async آنها نیز اضافه شدهاند:
QueryableExtensions: AllAsync AnyAsync AverageAsync ContainsAsync CountAsync FirstAsync FirstOrDefaultAsync ForEachAsync LoadAsync LongCountAsync MaxAsync MinAsync SingleAsync SingleOrDefaultAsync SumAsync ToArrayAsync ToDictionaryAsync ToListAsync DbSet: FindAsync DbContext: SaveChangesAsync Database: ExecuteSqlCommandAsync
چند مثال
فرض کنید، مدلهای برنامه، رابطهی one-to-many ذیل را بین یک کاربر و مقالات او دارند:
public class User { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<BlogPost> BlogPosts { get; set; } } public class BlogPost { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } [ForeignKey("UserId")] public virtual User User { get; set; } public int UserId { get; set; } }
public class MyContext : DbContext { public DbSet<User> Users { get; set; } public DbSet<BlogPost> BlogPosts { get; set; } public MyContext() : base("Connection1") { this.Database.Log = sql => Console.Write(sql); } }
private async Task<User> addUserAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var context = new MyContext()) { var user = context.Users.Add(new User { Name = "Vahid" }); context.BlogPosts.Add(new BlogPost { Content = "Test", Title = "Test", User = user }); await context.SaveChangesAsync(cancellationToken); return user; } }
چند نکته جهت یادآوری مباحث Async
- به امضای متد واژهی کلیدی async اضافه شدهاست، زیرا در بدنهی آن از کلمهی کلیدی await استفاده کردهایم (لازم و ملزوم هستند).
- به انتهای نام متد، کلمهی Async اضافه شدهاست. این مورد ضروری نیست؛ اما به یک استاندارد و قرارداد تبدیل شدهاست.
- مدل Async دات نت 4.5 مبتنی بر Taskها است. به همین جهت اینبار خروجیهای توابع نیاز است از نوع Task باشند و آرگومان جنریک آنها، بیانگر نوع مقداری که باز میگردانند.
- تمام متدهای الحاقی جدیدی که نامبرده شدند، دارای پارامتر اختیاری لغو عملیات نیز هستند. این مورد را با مقدار دهی cancellationToken در کدهای فوق ملاحظه میکنید.
نمونهای از نحوهی مقدار دهی این پارامتر در ASP.NET MVC به صورت زیر میتواند باشد:
[AsyncTimeout(8000)] public async Task<ActionResult> Index(CancellationToken cancellationToken)
- برای اجرا و دریافت نتیجهی متدهای Async دار EF، نیاز است از واژهی کلیدی await استفاده گردد.
استفاده کننده نیز میتواند متد addUserAsync را به صورت زیر فراخوانی کند:
var user = await addUserAsync(); Console.WriteLine("user id: {0}", user.Id);
شبیه به همین اعمال را نیز جهت به روز رسانی و یا حذف اطلاعات خواهیم داشت:
private async Task<User> updateAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var context = new MyContext()) { var user1 = await context.Users.FindAsync(cancellationToken, 1); if (user1 != null) user1.Name = "Vahid N."; await context.SaveChangesAsync(cancellationToken); return user1; } } private async Task<int> deleteAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var context = new MyContext()) { var user1 = await context.Users.FindAsync(cancellationToken, 1); if (user1 != null) context.Users.Remove(user1); return await context.SaveChangesAsync(cancellationToken); } }
کدهای Async تقلبی!
به قطعه کد ذیل دقت کنید:
public async Task<List<TEntity>> GetAllAsync() { return await Task.Run(() => _tEntities.ToList()); }
به این نوع متدها که از Task.Run برای فراخوانی متدهای همزمان قدیمی مانند ToList جهت Async جلوه دادن آنها استفاده میشود، کدهای Async تقلبی میگویند! این عملیات هر چند در یک ترد دیگر انجام میشود اما هم سربار ایجاد یک ترد جدید را به همراه دارد و هم عملیات ToList آن کاملا blocking است.
معادل صحیح Async واقعی این عملیات را در ذیل مشاهده میکنید:
private async Task<List<User>> getUsersAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var context = new MyContext()) { return await context.Users.ToListAsync(cancellationToken); } }
برای مثال پشت صحنهی متد الحاقی SaveChangesAsync به یک چنین متدی ختم میشود:
internal override async Task<long> ExecuteAsync( //... rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); //...
و یا برای شبیه سازی ToListAsync با ADO.NET 4.5 و استفاده از متدهای Async واقعی آن، به یک چنین کدهایی نیاز است:
var connectionString = "........"; var sql = @"......""; var users = new List<User>(); using (var cnx = new SqlConnection(connectionString)) { using (var cmd = new SqlCommand(sql, cnx)) { await cnx.OpenAsync(); using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { while (await reader.ReadAsync()) { var user = new User { Id = reader.GetInt32(0), Name = reader.GetString(1), }; users.Add(user); } } } }
محدودیت پردازش موازی اعمال در EF
در متد ذیل، دو Task غیرهمزمان تعریف شدهاند و سپس با await Task.WhenAll درخواست اجرای همزمان و موازی آنها را کردهایم:
// multiple operations private static async Task loadAllAsync(CancellationToken cancellationToken = default(CancellationToken)) { using (var context = new MyContext()) { var task1 = context.Users.ToListAsync(cancellationToken); var task2 = context.BlogPosts.ToListAsync(cancellationToken); await Task.WhenAll(task1, task2); // use task1.Result } }
An unhandled exception of type 'System.NotSupportedException' occurred in mscorlib.dll Additional information: A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
User authentication in Asp.NET MVC using Cassandra and HectorSharp
var container = new Container(x => { x.Scan(scanner => { scanner.AssemblyContainingType<IOrderHandler>(); // connects `IAccounting` to `Accounting` and `ISales` to `Sales` automatically. scanner.WithDefaultConventions(); }); });
builder.RegisterAssemblyTypes(myAssembly) .Where(t => t.IsAssignableTo<IMyInterface>()) .AsImplementedInterfaces();
دریافت و نصب کتابخانهی کمکی Scrutor
کتابخانهی کمکی Scrutor سورس باز بوده و بستهی NuGet آن توسط یکی از دستورات زیر به پروژه افزوده میشود:
> Install-Package Scrutor > dotnet add package Scrutor
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="Scrutor" Version="3.0.2" /> </ItemGroup> </Project>
ثبت و معرفی سادهتر سرویسها بر اساس قواعد نامگذاری آنها توسط Scrutor
فرض کنید تعدادی سرویس را به صورت زیر تعریف کردهاید:
namespace CoreIocServices { public interface IFoo { void Run(); } public class Foo : IFoo { public void Run() { throw new System.NotImplementedException(); } } public interface IBar { void Add(); } public class Bar : IBar { public void Add() { throw new System.NotImplementedException(); } } public interface IBaz { void Stop(); } public class Baz : IBaz { public void Stop() { throw new System.NotImplementedException(); } } }
services.AddScoped<IFoo, Foo>(); services.AddScoped<IBar, Bar>(); services.AddScoped<IBaz, Baz>();
در اینجا در حین تعریف سرویسهای فوق این روش نامگذاری رعایت شدهاست: هر اینترفیس، نامش یک I بیشتر از نام کلاس مشتق شدهی از آن دارد؛ مانند اینترفیس IFoo و کلاس Foo. کتابخانهی StructureMap که در ابتدای بحث معرفی شد، کار اسکن و اتصال یک چنین سرویسهایی را با تعریف scanner.WithDefaultConventions انجام میدهد. معادل آن با Scrutor به صورت زیر است:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.Scan(scan => //scan.FromCallingAssembly() scan.FromAssemblyOf<IFoo>() .AddClasses() .AsMatchingInterface() .WithScopedLifetime());
- scan.FromAssemblyOf کار اسکن اسمبلی را انجام میدهد که نوع IFoo در آن قرار دارد. اگر از scan.FromCallingAssembly استفاده کنیم، به این معنا است که کار اسکن را دقیقا از همین اسمبلی فراخوان کدهای جاری، شروع کن. اما چون IFoo تعریف شده، در یک پروژه و اسمبلی دیگر قرار دارد، به همین جهت نیاز به ذکر صریح اسمبلی آن نیز هست.
- AddClasses یعنی تمام کلاسهای public, non-abstract را به لیست services اضافه کن.
- AsMatchingInterface یعنی بر اساس قرارداد نامگذاری IClassName و ClassName، اتصالات سرویسها را انجام بده.
بجای آن میتوان از AsImplementedInterfaces نیز استفاده کرد. این حالت برای زمانی مناسب است که یک کلاس، چندین اینترفیس را پیاده سازی کند (مثلا کلاس TestService اینترفیسهای ITestService و IService را پیاده سازی کرده باشد) و علاقمند باشید به ازای هر اینترفیس، یکبار سرویس آن نیز ثبت شود؛ کاری مانند تنظیمات زیر:
services.AddScoped<ITestService, TestService>(); services.AddScoped<IService, TestService>();
- WithScopedLifetime نیز طول عمر این سرویسهای اضافه شده را مشخص میکند. در اینجا میتوان WithTransientLifetime و WithSingletonLifetime را نیز ذکر کرد.
بنابراین همانطور که ملاحظه میکنید، هنوز هم همان سیستم Microsoft.Extensions.DependencyInjection برقرار است؛ اما با وجود متد الحاقی جدید Scan، کار تعاریف سرویسهای برنامه به شدت ساده میشود.
کار با وهلههای کلاسهای سرویسها بجای اینترفیسهای آن توسط Scrutor
میخواهیم مثال سوم قسمت ششم «چگونه بجای اینترفیسها، یک وهله از کلاسی مشخص را از سیستم تزریق وابستگیها درخواست کنیم؟» را توسط Scrutor پیاده سازی کنیم:
namespace CoreIocServices { public interface IService { } public class Service1 : IService { } public class Service2 : IService { } public class Service : IService { } }
services.AddTransient<Service1>(); services.AddTransient<Service2>(); services.AddTransient<Service>();
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { services.Scan(scan => //scan.FromCallingAssembly() scan.FromAssemblyOf<IService>() .AddClasses() .AsSelf() .WithTransientLifetime());
services.Scan(scan => scan.AddTypes(new[] { typeof(Service1), typeof(Service2) }) .AsSelf() .WithTransientLifetime());
AsSelf: معادل ()<services.AddTransient<TestService است. در این حالت کلاسهایی که اینترفیسی را پیاده سازی نمیکنند و یا در کل مایل هستید که از طریق تزریق وابستگیها در دسترس باشند، میتوان توسط متد AsSelf به سیستم معرفی کرد.
AsSelfWithInterfaces: معادل تنظیمات زیر است:
services.AddSingleton<TestService>(); services.AddSingleton<ITestService>(x => x.GetRequiredService<TestService>()); services.AddSingleton<IService>(x => x.GetRequiredService<TestService>());
روشهای متفاوت اسکن اسمبلیها در Scrutor
Scrutor به همراه روشهای متعددی برای تعریف اسمبلی یا اسمبلیهایی است که باید اسکن شوند و نمونهای از آنرا با FromAssemblyOf بررسی کردیم:
services.Scan(scan => //scan.FromCallingAssembly() scan.FromAssemblyOf<IService>()
الف) FromAssemblyOf<>, FromAssembliesOf : اسمبلی یا اسمبلیهایی که نوع یا نوعهای تعیین شده را به همراه دارند، اسکن میکند.
ب) FromCallingAssembly, FromExecutingAssembly, FromEntryAssembly کار اسکن اسمبلیهای فراخوان، اسمبلی که هم اکنون در حال اجرا است و اسمبلی آغازین برنامه را انجام میدهند.
ج) FromAssemblyDependencies: تمام اسمبلیهایی را که وابستهی به اسمبلی معرفی شدهی به آن هستند، اسکن میکند.
د) FromApplicationDependencies, FromDependencyContext: تمام اسمبلیهایی را که توسط برنامه، ارجاعی به آنها وجود دارند، اسکن میکند.
انتخاب دقیقتر کلاسها و سرویسهای مدنظر توسط Scrutor
شاید عملکرد کلی متد AddClasses مدنظر شما نباشد و نیاز به انتخاب دقیقتری از سرویسهای اسکن شده را داشته باشید؛ برای این مورد نیز Scrutor روشهای زیر را ارائه میدهد. برای مثال خود کلاس AddClasses دارای overloadهای زیر نیز هست:
public interface IImplementationTypeSelector : IAssemblySelector, IFluentInterface { IServiceTypeSelector AddClasses(); IServiceTypeSelector AddClasses(bool publicOnly); IServiceTypeSelector AddClasses(Action<IImplementationTypeFilter> action); IServiceTypeSelector AddClasses(Action<IImplementationTypeFilter> action, bool publicOnly); }
services.Scan(scan => scan .FromAssemblyOf<IService>() .AddClasses(classes => classes.AssignableTo<IService>()) // .AddClasses(classes => classes.InNamespaces("MyApp")) // .AddClasses(classes => classes.Where(type => type.Name.EndsWith("Repository")) .AsImplementedInterfaces() .WithTransientLifetime());
مدیریت جایگزینی سرویسها توسط Scrutor
یکی از مزیتهای طراحی یک برنامه با درنظر گرفتن الگوی تزریق وابستگیها، امکان جایگزین کردن سرویسهای پیشفرض آن با سرویسهای دیگری است. فرض کنید کتابخانهای ارائه شده و از الگوریتم هش کردن X استفاده کردهاست؛ اما شما علاقمندید تا از الگوریتم Y بجای آن استفاده کنید. اگر این کتابخانه وهلهی الگوریتم هش کردن را از طریق تزریق وابستگیها تامین کرده باشد، فقط کافی است در ابتدای معرفی تنظیمات تزریق وابستگیهای آن، سرویس الگوریتم هش کردن موجود را با نمونهی خاص خودتان جایگزین کنید.
اکنون فرض کنید پیش از استفادهی از Scrutor، تعدادی سرویس را به روش متداولی ثبت و معرفی کردهاید:
services.AddTransient<ITransientService, TransientService>(); services.AddScoped<IScopedService, ScopedService>();
public class TransientService : IFooService {} public class AnotherService : IScopedService {}
services.Scan(scan => scan.FromAssemblyOf<IFoo>() .AddClasses() .UsingRegistrationStrategy(RegistrationStrategy.Skip) .AsMatchingInterface() .WithScopedLifetime());
namespace Scrutor { public abstract class RegistrationStrategy { public static readonly RegistrationStrategy Skip; public static readonly RegistrationStrategy Append; protected RegistrationStrategy(); public static RegistrationStrategy Replace(); public static RegistrationStrategy Replace(ReplacementBehavior behavior); public abstract void Apply(IServiceCollection services, ServiceDescriptor descriptor); } }
- حالت Skip آن، سرویسی را تکراری ثبت نمیکند. یعنی اگر سرویسی پیشتر در مجموعهی IServiceCollection موجود بود، مجددا آنرا ثبت نمیکند.
سپس نوبت به متدهای Replace میرسد که یک چنین پارامتری را قبول میکنند:
namespace Scrutor { [Flags] public enum ReplacementBehavior { Default = 0, ServiceType = 1, ImplementationType = 2, All = 3 } }
- در حالت استفادهی از Replace(ReplacementBehavior.ImplementationType)، اگر پیاده سازی کلاسی پیشتر در لیست IServiceCollection ثبت شده باشد، آنرا حذف کرده و سپس نمونهی جدید را ثبت میکند (ثبت سرویس صرفا بر اساس نام کلاس آن).
- حالت Replace(ReplacementBehavior.All) هر دو حالت قبل را با هم شامل میشود.
امکان ترکیب چندین استراتژی جستجو با هم توسط Scrutor
در یک برنامهی واقعی غیرممکن است که بخواهید تمام کلاسها را با یک طول عمر، اسکن و ثبت کنید. برای این منظور میتوان از قابلیت فیلتر کردن کلاسها که در مورد آن بحث شد و همچنین امکان ترکیب زنجیر وار حالتهای مختلف اسکن، استفاده کرد:
services.Scan(scan => scan .FromAssemblyOf<CombinedService>() .AddClasses(classes => classes.AssignableTo<ICombinedService>()) // Filter classes .AsSelfWithInterfaces() .WithSingletonLifetime() .AddClasses(x=> x.AssignableTo(typeof(IOpenGeneric<>))) // Can close generic types .AsMatchingInterface() .AddClasses(x=> x.InNamespaceOf<MyClass>()) .UsingRegistrationStrategy(RegistrationStrategy.Replace()) // Defaults to ReplacementBehavior.ServiceType .AsMatchingInterface() .WithScopedLifetime() .FromAssemblyOf<DatabaseContext>() // Can load from multiple assemblies within one Scan() .AddClasses() .AsImplementedInterfaces() );