استفاده از StructureMap به عنوان یک IoC Container
در نگارش بعدی، ObjectFactory استاتیک حذف میشود. بجای آن باید بنویسید:
var container = new Container(x => { // تنظیمات در اینجا });
var controller = container.GetInstance(controllerType) as SomeType;
public class MyController { public MyController(IContainer container) { } }
آشنایی با AOP Interceptors
معرفی پروژه DNTFrameworkCore
<input type="button" onclick="startTrace('Some Text')" value="startTrace" /> <input type="button" onclick="startError()" value="test Error" /> <script type="text/javascript"> function startTrace(str) { return method1(100, 200); } function method1(arg1, arg2) { return method2(arg1 + arg2 + 100); } function method2(arg1) { var var1 = arg1 / 100; return method3(var1); } function method3(arg1) { console.trace(); var total = arg1 * 100; return total; } function testCount() { // do something console.count("testCount() Calls Count ."); } function startError() { testError(); } function testError() { var errorObj = new Error(); errorObj.message = "this is a test error"; console.exception(errorObj); } function testFunc() { var t = 0; for (var i = 0; i < 100; i++) { t += i; } } </script>
- console.log(object[,object,...])
این دستور یک پیغام در کنسول چاپ میکند .
console.log("This is a log message!");
این دستور را میتوانیم به شکلهای مختلفی فراخوانی کنیم .
مثلا :
console.log(1 , "+" , 2 , "=", (1+2));
در این دستور میتوانیم از چند حرف جایگزین هم استفاده کنیم .
مثال :
console.log("Firebug 1.0 beta was %s in December %i.","released",2006);
اگر در رشتهی مورد نظر ، یک شیء ( تابع ، آرایه ، ... ) برای جایگزین %o ارسال کنیم ، در خروجی آن شیء بصورت لینک نمایش داده میشود که با کلیک بروی آن ، فایرباگ آن شیء را در تب مناسبش Inspect میکند .
مثال :
console.log("this is a test functin : %o",testFunc);
نتیجه :
و زمانی که بروی لینک testFunc کلیک کنیم :
یک ترفند : بوسیله جایگزین %o توانستیم به تابع مورد نظر لینک بدهیم . اگر بجای جایگزین %o از %s استفاده کنیم ، میتوانیم بدنهی تابع را ببینیم :
console.log("this is a test functin : %s",testFunc);
توسط جایگزین %c هم میتوانید خروجی را فرمت کنید .
console.log("%cThis is a Style Formatted Log","color:green;text-decoration:underline;");
نتیجه :
- console.debug(object[, object, ...])
- console.info(object[, object, ...])
- console.warn(object[, object, ...])
- console.error(object[, object, ...])
مشابه با دستور log عمل میکنند با این تفاوت که خروجی را با استایل متفاوتی نمایش میدهند .
همچنین هر یک از این دستورات ، توسط دکمههای همنام در کنسول قابل فیلتر شدن هستند .
- console.assert(expression[, object, ...])
چک میکند که عبارت ارسال شده true هست یا نه . اگر true نبود ، پیغام وارد شده را چاپ و یک استثناء ایجاد میکند .
console.assert(1==1,"this is a test error"); console.assert(1!=1,"this is a test error");
نتیجه :
- console.clear()
- console.dir(object)
- console.dirxml(node)
- console.profile([title])
- console.profileEnd()
- console.trace()
با این متد میتوانید پی ببرید که از کجا و توسط چه متدهایی برنامه به قسمت trace رسیده . برای درک بهتر مجددا اسکریپت صفحهی تست این مقاله را بررسی کنید ( جایی که متد trace قرار داده شده است ) .
اکنون صفحهی تست را باز کنید و بروی دکمهی startTrace کلیک کنید . خروجی ظاهر شده در کنسول را از پایین به بالا بررسی کنید .
حتما متوجه شدید که متد method3 چگونه در کدهایمان فراخوانی شده است !؟
ابتدا با کلیک بروی دکمهی startTrace ، متد startTrace اجرا شده و به همین ترتیب متد startTrace متد method1 ، متد method1 هم متد method2 و در نهایت method2 متد method3 را فراخوانی کرده است .
دستور trace زمانی که در حال بررسی کدهای برنامه نویسان دیگر هستید ، بسیار میتواند به شما کمک کند .
- console.group(object[, object, ...])
با این دستور میتوانید لاگهای کنسول را بصورت تو در تو گروه بندی کنید .
console.group("Group1"); console.log("Log in Group1"); console.group("Group2"); console.log("Log in Group2"); console.group("Group3"); console.log("Log in Group3");
- console.groupCollapsed(object[, object, ...])
این دستور معادل دستور قبلی است با این تفاوت که هنگام ایجاد ، گروه را جمع میکند .
- console.groupEnd()
به آخرین گروه بندی ایجاد شده خاتمه میدهد .
- console.time(name)
یک تایمر با نام داده شده ایجاد میکند . زمانی که نیاز دارید زمان طی شده بین 2 نقطه را اندازه گیری کنید ، این تابع مفید خواهد بود .
- console.timeEnd(name)
تایمر همنام را متوقف و زمان طی شده را چاپ میکند .
console.time("TestTime"); var t = 1; for (var i = 0; i < 100000; i++) { t *= (i + t) } console.timeEnd("TestTime");
- console.timeStamp()
توضیحات کامل را از اینجا دریافت کنید .
- console.count([title])
تعداد دفعات فراخوانی شدن کدی که این متد در آنجا قرار دارد را چاپ میکند .
البته ظاهرا در ورژن 10.0.1 که بنده با آن کار میکنم ، این دستور بی عیب کار نمیکند . زیرا بجای آنکه در هربار فراخوانی ، در همان خط تعداد فراخوانی را نمایش بدهد ، فقط اولین لاگ را آپدیت میکند .
- console.exception(error-object[, object, ...])
یک پیغام خطا را به همراه ردیابی کامل اجرای کدها تا زمان رویداد خطا ( مانند متد trace ) چاپ میکند .
در صفحهی تست این متد را اجرا کنید :
startError();
توجه کنید که ما برای مشاهدهی عملکرد صحیح این دستور ، آن را در تابع testError قرار دادیم و بوسیله تابع startError آن فراخوانی کردیم .
- console.table(data[, columns])
بوسیله این دستور میتوانید مجموعه ای از اطلاعات را بصورت جدول بندی نمایش بدهید .
این متد از متدهای جدیدی است که در فایرباگ قرار داده شده است .
برای اطلاعات بیشتر به اینجا مراجعه کنید .
این توابع معادل توابع همنامشان در خط فرمان هستند که در قسمت قبل با عملکردشان آشنا شدیم .
بد نیست لیست تعدادی از بانکهای اطلاعاتی مهم قابل استفاده در دات نت به همراه درایورهای ADO.NET آنها را با هم مرور نمائیم.
بانکهای اطلاعاتی قابل استفاده در دات نت فریم ورک | ||||||
ردیف | بانک اطلاعاتی | سایت مرجع | درایور ADO.NET | امکان استفاده از LINQ | مجوز استفاده | توضیحات |
1 | SQL Server 2000/2005/2008/2008 R2 | + | توکار (به صورت پیش فرض در دات نت فریم ورک موجود است) | بلی . به کمک LINQ to SQL ، Entity Framework ، NHibernate و بسیاری از ORM های دیگر | رایگان - تجاری | نسخههای Express آن رایگان است. |
2 | Microsoft SQL Azure | + | بلی : + | بلی. به کمک LINQ to SQL و Entity Framework | تجاری | |
3 | SQL Server Compact | + | بلی : + | بلی. به کمک LINQ to SQL و Entity Framework | رایگان | |
4 | Advantage Database Server | + | قابل دریافت از سایت اصلی: + | بلی. به کمک Entity framework و Telerik OpenAccess ORM | تجاری | |
5 | SQL Anywhere | + | قابل دریافت از سایت اصلی: + | بلی. به کمک Entity framework و Telerik OpenAccess ORM | رایگان - تجاری | Web Edition آن رایگان است. |
6 | MySQL | + | قابل دریافت از سایت اصلی : + | بلی . به کمک NHibernate ، LightSpeed ، DbLinq و تعدادی دیگر از ORM's | رایگان - تجاری | |
7 | Oracle | + | پشتیبانی توکار آن به زودی حذف خواهد شد اما از سایت اصلی قابل دریافت است : + | بلی . به کمک NHibernate ، LightSpeed ، DbLinq و تعدادی دیگر از ORM's | رایگان - تجاری | نسخهی Express آن رایگان است. |
8 | Access | + | توکار | بلی. به کمک ALinq ، NHibernate و یا LINQ to DataSets | تجاری | اگر از دات نت فریم ورک سه و نیم، سرویس پک یک استفاده کنید، امکان استفاده از LINQ to SQL جهت کار با بانکهای اطلاعاتی اکسس نیز مهیا است: + |
9 | SQLite | + | مهیا به صورت سورس باز : + | بلی. درایور ADO.NET آن پشتیبانی از Entity Framework را نیز اضافه میکند. همچنین NHibernate ، ALinq و سایر ORM's را باید به این لیست اضافه کرد. | رایگان | |
10 | Firebird | + | قابل دریافت از سایت اصلی: + | بلی. توسط ALinq ، NHibernate و موارد دیگر. | رایگان | |
11 | PostgreSQL | + | قابل دریافت از سایت اصلی: + | بلی. توسط NHibernate ، DBLinq و موارد دیگر | رایگان | |
12 | DB2 UDB | + | قابل دریافت از سایت اصلی: + | بلی. توسط NHibernate | تجاری | |
13 | ScimoreDB | + | قابل دریافت از سایت اصلی: + | محدود. توسط LINQ to DataSets | رایگان | |
14 | MongoDB | + | معرفی شده در سایت اصلی : + | بلی. درایور ADO.NET معرفی شده به همراه پروایدر LINQ نیز میباشد. | رایگان | |
15 | CouchDB | + | معرفی شده در سایت اصلی : + | محدود | رایگان | |
16 | VistaDB | + | اساسا برای دات نت نوشته شده است. | بلی. به کمک Entity framework | تجاری |
از سرگیری مجدد درخواست ارسالی توسط HttpClient
یک نمونه از سرگیری مجدد درخواست را در مطلب «اضافه کردن قابلیت از سرگیری مجدد (resume) به HttpWebRequest» پیشتر در این سایت مطالعه کردهاید. اصول کلی آن نیز در اینجا صادق است. HTTP 1.1 از مفهوم range headers، برای دریافت پاسخهای جزئی پشتیبانی میکند. به این ترتیب در صورت پیاده سازی چنین قابلیتی در برنامهی سمت سرور، میتوان دریافت بازهای از بایتها را بجای دریافت فایل از ابتدا، از سرور درخواست کرد. به یک چنین قابلیتی Resume و یا از سرگیری مجدد گرفته میشود و درحین دریافت فایلهای حجیم بسیار حائز اهمیت است.
var fileInfo = new FileInfo(outputFilePath); long resumeOffset = 0; if (fileInfo.Exists) { resumeOffset = fileInfo.Length; } if (resumeOffset > 0) { _client.DefaultRequestHeaders.Range = new RangeHeaderValue(resumeOffset, null); }
یک نکته: تمام وب سرورها و یا برنامههای وب از یک چنین قابلیتی پشتیبانی نمیکنند.
روش تشخیص آن نیز به صورت زیر است:
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); if (response.Headers.AcceptRanges == null && resumeOffset > 0) { // resume not supported, starting over }
لغو درخواست ارسالی توسط HttpClient
پس از شروع غیرهمزمان client.GetAsync میتوان متد CancelPendingRequests آنرا فراخوانی کرد تا کلیه درخواستهای مرتبط با این client لغو شوند. اما این متد صرفا برای حالت پیشفرض client.GetAsync که دریافت هدر + محتوا است کار میکند (یعنی حالت HttpCompletionOption.ResponseContentRead). اگر همانند نکات بررسی شدهی در مطلب «دریافت فایلهای حجیم توسط HttpClient» صرفا درخواست خواندن هدر را بدهیم (HttpCompletionOption.ResponseHeadersRead)، چون کنترل ادامهی بحث را خودمان بر عهده گرفتهایم، لغو آن نیز به عهدهی خودمان است و متد CancelPendingRequests بر روی آن تاثیر نخواهد داشت.
این نکته در مورد تنظیم خاصیت TimeOut نیز صادق است. این خاصیت فقط زمانیکه دریافت کل هدر + محتوا توسط متد GetAsync مدیریت شوند، تاثیر گذار است.
بنابراین درحالتیکه نیاز به کنترل بیشتر است، هرچند فراخوانی متد CancelPendingRequests ضرری ندارد، اما الزاما سبب قطع کل درخواست نمیشود و باید این لغو را به صورت ذیل پیاده سازی کرد:
ابتدا یک منبع توکن لغو عملیات را به صورت ذیل ایجاد میکنیم:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
پس از این فراخوانی (()cts.Cancel)، نحوهی واکنش به آن به صورت ذیل خواهد بود:
var result = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, _cts.Token); using(var stream = await result.Content.ReadAsStreamAsync()) { byte[] buffer = new byte[80000]; int bytesRead; while((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0 && !_cts.IsCancellationRequested) { outputStream.Write(buffer, 0, bytesRead); } }
سعی مجدد درخواست ارسالی توسط HttpClient
یک روش پیاده سازی سعی مجدد درخواست شکست خورده، توسط کتابخانهی Polly است. روش دیگر آن نیز به صورت ذیل است:
public async Task DownloadFileAsync(string url, string outputFilePath, int maxRequestAutoRetries) { var exceptions = new List<Exception>(); do { --maxRequestAutoRetries; try { await doDownloadFileAsync(url, outputFilePath); } catch (TaskCanceledException ex) { exceptions.Add(ex); } catch (HttpRequestException ex) { exceptions.Add(ex); } catch (Exception ex) when (isNetworkError(ex)) { exceptions.Add(ex); } // Wait a bit and try again later if (exceptions.Any()) await Task.Delay(2000, _cts.Token); } while (maxRequestAutoRetries > 0 && !_cts.IsCancellationRequested); var uniqueExceptions = exceptions.Distinct().ToList(); if (uniqueExceptions.Any()) { if (uniqueExceptions.Count() == 1) throw uniqueExceptions.First(); throw new AggregateException("Could not process the request.", uniqueExceptions); } } private static bool isNetworkError(Exception ex) { if (ex is SocketException || ex is WebException) return true; if (ex.InnerException != null) return isNetworkError(ex.InnerException); return false; }
اگر یکی از این استثناءهای یاد شده رخدادند، اندکی صبر کرده و مجددا درخواست را از ابتدا صادر میکنیم.
در پایان این سعیهای مجدد، اگر استثنایی ثبت شده بود و همچنین عملیات نیز با موفقیت به پایان نرسیده بود، آنرا به فراخوان صادر میکنیم.
public class Service { public int ServiceId { get; set; } public string ServiceName { get; set; } }
public interface ICoreService { Service LoadDefaultService(); }
An unhandled exception occurred while processing the request InvalidOperationException: Unable to resolve service for type 'WebApplication1.Models.ICoreService' while attempting to activate 'WebApplication1.Controllers.HomeController' Microsoft.Extensions.Internal.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
در نسخههای قدیمی MVC (منظور نسخههای قبل از 6)، برای تزریق وابستگیها از یک Controller Factory یا Dependency Resolver سفارشی استفاده میشد. اما در نسخه جدید MVC دیگری خبری از روشهای قدیمی نیست. چونکه یک سیستم تزریق وابستگی توکار، همراه با MVC یکپارچه شدهاست که عملیات تزریق وابستگیها را انجام میدهد. سیستم تزریق وابستگی پیش فرض، تنها از 4 حالت عملیاتی پشتیبانی میکند:
تیم Asp.Net برای فراهم آوردن امکان تزریق وابستگیها، تصمیم به انتزاعی کردن ویژگیهای مشترک محبوبترین Ioc Containerها و اجازه دادن به میان افزارها، جهت ارتباط با این اینترفیسها برای دستیابی به تزریق وابستگی بود.
namespace Microsoft.Extensions.DependencyInjection { // // Summary: // Specifies the lifetime of a service in an Microsoft.Extensions.DependencyInjection.IServiceCollection. public enum ServiceLifetime { // // Summary: // Specifies that a single instance of the service will be created. Singleton = 0, // // Summary: // Specifies that a new instance of the service will be created for each scope. // // Remarks: // In ASP.NET Core applications a scope is created around each server request. Scoped = 1, // // Summary: // Specifies that a new instance of the service will be created every time it is // requested. Transient = 2 } }
public void ConfigureServices(IServiceCollection services) { ServiceDescriptor descriptor = new ServiceDescriptor(typeof(ICoreService),typeof(CoreServise),ServiceLifetime.Transient); services.Add(descriptor); services.AddMvc(); }
ساخت یک Service Descriptor و اضافه کردن آن به سرویسها، فلسفه وجودی میان افزارها را زیر سوال میبرد. پس بجای ایجاد یک Service Descriptor، از متدهای الحاقی تدارک دیده شده استفاده میکنیم. مثلا بجای دو خط کد بالا میتوان از کد زیر استفاده نمود:
services.AddTransient<ICoreService,CoreServise>();
حال که یک دید کلی از نحوه کار مکانیزم تزریق وابستگی بدست آوردیم، میخواهیم این مکانیزم را با StructureMap جایگزین کنیم. بدین منظور ابتدا پکیج StructureMap را نصب میکنم.
در مرحله اول باید کلاسهایی را تدارک ببینیم که اینترفیسهای بالا را پیاده سازی نمایند. یعنی کلاسهای ما باید بتوانند همان کاری را انجام دهند که مکانیزم پیش فرض MVC انجام میدهد.
اولین مورد، کلاس StructureMapServiceProvider میباشد.
internal class StructureMapServiceProvider : IServiceProvider { private readonly IContainer _container; public StructureMapServiceProvider(IContainer container, bool scoped = false) { _container = container; } public object GetService(Type type) { try { return _container.GetInstance(type); } catch { return null; } } }
مورد دوم کلاس StructureMapServiceScope میباشد:
internal class StructureMapServiceScope : IServiceScope { private readonly IContainer _container; private readonly IContainer _childContainer; private IServiceProvider _provider; public StructureMapServiceScope(IContainer container) { _container = container; _childContainer = _container.GetNestedContainer(); _provider = new StructureMapServiceProvider(_childContainer, true); } public IServiceProvider ServiceProvider => _provider; public void Dispose() { _provider = null; if (_childContainer != null) _childContainer.Dispose(); } }
مورد سوم StructureMapServiceScopeFactory میباشد:
internal class StructureMapServiceScopeFactory : IServiceScopeFactory { private IContainer _container; public StructureMapServiceScopeFactory(IContainer container) { _container = container; } public IServiceScope CreateScope() { return new StructureMapServiceScope(_container); } }
مورد بعدی کلاس StructureMapPopulator میباشد. وظیفه این کلاس جمع آوری اطلاعات مربوط به سرویسها میباشد.
internal class StructureMapPopulator { private IContainer _container; public StructureMapPopulator(IContainer container) { _container = container; } public void Populate(IEnumerable<ServiceDescriptor> descriptors) { _container.Configure(c => { c.For<IServiceProvider>().Use(new StructureMapServiceProvider(_container)); c.For<IServiceScopeFactory>().Use<StructureMapServiceScopeFactory>(); foreach (var descriptor in descriptors) { switch (descriptor.Lifetime) { case ServiceLifetime.Singleton: Use(c.For(descriptor.ServiceType).Singleton(), descriptor); break; case ServiceLifetime.Transient: Use(c.For(descriptor.ServiceType), descriptor); break; case ServiceLifetime.Scoped: Use(c.For(descriptor.ServiceType), descriptor); break; } } }); } private static void Use(GenericFamilyExpression expression, ServiceDescriptor descriptor) { if (descriptor.ImplementationFactory != null) { expression.Use(Guid.NewGuid().ToString(), context => { return descriptor.ImplementationFactory(context.GetInstance<IServiceProvider>()); }); } else if (descriptor.ImplementationInstance != null) { expression.Use(descriptor.ImplementationInstance); } else if (descriptor.ImplementationType != null) { expression.Use(descriptor.ImplementationType); } else { throw new InvalidOperationException("IServiceDescriptor is invalid"); } } }
و در آخر کلاس StructureMapRegistration میباشد:
public static class StructureMapRegistration { public static void Populate(this IContainer container, IEnumerable<ServiceDescriptor> descriptors) { var populator = new StructureMapPopulator(container); populator.Populate(descriptors); } }
نهایتاً باید متد ConfigurationServices در کلاس StartUp را اندکی تغییر دهیم.
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); var container = new Container(); container.Configure(configure => { configure.For<ICoreService>().Use<CoreServise>(); }); container.Populate(services); return container.GetInstance<IServiceProvider>(); }
در کد بالا، متد ConfigurationServices به جای آنکه Void برگرداند، نمونهای از اینترفیس IServiceProvider را برمیگرداند. حال اگر برنامه را اجرا کنیم، وابستگیها توسط StructureMap تزریق شده و برنامه بدون هیچ مشکلی اجرا میشود.