ابتدا پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب آن را MVC + Web API انتخاب کنید.
ابتدا به فایل WebApiConfig.cs در پوشه App_Start مراجعه کنید و مسیر پیش فرض را حذف کنید. برای مسیریابی سرویسها از قابلیت جدید Attribute Routing استفاده خواهیم کرد. فایل مذکور باید مانند لیست زیر باشد.
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API configuration and services // Web API routes config.MapHttpAttributeRoutes(); } }
سپس در پوشه Models کلاس جدیدی بنام VideoStream ایجاد کنید. این کلاس مسئول نوشتن داده فایلهای ویدیویی در OutputStream خواهد بود. کد کامل این کلاس را در لیست زیر مشاهده میکنید.
public class VideoStream { private readonly string _filename; private long _contentLength; public long FileLength { get { return _contentLength; } } public VideoStream(string videoPath) { _filename = videoPath; using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { _contentLength = video.Length; } } public async void WriteToStream(Stream outputStream, HttpContent content, TransportContext context) { try { var buffer = new byte[65536]; using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { var length = (int)video.Length; var bytesRead = 1; while (length > 0 && bytesRead > 0) { bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length)); await outputStream.WriteAsync(buffer, 0, bytesRead); length -= bytesRead; } } } catch (HttpException) { return; } finally { outputStream.Close(); } } }
شرح کلاس VideoStream
این کلاس ابتدا دو فیلد خصوصی تعریف میکند. یکی filename_ که فقط-خواندنی است و نام فایل ویدیو درخواستی را نگهداری میکند. و دیگری contentLength_ که سایز فایل ویدیو درخواستی را نگهداری میکند.
یک خاصیت عمومی بنام FileLength نیز تعریف شده که مقدار خاصیت contentLength_ را بر میگرداند.
متد سازنده این کلاس پارامتری از نوع رشته بنام videoPath را میپذیرد که مسیر کامل فایل ویدیوی مورد نظر است. در این متد، متغیرهای filename_ و contentLength_ مقدار دهی میشوند. نکتهی قابل توجه در این متد استفاده از پارامتر FileShare.Read است که باعث میشود فایل مورد نظر هنگام باز شدن قفل نشود و برای پروسههای دیگر قابل دسترسی باشد.
در آخر متد WriteToStream را داریم که مسئول نوشتن داده فایلها به OutputStream است. اول از همه دقت کنید که این متد از کلمه کلیدی async استفاده میکند بنابراین بصورت asynchronous اجرا خواهد شد. در بدنه این متد متغیری بنام buffer داریم که یک آرایه بایت با سایز 64KB را تعریف میکند. به بیان دیگر اطلاعات فایلها را در پکیجهای 64 کیلوبایتی برای کلاینت ارسال خواهیم کرد. در ادامه فایل مورد نظر را باز میکنیم (مجددا با استفاده از FileShare.Read) و شروع به خواندن اطلاعات آن میکنیم. هر 64 کیلوبایت خوانده شده بصورت async در جریان خروجی نوشته میشود و تا هنگامی که به آخر فایل نرسیده ایم این روند ادامه پیدا میکند.
while (length > 0 && bytesRead > 0) { bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length)); await outputStream.WriteAsync(buffer, 0, bytesRead); length -= bytesRead; }
حال که کلاس VideoStream را در اختیار داریم میتوانیم پروژه را تکمیل کنیم. در پوشه کنترلرها کلاسی بنام VideoControllerبسازید. کد کامل این کلاس را در لیست زیر مشاهده میکنید.
public class VideoController : ApiController { [Route("api/video/{ext}/{fileName}")] public HttpResponseMessage Get(string ext, string fileName) { string videoPath = HostingEnvironment.MapPath(string.Format("~/Videos/{0}.{1}", fileName, ext)); if (File.Exists(videoPath)) { FileInfo fi = new FileInfo(videoPath); var video = new VideoStream(videoPath); var response = Request.CreateResponse(); response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)video.WriteToStream, new MediaTypeHeaderValue("video/" + ext)); response.Content.Headers.Add("Content-Disposition", "attachment;filename=" + fi.Name.Replace(" ", "")); response.Content.Headers.Add("Content-Length", video.FileLength.ToString()); return response; } else { return Request.CreateResponse(HttpStatusCode.NotFound); } } }
شرح کلاس VideoController
همانطور که میبینید مسیر دستیابی به این کنترلر با استفاده از قابلیت Attribute Routing تعریف شده است.
[Route("api/video/{ext}/{fileName}")]
api/video/mp4/sample
متد Get این کنترلر دو پارامتر با نامهای ext و fileName را میپذیرد که همان فرمت و نام فایل هستند. سپس با استفاده از کلاس HostingEnvironment سعی میکنیم مسیر کامل فایل درخواست شده را بدست آوریم.
string videoPath = HostingEnvironment.MapPath(string.Format("~/Videos/{0}.{1}", fileName, ext));
var video = new VideoStream(videoPath);
var response = Request.CreateResponse(); response.Content = new PushStreamContent((Action<Stream, HttpContent, TransportContext>)video.WriteToStream, new MediaTypeHeaderValue("video/" + ext));
کلاس PushStreamContent در فضای نام System.Net.Http وجود دارد. همانطور که میبینید امضای Action پاس داده شده، با امضای متد WriteToStream در کلاس VideoStream مطابقت دارد.
در آخر دو Header به پاسخ ارسالی اضافه میکنیم تا نوع داده ارسالی و سایز آن را مشخص کنیم.
response.Content.Headers.Add("Content-Disposition", "attachment;filename=" + fileName); response.Content.Headers.Add("Content-Length", video.FileLength.ToString());
if(File.Exists(videoPath)) { // removed for bravity } else { return Request.CreateResponse(HttpStatusCode.NotFound); }
public class HomeController : Controller { // GET: Home public ActionResult Index() { return View(); } }
<div> <div> <video width="480" height="270" controls="controls" preload="auto"> <source src="/api/video/mp4/sample" type="video/mp4" /> Your browser does not support the video tag. </video> </div> </div>
اگر پروژه را اجرا کنید میبینید که ویدیو مورد نظر آماده پخش است. برای اینکه ببینید چطور دادههای ویدیو در قالب پکیجهای 64 کیلو بایتی دریافت میشوند از ابزار مرورگرتان استفاده کنید. مثلا در گوگل کروم F12 را بزنید و به قسمت Network بروید. صفحه را یکبار مجددا بارگذاری کنید تا ارتباطات شبکه مانیتور شود. اگر به المنت sample دقت کنید میبینید که با شروع پخش ویدیو پکیجهای اطلاعات یکی پس از دیگری دریافت میشوند و اطلاعات ریز آن را میتوانید مشاهده کنید.
پروژه نمونه به این مقاله ضمیمه شده است. قابلیت Package Restore فعال شده و برای صرفه جویی در حجم فایل، تمام پکیجها و محتویات پوشه bin حذف شده اند. برای تست بیشتر میتوانید فایل sample.mp4 را با فایلی حجیمتر جایگزین کنید تا نحوه دریافت اطلاعات را با روشی که در بالا بدان اشاره شد مشاهده کنید.
AsyncVideoStreaming.rar
کتابخانه glogg
نرم افزاری بسیار سریع با قابلیت باز کردن فایلهای چند گیگابایتی است که با استفاده از regular expressions به راحتی میتوانید در آن جستجو کنید. دانلود
glogg - the fast, smart log explorer
glogg is a multi-platform GUI application that helps browse and search through long and complex log files. It is designed with programmers and system administrators in mind and can be seen as a graphical, interactive combination of grep and less.
Main features
- Runs on Unix-like systems, Windows and Mac thanks to Qt
- Provides a second window showing the result of the current search
- Reads UTF-8 and ISO-8859-1 files
- Supports grep/egrep like regular expressions
- Colorizes the log and search results
- Displays a context view of where in the log the lines of interest are
- Is fast and reads the file directly from disk, without loading it into memory
- Is open source, released under the GPL
SELECT * FROM tb1 WHERE x1 = '12';
SELECT * FROM tb1 WHERE x1 = '12';
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Microsoft.SqlServer.TransactSql.ScriptDom; namespace SqlDomAnalyzer.Core { public static class PrettyPrintTSql { public static string FormatTSql(string tSql) { IList<ParseError> errors; TSqlScript sqlFragment; using (var reader = new StringReader(tSql)) { var parser = new TSql120Parser(initialQuotedIdentifiers: true); sqlFragment = (TSqlScript)parser.Parse(reader, out errors); } if (errors != null && errors.Any()) { var sb = new StringBuilder(); foreach (var error in errors) sb.AppendLine(error.Message); throw new InvalidOperationException(sb.ToString()); } var sql110ScriptGenerator = new Sql120ScriptGenerator(new SqlScriptGeneratorOptions { SqlVersion = SqlVersion.Sql120 }); string finalScript; sql110ScriptGenerator.GenerateScript(sqlFragment, out finalScript); return finalScript; } } }
نکتهی جالب دیگری که در اینجا وجود دارد، تهیهی یک خروجی همواره یک شکل است. برای نمونه سه عبارت SQL زیر را در نظر بگیرید:
SELECT * from tb1 WHERE x1 = '12'; SELECT * from tb1 where x1 = '12'; select * from tb1 WHERE x1 = '12';
در هر سه حالت یا هر حالت قابل تصور دیگری، خروجی SQL فرمت شدهی حاصل یک چنین شکلی را دارد:
SELECT * FROM tb1 WHERE x1 = '12';
موارد کاربرد آن؟
علاوه بر نمایش زیبای SQL فرمت نشده، احتمالا برنامههای Profiler ایی را دیدهاید که عنوان میکنند قادرند عبارات SQL همانند را تشخیص دهند (جهت یافتن Lazy loading اشتباه). یک چنین خروجی یکسانی، قابلیت تهیه Hash عبارات SQL دریافتی را میسر میکند؛ چون دیگر اینبار مهم نیست که اجزای تشکیل دهندهی یک عبارت SQL با حروف بزرگ هستند یا کوچک و فاصلهی بین آنها چقدر است و آیا در این بین خطوط جدیدی نیز وجود دارند و امثال آن. خروجی نهایی نرمال شدهی توسط Sql120ScriptGenerator همواره یک شکل است. از این دو قابلیت در برنامهی DNTProfiler استفاده شدهاست.
- تنظیمات پیش فرض باید تغییر کنند تا کلمات عبور حداقل 10 کاراکتر باشند
- کلمه عبور حداقل یک عدد و یک کاراکتر ویژه باید داشته باشد
- امکان استفاده از 5 کلمه عبور اخیری که ثبت شده وجود ندارد
ایجاد اپلیکیشن جدید
در پنجره Solution Explorer روی نام پروژه کلیک راست کنید و گزینه Manage NuGet Packages را انتخاب کنید. به قسمت Update بروید و تمام انتشارات جدید را در صورت وجود نصب کنید.
بگذارید تا به روند کلی ایجاد کاربران جدید در اپلیکیشن نگاهی بیاندازیم. این به ما در شناسایی نیازهای جدیدمان کمک میکند. در پوشه Controllers فایلی بنام AccountController.cs وجود دارد که حاوی متدهایی برای مدیریت کاربران است.
- کنترلر Account از کلاس UserManager استفاده میکند که در فریم ورک Identity تعریف شده است. این کلاس به نوبه خود از کلاس دیگری بنام UserStore استفاده میکند که برای دسترسی و مدیریت دادههای کاربران استفاده میشود. در مثال ما این کلاس از Entity Framework استفاده میکند که پیاده سازی پیش فرض است.
- متد Register POST یک کاربر جدید میسازد. متد CreateAsync به طبع متد 'ValidateAsync' را روی خاصیت PasswordValidator فراخوانی میکند تا کلمه عبور دریافتی اعتبارسنجی شود.
var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); }
قانون 1: کلمههای عبور باید حداقل 10 کاراکتر باشند
- مقدار حداقل کاراکترهای کلمه عبور به دو شکل میتواند تعریف شود. راه اول، تغییر کنترلر Account است. در متد سازنده این کنترلر کلاس UserManager وهله سازی میشود، همینجا میتوانید این تغییر را اعمال کنید. راه دوم، ساختن کلاس جدیدی است که از UserManager ارث بری میکند. سپس میتوان این کلاس را در سطح global تعریف کرد. در پوشه IdentityExtensions کلاس جدیدی با نام ApplicationUserManager بسازید.
public class ApplicationUserManager : UserManager<ApplicationUser> { public ApplicationUserManager(): base(new UserStore<ApplicationUser>(new ApplicationDbContext())) { PasswordValidator = new MinimumLengthValidator (10); } }
- حال باید کلاس ApplicationUserManager را در کنترلر Account استفاده کنیم. متد سازنده و خاصیت UserManager را مانند زیر تغییر دهید.
public AccountController() : this(new ApplicationUserManager()) { } public AccountController(ApplicationUserManager userManager) { UserManager = userManager; } public ApplicationUserManager UserManager { get; private set; }
- اپلیکیشن را اجرا کنید و سعی کنید کاربر محلی جدیدی ثبت نمایید. اگر کلمه عبور وارد شده کمتر از 10 کاراکتر باشد پیغام خطای زیر را دریافت میکنید.
قانون 2: کلمههای عبور باید حداقل یک عدد و یک کاراکتر ویژه داشته باشند
- در پوشه IdentityExtensions کلاس جدیدی بنام CustomPasswordValidator بسازید و اینترفیس مذکور را پیاده سازی کنید. از آنجا که نوع کلمه عبور رشته (string) است از <IIdentityValidator<string استفاده میکنیم.
public class CustomPasswordValidator : IIdentityValidator<string> { public int RequiredLength { get; set; } public CustomPasswordValidator(int length) { RequiredLength = length; } public Task<IdentityResult> ValidateAsync(string item) { if (String.IsNullOrEmpty(item) || item.Length < RequiredLength) { return Task.FromResult(IdentityResult.Failed(String.Format("Password should be of length {0}",RequiredLength))); } string pattern = @"^(?=.*[0-9])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*0-9]{10,}$"; if (!Regex.IsMatch(item, pattern)) { return Task.FromResult(IdentityResult.Failed("Password should have one numeral and one special character")); } return Task.FromResult(IdentityResult.Success); }
- قدم بعدی تعریف این اعتبارسنج سفارشی در کلاس UserManager است. باید مقدار خاصیت PasswordValidator را به این کلاس تنظیم کنیم. به کلاس ApplicationUserManager که پیشتر ساختید بروید و مقدار خاصیت PasswordValidator را به CustomPasswordValidator تغییر دهید.
public class ApplicationUserManager : UserManager<ApplicationUser> { public ApplicationUserManager() : base(new UserStore<ApplicationUser(new ApplicationDbContext())) { PasswordValidator = new CustomPasswordValidator(10); } }
قانون 3: امکان استفاده از 5 کلمه عبور اخیر ثبت شده وجود ندارد
public class PreviousPassword { public PreviousPassword() { CreateDate = DateTimeOffset.Now; } [Key, Column(Order = 0)] public string PasswordHash { get; set; } public DateTimeOffset CreateDate { get; set; } [Key, Column(Order = 1)] public string UserId { get; set; } public virtual ApplicationUser User { get; set; } }
- خاصیت جدیدی به کلاس ApplicationUser اضافه کنید تا لیست آخرین کلمات عبور استفاده شده را نگهداری کند.
public class ApplicationUser : IdentityUser { public ApplicationUser() : base() { PreviousUserPasswords = new List<PreviousPassword>(); } public virtual IList<PreviousPassword> PreviousUserPasswords { get; set; } }
public class ApplicationUserStore : UserStore<ApplicationUser> { public ApplicationUserStore(DbContext context) : base(context) { } public override async Task CreateAsync(ApplicationUser user) { await base.CreateAsync(user); await AddToPreviousPasswordsAsync(user, user.PasswordHash); } public Task AddToPreviousPasswordsAsync(ApplicationUser user, string password) { user.PreviousUserPasswords.Add(new PreviousPassword() { UserId = user.Id, PasswordHash = password }); return UpdateAsync(user); } }
public class ApplicationUserManager : UserManager<ApplicationUser> { private const int PASSWORD_HISTORY_LIMIT = 5; public ApplicationUserManager() : base(new ApplicationUserStore(new ApplicationDbContext())) { PasswordValidator = new CustomPasswordValidator(10); } public override async Task<IdentityResult> ChangePasswordAsync(string userId, string currentPassword, string newPassword) { if (await IsPreviousPassword(userId, newPassword)) { return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password")); } var result = await base.ChangePasswordAsync(userId, currentPassword, newPassword); if (result.Succeeded) { var store = Store as ApplicationUserStore; await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword)); } return result; } public override async Task<IdentityResult> ResetPasswordAsync(string userId, string token, string newPassword) { if (await IsPreviousPassword(userId, newPassword)) { return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password")); } var result = await base.ResetPasswordAsync(userId, token, newPassword); if (result.Succeeded) { var store = Store as ApplicationUserStore; await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword)); } return result; } private async Task<bool> IsPreviousPassword(string userId, string newPassword) { var user = await FindByIdAsync(userId); if (user.PreviousUserPasswords.OrderByDescending(x => x.CreateDate). Select(x => x.PasswordHash).Take(PASSWORD_HISTORY_LIMIT) .Where(x => PasswordHasher.VerifyHashedPassword(x, newPassword) != PasswordVerificationResult.Failed).Any()) { return true; } return false; } }
سورس کد این مثال را میتوانید از این لینک دریافت کنید. نام پروژه Identity-PasswordPolicy است، و زیر قسمت Samples/Identity قرار دارد.
services.AddTransient(typeof(Lazy<>), typeof(LazyFactory<>)); class LazyFactory<T> : Lazy<T> where T : class { public LazyFactory(IServiceProvider provider) : base(() => provider.GetRequiredService<T>()) { } }
یک debug visualizer برای VS.Net هست به نام Regular Expression Visualizer. با VS2005 و 2008 سازگار است.
آنرا از آدرس زیر دریافت کنید:
http://weblogs.asp.net/rosherove/archive/2005/11/26/AnnoucingRegexKit10.aspx
سپس فایلهای dll آنرا در یکی از مسیرهای زیر بسته به نگارش VS.Net خودتون کپی کنید:
My Documents\Visual Studio 2008\Visualizers
یا
My Documents\Visual Studio 2005\Visualizers
اکنون در VS.Net روی سطر return _pbrRegex یک breakpoint بگذارید و نتیجه را ملاحظه کنید.
یک مثال عملی:
http://professionalaspnet.com/archive/2008/06/18/Regular-Expression-Visualizer.aspx
برای نمونه خروجی عبارت باقاعده مثال جاری به صورت زیر است که کمک شایانی است در درک عبارت فوق و امثال آن:
<
zero-width negative lookahead
br
or
/br
or
p
or
/p
End Capture
. (any character)
+ (one or more times) (non-greedy)
>
JSLint.VS
JSLint.VS افزونهای است رایگان برای VS.Net2005/2008 جهت بررسی سادهتر مشکلات دستوری در فایلهای JavaScript یک پروژه.
اکنون بجای اینکه در مرورگر به دنبال خطاهای گزارش شده بگردیم، میتوان پیش از بررسی نهایی آنها، در VS.Net مشکلات ممکن را یافته و برطرف ساخت.
JSLint.VS به منوی کلیک راست بر روی یک فایل js اضافه میشود و یا در حالت انتخاب قطعهای کد و سپس کلیک راست و بررسی مشکلات موجود و یا در حالت یکپارچه با امکانات build پروژه قابل استفاده است (برای یکپارچه سازی با Build باید به منوی Tools قسمت JSLint.VS options مراجعه کرد و سپس گزینه build را درصفحه ظاهر شده تیک زد).
پس از دریافت آن، محتویات پوشه bin آنرا در مسیر زیر کپی نمائید:
%My Documents%/Visual Studio 2008/Addins
Or
%My Documents%/Visual Studio 2005/Addins
public class Startup { public void ConfigureServices(IServiceCollection services) { // ... services.AddTransient<ICustomerService, DefaultCustomerService>(); // ... } }
public class SupportController { // DefaultCustomerService will be injected here: public SupportController(ICustomerService customerService) { // ... } }
برای سفارشی سازی مراحل وهله سازی اشیاء توسط IoC Container توکار برنامه و امکان دخالت در آن، قابلیتی تحت عنوان «factory registration» نیز پیش بینی شدهاست که در ادامه آنرا بررسی میکنیم.
Factory Registration چیست؟
اگر در اسمبلی Microsoft.Extensions.DependencyInjection.Abstractions و فضای نام Microsoft.Extensions.DependencyInjection آن به کلاس ServiceCollectionServiceExtensions که متدهای الحاقی مانند AddScoped را ارائه میکند، بیشتر دقت کنیم، تک تک این متدها امضاهای دیگری را نیز دارند:
namespace Microsoft.Extensions.DependencyInjection { public static class ServiceCollectionServiceExtensions { public static IServiceCollection AddScoped<TService>( this IServiceCollection services) where TService : class; public static IServiceCollection AddScoped( this IServiceCollection services, Type serviceType, Type implementationType); public static IServiceCollection AddScoped( this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory); public static IServiceCollection AddScoped<TService, TImplementation>(this IServiceCollection services) public static IServiceCollection AddScoped( this IServiceCollection services, Type serviceType); public static IServiceCollection AddScoped<TService>( this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class; public static IServiceCollection AddScoped<TService, TImplementation>( this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory) // ... } }
مثال 1 : تزریق وابستگیها در حالتیکه کلاس سرویس مدنظر دارای تعدادی پارامتر ثابت است
IoC Container توکار برنامههای NET Core.، به صورت خودکار وابستگیهای تزریق شدهی در سازندههای سرویسهای مختلف را تا هر چند سطح ممکن، به صورت خودکار وهله سازی میکند؛ به شرطیکه این وابستگیهای تزریق شده نیز خودشان سرویس بوده باشند و در تنظیمات ابتدایی آن ثبت و معرفی شده باشند. به عبارتی زمانیکه با سیستم تزریق وابستگیها کار میکنیم، مهم نیست که نگران مقدار دهی پارامترهای سازندهی تزریق شدهی در سازندههای سرویسی خاص باشیم. اما ... برای نمونه سرویس زیر را که یک رشته را در سازندهی خود دریافت میکند درنظر بگیرید:
namespace CoreIocServices { public interface IParameterizedService { string GetConstructorParameter(); } public class ParameterizedService : IParameterizedService { private readonly string _connectionString; public ParameterizedService(string connectionString) { _connectionString = connectionString; } public string GetConstructorParameter() { return _connectionString; } } }
services.AddTransient<IParameterizedService, ParameterizedService>();
services.AddTransient<IParameterizedService>(serviceProvider => { return new ParameterizedService("some value ...."); });
services.AddTransient<IParameterizedService>(serviceProvider => new ParameterizedService("some value ...."));
در اینجا چون serviceProvider نیز در اختیار ما است، حتی میتوان این مقدار را از سرویسی دیگر دریافت کرد و سپس مورد استفاده قرار داد:
services.AddTransient<IParameterizedService>(serviceProvider => { var config = serviceProvider.GetRequiredService<ITestService>().GetConfigValue(); return new ParameterizedService(config); });
نمونهی دیگری از این دست، کار با IUrlHelper توکار ASP.NET Core است. این سرویس برای اینکه پاسخ درستی را ارائه دهد، نیاز به ActionContext جاری را دارد تا بتواند از طریق آن به تمام جزئیات اکشن متد یک کنترلر و درخواست رسیده دسترسی داشته باشد. در این حالت برای ساده سازی کار با آن، بهتر است تامین وابستگیهای لحظهای این سرویس را با سفارشی سازی نحوهی وهله سازی آن، انجام دهیم، تا اینکه این قطعه کد تکراری را در هر جائیکه به IUrlHelper نیاز است، تکرار کنیم:
services.AddScoped<IUrlHelper>(serviceProvider => { var actionContext = serviceProvider.GetRequiredService<IActionContextAccessor>().ActionContext; var urlHelperFactory = serviceProvider.GetRequiredService<IUrlHelperFactory>(); return urlHelperFactory.GetUrlHelper(actionContext); });
مثال 2: وهله سازی در صورت نیاز به وابستگیهای یک سرویس، به کمک Lazy loading
فرض کنید دو سرویس را در سازندهی سرویس دیگری تزریق کردهاید:
namespace Services { public class OrderHandler : IOrderHandler { private readonly IAccounting _accounting; private readonly ISales _sales; public OrderHandler(IAccounting accounting, ISales sales) {
روش انجام یک چنین کارهایی با استفاده از کلاس Lazy اضافه شدهی به NET 4x. قابل انجام است:
public class OrderHandlerLazy : IOrderHandler { public OrderHandlerLazy(Lazy<IAccounting> accounting, Lazy<ISales> sales) {
services.AddTransient<IOrderHandler, OrderHandlerLazy>(); services.AddTransient<IAccounting, Accounting>() .AddTransient(serviceProvider => new Lazy<IAccounting>(() => serviceProvider.GetRequiredService<IAccounting>())); services.AddTransient<ISales, Sales>() .AddTransient(serviceProvider => new Lazy<ISales>(() => serviceProvider.GetRequiredService<ISales>()));
- سپس سرویسهایی که قرار است به صورت Lazy نیز واکشی شوند، بار دیگر توسط روش factory registration با وهله سازی new Lazy از نوع سرویس مدنظر و فراهم آوردن پیاده سازی آن با استفاده از serviceProvider.GetRequiredService، مجددا معرفی خواهند شد.
پس از این تنظیمات، اگر سرویس IOrderHandler را از طریق سیستم تزریق وابستگیها درخواست کنید، وابستگیهای تزریق شدهی در سازندهی آن فقط زمانی و در محلی وهله سازی میشوند که از طریق خاصیت Value شیء Lazy آنها مورد استفاده قرار گرفته شده باشند.
مثال کامل IOrderHandler را از فایل پیوستی انتهای مطلب میتوانید دریافت. اگر آنرا اجرا کنید (برنامهی کنسول آنرا)، در خروجی آن، فقط اجرا شدن سازندهی سرویسی را مشاهده میکنید که مورد استفاده قرار گرفته و نه وابستگی دومی که تزریق شده، اما استفاده نشدهاست.
مثال 3: چگونه بجای اینترفیسها، یک وهله از کلاسی مشخص را از سیستم تزریق وابستگیها درخواست کنیم؟
فرض کنید سرویسی را به صورت زیر به سیستم تزریق وابستگیها معرفی کردهاید:
services.AddTransient<IMyDisposableService, MyDisposableService>();
public class AnotherController { public AnotherController(MyDisposableService customerService) { // ... } }
An unhandled exception occurred while processing the request. InvalidOperationException: Unable to resolve service for type ‘MyDisposableService’ while attempting to activate ‘AnotherController’. Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
services.AddTransient<IMyDisposableService, MyDisposableService>(); services.AddTransient<MyDisposableService>(serviceProvider => serviceProvider.GetRequiredService<IMyDisposableService>() as MyDisposableService);
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: CoreDependencyInjectionSamples-06.zip