Secure password storage in the Windows Credential Store
Multi-factor authentication support for Visual Studio Team Services.
Two-factor authentication support for GitHub
Personal Access Token generation and usage support for Visual Studio Team Services and GitHub
Non-interactive mode support for Visual Studio Team Services backed by Azure Directory
Optional settings for build agent optimization
LocalDB چیست؟
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
<parameters>
<parameter value="Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=True;" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
<connectionStrings> <clear/> <add name="MyContext" connectionString="Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=True; AttachDBFilename=MyDatabase.mdf" providerName="System.Data.SqlClient" /> </connectionStrings>
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run();
البته این روش شاید برای برنامههای کوچک جالب بهنظر برسد، اما برای برنامههای بزرگتر میتوان به گزینههای زیر نیز توجه داشت.
گزینهی ارتقاء 1: هیچ کاری نکنید!
اگر میخواهید برنامههای NET 5. خود را به دات نت 6 ارتقاء دهید و نگران هستید که با دو فایل قدیمی Program.cs و Startup.cs آن باید چکار کنیم، پاسخ سادهی آن این است: هیچ کاری نکنید!
شیوهی قدیمی مبتنی بر generic host و Startup، کاملا در دات نت 6 پشتیبانی میشوند؛ از این جهت که WebApplication جدید دات نت 6، صرفا یک محصور کنندهی پیچیدگیهای generic host است. بنابراین برای ارتقاء پروژههای ASP.NET Core 5x به 6x، تنها کافی است فایل csproj خود را ویرایش کرده و TargetFramework آنرا به net6.0 تغییر دهید. پس از آن Program.cs و Stratup.cs قبلی شما بدون هیچ مشکلی و بدون نیاز به هیچ تغییری، با دات نت 6 هم کار خواهند کرد.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> </PropertyGroup> </Project>
گزینهی ارتقاء 2: از کلاس Startup قبلی خود استفادهی مجدد کنید
اما اگر واقعا علاقمندیم که از WebApplication جدید استفاده کنیم و همچنین نمیخواهیم همهچیز را داخل Program.cs قرار دهیم، چکار باید کرد؟
فرض کنید ساختار کلاس Startup موجود شما چنین شکلی را دارد که به همراه سازندهای است که IConfigurationRoot را دریافت میکند و همچنین دارای دو متد ConfigureServices و Configure نیز هست:
public class Startup { public Startup(IConfigurationRoot configuration) { Configuration = configuration; } public IConfigurationRoot Configuration { get; } public void ConfigureServices(IServiceCollection services) { // ... } public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime) { // ... } }
var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); startup.ConfigureServices(builder.Services); var app = builder.Build(); startup.Configure(app, app.Lifetime); app.Run();
گزینهی ارتقاء 3: استفاده از متدهای محلی در فایل Program.cs
اگر بخواهیم سیستم طراحی مینیمال دات نت 6 را رعایت کنیم، میتوان بجای ایجاد یک فایل Startup مجزا، متدهای تنظیمی آنرا به صورت تعدادی متد محلی، در همان فایل Program.cs قرار داد تا کمی ساختار پیدا کند(!)؛ چیزی شبیه به طراحی زیر که همان متدهای قبلی فایل Startup را در انتهای فایل Program.cs جاری به صورت متدهایی محلی، مشاهده میکنید؛ به همراه متدهای اختیاری دیگری برای تنظیم میانافزارها و یا endpoints:
var builder = WebApplication.CreateBuilder(args); ConfigureConfiguration(builder.configuration); ConfigureServices(builder.Services); var app = builder.Build(); ConfigureMiddleware(app, app.Services); ConfigureEndpoints(app, app.Services); app.Run(); void ConfigureConfiguration(ConfigurationManager configuration) => { } void ConfigureServices(IServiceCollection services) => { } void ConfigureMiddleware(IApplicationBuilder app, IServiceProvider services) => { } void ConfigureEndpoints(IEndpointRouteBuilder app, IServiceProvider services) => { }
مروری بر چند تجربهی کاری با SQLite
بله. تا این حد رو خوب جواب میده. البته مکانیزمهای کش کردن اطلاعات رو باید خودتون در نظر داشته باشید و پیاده سازی کنید.
ضمنا استفاده از SQL Server Compact Edition را هم مدنظر داشته باشید (اگر کار شما فقط ویندوزی است)؛ نسخهی جدید آن قرار است از Entity framework پشتیبانی کند و مشکلات استفاده چند کاربری را هم نخواهد داشت و برای ASP.NET بهینه سازی شده؛ هر چند برای SQLite هم اکنون پروایدر EF موجود است.
در بعضی از شرایط پیش رفته، ممکن است نمونه سازی از یک Implementation Type، نیاز به دخالت مستقیم ما را داشته باشد. Implementation Factory کنترل بیشتری بر چگونگی استفادهی از Implementation Typeها را به ما ارائه میدهد. در هنگام ثبت سرویسی که Implementation Factory را در اختیار ما قرار میدهد، ما یک Delegate را برای فراخوانی سرویس استفاده میکنیم. این Delegate مسئول ساخت یک نمونه از Service Type است. برای مثال وقتی از الگوهای builder یا factory برای ساخت یک شیء استفاده میکنید، شاید نیاز باشد که Implementation Factory را به صورت دستی پیاده سازی کنید. اولین قدم این است که کدتان را در صورت امکان چنان refactor کنید تا DI Container بتواند آن را به صورت خودکار بسازد؛ ولی اینکار همیشه ممکن نیست. برای مثال بعضی از برنامه نویسان ترجیح میدهند یک Config را مستقیما از IOptionMonitor بگیرند و بعد در هر جائیکه خواستند، بجای تزریق IOptionMonitor به سرویس، مستقیما از همان سرویس ثبت شده استفاده کنند:
services.AddSingleton<ILiteDbConfig>(sp =>sp.GetRequiredService<IOptionsMonitor<LiteDbConfig>>().CurrentValue);
یک کاربرد بالقوهی دیگر برای Implementation Factory ، استفاده از Composite Pattern است. هر چند Microsoft DI Container به صورت پیش فرض از Composite Pattern پشتیبانی نمیکند، ولی ما میتوانیم آنرا پیاده سازی کنیم. فرض کنید که قبلا به ازای انجام کاری، به کاربر یک ایمیل را میفرستادیم؛ ولی حالا مالک محصول میآید و میگوید که علاوه بر ایمیل، باید پیامک هم بفرستید و ما یا این سرویس پیامک را از قبل داریم و یا باید آن را بسازیم که فرض میکنیم از قبل آن را داریم. برای این کار ما یک اینترفیس کلیتر به نام INotificationService میسازیم که دو سرویس IEmailNotificationService و ISmsNotificaitonService از آن ارث بری میکنند:
public interface INotificationService { void SendMessage(string msg, int userId); }
public class CompositeNotificationService : INotificationService { private readonly IEnumerable<INotificationService> _notificationServices; public CompositeNotificationService(IEnumerable<INotificationService> notificationServices) { _notificationServices = notificationServices; } public void SendMessage(string msg, int userId) { foreach (var notificationServicei in _notificationServices) { notificationServicei.SendMessage(msg, userId); } } }
services.AddScoped<IEmailNotificationService, EmailNotificationService>(); services.AddScoped<ISMSNotificationService, SMSNotificationService>(); services.AddSingleton<INotificationService>(sp => new CompositeNotificationService( new INotificationService[] { sp.GetRequiredService<IEmailNotificationService>() , sp.GetRequiredService<ISMSNotificationService>() } ));
وهله سازی سفارشی
در مثال بعدی نشان میدهیم که چطور میتوانیم از Implementation Factory برای برگرداندن پیادهسازی سرویسهایی که Service Provider امکان ساخت
خودکار آنها را ندارد، استفاده کنیم. فرض کنید یک کلاس Account داریم که از IAccount ارث بری میکند
و برای ساخت آن باید از متدهای IAccountBuilder که فرآیند ساخت را انجام میدهند، استفاده کنیم. بنابراین امکان ساخت مستقیم یک
شیء از IAccount وجود ندارد. در این حالت بدین صورت عمل میکنیم:
services.AddTransient<IAccountBuilder, AccountBuilder>(); services.AddScoped<IAccount>(sp => { var builder = sp.GetService<IAccountBuilder>(); builder.WithAccountType(AccountType.Guest); return builder.Build(); });
ثبت یک نمونه برای چندین اینترفیس
ممکن است بنا به دلایلی مجبور باشیم یک implementation Type را برای چند سرویس (اینترفیس) به ثبت برسانیم. در این حالت
نمونهی شیء ساخته شده، توسط هر کدام از اینترفیسها قابل استفاده است. فرض کنید یک سرویس Greeting
داریم که پیش از این فقط اینترفیس IHomeGreetingService را پیاده سازی میکرد؛ ولی بنا به دلایلی تصمیم گرفتیم که سرویسی
جامعتر را با نیازمندیهای دیگری نیز تعریف کنیم و GreetingService آن را پیاده سازی کند:
public class GreetingService : IHomeGreetingService , IGreetingService
{ // code here }
احتمالا اولین چیزی که به ذهنمان میرسد این است:
services.TryAddSingleton<IHomeGreetingService, GreetingService>(); services.TryAddSingleton<IGreetingService, GreetingService>();
مشکل روش بالا این است که دو نمونه از GreetingService ساخته میشود و درون حافظه باقی میماند و در حقیقت برای هر اینترفیس، یک نوع جداگانه از GreetingService ثبت میشود؛ در حالیکه ما میخواهیم هر دو اینترفیس فقط از یک نمونه از شیء GreetingService استفاده کنند. دو راه حل برای این مشکل وجود دارد:
var greetingService = new GreetingService(Environment); services.TryAddSingleton<IHomeGreetingService>(greetingService); services.TryAddSingleton<IGreetingService>(greetingService);
در اینجا سازندهی کلاس GreetingService فقط به environment نیاز داشت که یکی از سرویسهای پایهی فریم ورک هست و در این مرحله در دسترس است. به صورت کلی مشکل روش بالا این است که ما مسئول نمونه سازی از سرویس GreetingService هستیم! اگر GreetingService برای ساخته شدن به سرویسها یا ورودی هایی نیاز داشته باشد که فقط در زمان اجرا در دسترس باشند، ما امکان نمونه سازی آنها را نداریم؛ علاوه بر این نمیتوان از روشهای بالای برای حالتهای Scoped یا Transient استفاده کرد.
روش بعدی همان روش استفاده
از Implementation Factory است که در ادامه آن را میبینید:
services.TryAddSingleton<GreetingService>(); services.TryAddSingleton<IHomeGreetingService>(sp => sp.GetRequiredService<GreetingService>()); services.TryAddSingleton<IGreetingService>(sp => sp.GetRequiredService<GreetingService>());
در این روش خود DI Container مسئول نمونه سازی از GreetingService است. علاوه بر این میتوان با استفاده از روش فوق از طول حیاتهای Scoped و Transient هم استفاده کرد؛ در حالیکه در روش قبلی این کار امکان پذیر نبود.
Open Generics Service
گاهی از اوقات میخواهید
سرویسهایی را ثبت کنید که از اینترفیسی جنریک ارث بری میکنند. هر نوع جنریک در
زمان اجرا، نوع مخصوص به خود را واکشی میکند. ثبت کردن دستی این سرویسها میتواند خسته کننده باشد. برای همین مایکروسافت در DI Container خود قابلیت ثبت و واکشی سرویسهای جنریک را نیز در
اختیار ما گذاشتهاست. بیایید نگاهی به سرویس ILogger<T> بیندازیم. این یک سرویس درونی فریمورک است و میتواند به ازای هر نوع، کارهای
مربوط به ثبت log را
انجام بدهد و در پروژهها معمولا از این اینترفیس برای ثبت لاگها در سطح
کنترلر و سرویسها استفاده میشود:
public interface ILogger<out TCategoryName> : ILogger { }
در حالت عادی اگر سرویسی
مشابه سرویس فوق را داشته باشیم، برای ثبت کردن هر سرویس با نوع جنریک اختصاصی آن،
مجبوریم به صورت دستی آن را درون DI Container ثبت کنیم؛ مثلا باید به این صورت عمل کنیم:
services.TryAddScoped<ILogger<HomeController>,Logger<HomeController>>();
services.TryAddScoped(typeof(ILogger<>) , typeof(Logger<>));
دسته بندی سرویسها درون متدهای مختلف و پاکسازی متد ConfigurationService
تا اینجای کار ما سرویسهای مختلفی را به روشهای مختلفی ثبت کردهایم. حتی در همین آموزش ساده، تعداد زیاد سرویسهای ثبت شده، باعث شلوغی و در هم ریختگی کدهای ما میشوند که خوانایی و در ادامه اشکال زدایی و توسعهی کدها را برای ما سختتر میکنند. سادهترین کار برای دسته بندی کدها، استفاده از متدهای private محلی یا استفاده از متدهای توسعهای(الحاقی) است که در اینجا مثالی از استفاده از متدهای توسعهای را آوردهام:namespace AspNetCoreDependencyInjection.Extensions { public static class DICRegisterationExetnsion { /// <summary> /// مثال ثبت برای اپن جنریت /// </summary> /// <param name="services"></param> public static void OpenGenericRegisterationExample(this IServiceCollection services) { services.TryAddScoped<ILogger<HomeController>, Logger<HomeController>>(); services.TryAddScoped(typeof(ILogger<>), typeof(Logger<>)); } /// <summary> /// ثبت تنظیمات به روشهای مختلف /// </summary> public static void RegisterConfiguration(this IServiceCollection services, IConfiguration configuration) { services.AddSingleton(services => new AppConfig { ApplicationName = configuration["ApplicationName"], GreetingMessage = configuration["GreetingMessage"], AllowedHosts = configuration["AllowedHosts"] }); services.AddSingleton(services => new AccountTypeBalanceConfig( new List<(AccountType, long)> { (AccountType.Guest , Convert.ToInt64 (configuration["AccountInitialBalance.Guest"]) ) , (AccountType.Silver , Convert.ToInt64 (configuration["AccountInitialBalance.Silver"]) ) , (AccountType.Gold , Convert.ToInt64 (configuration["AccountInitialBalance.Gold"]) ) , (AccountType.Platinum , Convert.ToInt64 (configuration["AccountInitialBalance.Platinum"]) ) , (AccountType.Titanium , Convert.ToInt64 (configuration["AccountInitialBalance.Titanium"]) ) , }) ); services.AddSingleton(services => new LiteDbConfig { ConnectionString = configuration["LiteDbConfig:ConnectionString"], }); services.Configure<UserOptionConfig>(configuration.GetSection("UserOptionConfig")); } } }
حالا در کلاس ConfigureServices ، درون متدStartup ، به این صورت از این متدهای توسعهای استفاده میکنیم:
services.RegisterConfiguration(this.Configuration); services.OpenGenericRegisterationExample();
میتوانید کد منبع این آموزش را در اینجا ببینید.
HttpContext و امکان دسترسی به Service Locatorها
در ASP.NET Core هر جائیکه دسترسی به HttpContext وجود داشته باشد، میتوان از الگوی Service Locator نیز توسط خاصیت HttpContext.RequestServices آن استفاده کرد. این خاصیت از نوع IServiceProvider قرار گرفتهی در فضای نام System است که در قسمت دوم آنرا بررسی کردیم. توسط این اینترفیس به متد object GetService(Type serviceType) دسترسی خواهیم یافت و برای کار با نگارشهای جنریک آن نیاز است فضای نام Microsoft.Extensions.DependencyInjection را مورد استفاده قرار داد:
using Microsoft.Extensions.DependencyInjection; namespace CoreIocSample02.Controllers { public class HomeController : Controller { public IActionResult Privacy() { var myDisposableService = this.HttpContext.RequestServices.GetRequiredService<IMyDisposableService>(); myDisposableService.Run(); return View(); } } }
استفاده از Service Locatorها در فیلترها
هرچند استفادهی از this.HttpContext.RequestServices در یک اکشن متد که کنترلر آن تزریق وابستگیهای در سازندهی کلاس را به صورت توکار پشتیبانی میکند، مزیت خاصی را به همراه ندارد و توصیه نمیشود، اما در انتهای قسمت قبل، امکان تزریق وابستگیهای متداول در فیلترها را نیز بررسی کردیم. زمانیکه کار تزریق وابستگیها در سازندهی یک فیلتر صورت میگیرد، دیگر نمیتوان ApiExceptionFilter را به نحو متداول [ApiExceptionFilter] فراخوانی کرد؛ چون پارامترهای سازندهی آن جزو ثوابت قابل کامپایل نیستند و کامپایلر سیشارپ چنین اجازهای را نمیدهد. به همین جهت مجبور به استفادهی از [ServiceFilter(typeof(ApiExceptionFilter))] برای معرفی یک چنین فیلترهایی هستیم. اما میتوان این وضعیت را با استفاده از الگوی Service Locator بهبود بخشید. اینبار بجای تعریف وابستگیها در سازندهی یک فیلتر:
public class ApiExceptionFilter : ExceptionFilterAttribute { private ILogger<ApiExceptionFilter> _logger; private IHostingEnvironment _environment; private IConfiguration _configuration; public ApiExceptionFilter(IHostingEnvironment environment, IConfiguration configuration, ILogger<ApiExceptionFilter> logger) { _environment = environment; _configuration = configuration; _logger = logger; }
using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; namespace Filters { public class ApiExceptionFilter : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ApiExceptionFilter>>(); logger.LogError(context.Exception, context.Exception.Message); base.OnException(context); } } }
استفاده از Service Locatorها در ValidationAttributes
روش تزریق وابستگیها در سازندهی کلاسهای ValidationAttribute مهیا نیست و امکانی مانند ServiceFilterها در اینجا کار نمیکند. به همین جهت تنها روشی که برای دسترسی به سرویسها باقی میماند استفاده از الگوی Service Locator است که مثالی از آنرا در کدهای زیر از طریق ValidationContext مشاهده میکنید:
using Microsoft.Extensions.DependencyInjection; using System.ComponentModel.DataAnnotations; using CoreIocServices; namespace Test { public class CustomValidationAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var service = validationContext.GetRequiredService<IMyDisposableService>(); // use service // ... validation logic } } }
استفاده از Service Locatorها در متد Main کلاس Program
فرض کنید سرویسی را در متد ConfigureServices کلاس Startup یک برنامهی وب ثبت کردهاید:
namespace Test { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSingleton<ITokenGenerator, TokenGenerator>(); }
namespace Test { public class Program { public static void Main(string[] args) { IWebHost webHost = CreateWebHostBuilder(args).Build(); var tokenGenerator = webHost.Services.GetRequiredService<ITokenGenerator>(); string token = tokenGenerator.GetToken(); System.Console.WriteLine(token); webHost.Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); } }
namespace Microsoft.AspNetCore.Hosting { public interface IWebHost : IDisposable { IServiceProvider Services { get; } } }
استفاده از Service Locatorها در متد ConfigureServices کلاس Startup
برای دسترسی به سرویسهای برنامه در متد ConfigureServices میتوان متد BuildServiceProvider را بر روی پارامتر services فراخوانی کرد. خروجی آن از نوع کلاس ServiceProvider است که امکان دسترسی به متدهایی مانند GetRequiredService را میسر میکند:
namespace CoreIocSample02 { public class Startup { public void ConfigureServices(IServiceCollection services) { var scopeFactory = services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var provider = scope.ServiceProvider; using (var dbContext = provider.GetRequiredService<ApplicationDbContext>()) { // ... } } }
استفاده از Service Locatorها در متد Configure کلاس Startup
در قسمت قبل عنوان شد که میتوان سرویسهای مدنظر خود را به صورت پارامترهایی جدید به متد Configure اضافه کرد و کار وهله سازی آنها توسط Service Provider برنامه به صورت خودکار صورت میگیرد:
public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IAmACustomService customService) { // .... } }
namespace CoreIocSample02 { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) { var provider = scope.ServiceProvider; using (var dbContext = provider.GetRequiredService<ApplicationDbContext>()) { //... } }
برای نوشتن تستهای واحد(Unit Test) برای پروژه مذکور به چه صورت میشه EntityFramework رو Mock کرد؟ یعنی تزریق وابستگی رو با StructureMap میشه انجام داد یا باید پیاده سازی دیگه ای رو استفاده کرد. میشه یه راهنمایی در مورد نوشتن یه تست واحد توی این سناریو بکنید.
EF Code First #4
الف) عنوان شده آخرین نسخه بتا این مشکل رو نداره (Install-Package EntityFramework -IncludePrerelease)
ب) VS.NET را با دسترسی Admin اجرا کنید.
ج) یا یک نفر دیگر در اینجا عنوان کرده که پروژه حتما باید در VS.NET باز شده باشد.
د) یا شخص دیگری عنوان کرده که آدرس فایلهای پروژه اگر مثلا یک [ داشته باشد، کار نمیکند.