This library helps write code in more functional way. To get to know more about the principles behind it, check out the Applying Functional Principles in C# Pluralsight course.
هدف ارائه راه حلی برای مدیریت Transactionها به عنوان یک Cross Cutting Concern، توسط ApplicationServiceها میباشد.
- دوره Aspect oriented programming
- بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن
- طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها
پیش فرض ما این است که شما از EF به عنوان OR-Mapper استفاده میکنید و الگوی Context Per Request را پیاده سازی کرده اید یا از طریق پیاده سازی الگوی Container Per Request به داشتن Context یکتا برای هر درخواست رسیده اید.
کتابخانه StructureMap.Mvc5 پیاده سازی از الگوی Container Per Request را با استفاده از امکانات Nested Container مربوط به StructureMap ارائه میدهد. اشیاء موجود در Nested Container طول عمر Singleton دارند.
واسط ITransaction
public interface ITransaction : IDisposable { void Commit(); void Rollback(); }
واسط بالا 3 متد را که برای مدیریت تراکنش لازم میباشد، در اختیار استفاده کننده قرار میدهد.
واسط IUnitOfWork
public interface IUnitOfWork : IDisposable { ... ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Snapshot); ITransaction Transaction { get; } IDbConnection Connection { get; } bool HasTransaction { get; } }
اعضای جدید واسط IUnitOfWork کاملا مشخص هستند.
پیاده سازی واسط ITransaction، توسط یک Nested Type در دل کلاس DbContextBase انجام میگیرد.
public abstract class DbContextBase : DbContext { ... #region Fields private ITransaction _currenTransaction; #endregion #region NestedTypes private class DbContextTransactionAdapter : ITransaction { private DbContextTransaction _transaction; public DbContextTransactionAdapter(DbContextTransaction transaction) { Guard.NotNull(transaction, nameof(transaction)); _transaction = transaction; } public void Commit() { _transaction?.Commit(); } public void Rollback() { if (_transaction?.UnderlyingTransaction.Connection != null) _transaction.Rollback(); } public void Dispose() { _transaction?.Dispose(); _transaction = null; } } #endregion #region Public Methods ... public ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted) { if (_currenTransaction != null) return _currenTransaction; return _currenTransaction = new DbContextTransactionAdapter(Database.BeginTransaction(isolationLevel)); } #endregion #region Properties ... public ITransaction Transaction => _currenTransaction; public IDbConnection Connection => Database.Connection; public bool HasTransaction => _currenTransaction != null; #endregion } public class ApplicationDbContext : DbContextBase, IUnitOfWork, ITransientDependency { }
کلاس DbContextTransactionAdapter همانطور که از نام آن مشخص میباشد، پیاده سازی از الگوی Adapter برای وفق دادن DbContextTransaction با واسط ITransaction، میباشد. متد BeginTransaction در صورتی که تراکنشی برای وهله جاری DbContext ایجاد نشده باشد، تراکنشی را ایجاد کرده و فیلد currentTransaction_ را نیز مقدار دهی میکند.
TransactionalAttribute
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public sealed class TransactionalAttribute : Attribute { public IsolationLevel IsolationLevel { get; set; } = IsolationLevel.ReadCommitted; public TimeSpan? Timeout { get; set; } }
TransactionInterceptor
public class TransactionInterceptor : ISyncInterceptionBehavior { private readonly IUnitOfWork _unitOfWork; public TransactionInterceptor(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public IMethodInvocationResult Intercept(ISyncMethodInvocation methodInvocation) { var transactionAttribute = GetTransactionaAttributeOrNull(methodInvocation.InstanceMethodInfo); if (transactionAttribute == null || _unitOfWork.HasTransaction) return methodInvocation.InvokeNext(); using (var transaction = _unitOfWork.BeginTransaction(transactionAttribute.IsolationLevel)) { var result = methodInvocation.InvokeNext(); if (result.Successful) transaction.Commit(); else { transaction.Rollback(); } return result; } } private static TransactionalAttribute GetTransactionaAttributeOrNull(MemberInfo methodInfo) { var transactionalAttribute = ReflectionHelper.GetAttributesOfMemberAndDeclaringType<TransactionalAttribute>( methodInfo ).FirstOrDefault(); return transactionalAttribute; } }
واسط ISyncInterceptionBehavior، مربوط میشود به کتابخانه جانبی دیگری که برای AOP توسط تیم StructureMap به نام StructureMap.DynamicInterception ارائه شدهاست. در متد Intercept، ابتدا چک میشود که که آیا این متد با TransactionAttribute تزئین شده و طی درخواست جاری برای Context جاری تراکنشی ایجاد نشده باشد؛ سپس تراکنش جدیدی ایجاد شده و بدنه اصلی متد اجرا میشود و نهایتا در صورت موفقیت آمیز بودن عملیات، تراکنش مورد نظر Commit میشود.
در آخر لازم است این Interceptor در تنظیمات اولیه StructureMap به شکل زیر معرفی شود:
Policies.Interceptors(new DynamicProxyInterceptorPolicy( type => typeof(IApplicationService).IsAssignableFrom(type), typeof(AuthorizationInterceptor), typeof(TransactionInterceptor), typeof(ValidationInterceptor)));
نکته: فرض کنید در بدنه اکشن متد یک کنترلر ASP.NET MVC یا ASP.NET Core، دو متد تراکنشی فراخوانی شود؛ در این صورت شاید لازم باشد که این دو متد طی یک تراکنش واحد به جای تراکنشهای مجزا، اجرا شوند؛ بنابراین نیاز است از الگوی Transaction Per Request استفاده شود. برای این کار میتوان یک ActionFilterAttribute سفارشی ایجاد کرد که ایجاد کننده تراکنش باشد و متدهای داخلی که هر کدام جدا تراکنشی بودند، نیز از تراکنش ایجاد شده استفاده کنند.
class Car { } class CarProducer { public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; ... } }
class Transporter { public string Name { get; private set; } public Transporter(string name) { this.Name = name; } public void Deliver(Car[] cars, string town) { Console.WriteLine("Delivering {0} car(s) to {1} by {2}", cars.Length, town, this.Name); } }
static class TransporterLocator { static IList<Transporter> transporters = new List<Transporter>(); public static void Register(Transporter transporter) { transporters.Add(transporter); } public static Transporter Locate(string name) { return transporters .Where(transporter => transporter.Name == name) .Single(); } }
class CarProducer { public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; Transporter transporter = null; if (carsCount <= 12) transporter = TransporterLocator.Locate("truck"); else transporter = TransporterLocator.Locate("train"); transporter.Deliver(cars, town); } }
TransporterLocator.Register(new Transporter("truck")); TransporterLocator.Register(new Transporter("train")); CarProducer producer = new CarProducer(); producer.DeliverTo(7, "Tehran"); producer.DeliverTo(74, "Tehran");
TransporterLocator.Register(new Transporter("truck")); CarProducer producer = new CarProducer(); producer.DeliverTo(7, "Tehran"); producer.DeliverTo(74, "Tehran");
class CarProducer { private Transporter truck; private Transporter train; public CarProducer(Transporter truck, Transporter train) { if (truck == null) throw new ArgumentNullException("truck"); if (train == null) throw new ArgumentNullException("train"); this.truck = truck; this.train = train; } public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; Transporter transporter = this.truck; if (carsCount > 12) transporter = this.train; transporter.Deliver(cars, town); } }
آیا وضعیتی وجود دارد که در آن Service Locator یک راه حل قابل قبول باشد؟
در برخی موارد بجای اینکه وابستگیها را به صورت صریح قید کنیم، بهتر است از این الگو استفاده کنیم.
چه موقع باید یک Property نوشت؟
The Problem
What they neglect to say is all that testability and persistence ignorance flies right out the window when you create a new ASP.NET Web Application using the MVC template and "Individual User Accounts" authentication. What you get is a single-layered application, tightly coupled to Entity Framework, that:
-
Ignores the patterns that facilitate testing, including: the repository pattern, unit of work pattern, and dependency injection;
-
Forces you to implement their
IUser
interface in your application’s User entity, thereby coupling it to ASP.NET Identity; -
Eliminates any clear separation between your entities, persistence concerns, and business logic. Persistence ignorance? Forget about it.
Thankfully, due to the extensibility designed into ASP.NET Identity, it is possible to ditch the reference to the Microsoft.AspNet.Identity.EntityFramework
assembly and write a custom implementation that can address these and other architectural issues. Just be forewarned: it is not a trivial undertaking, and you’ll have to put up with some code smell that is baked into the Microsoft.AspNet.Identity.Core
assembly.
سیستم بلاگ cloudscribe.SimpleContent
تزریق وابستگیها
تزریق وابستگیها یا DI چیست؟
تزریق وابستگیها یکی از انواع IoC بوده که در آن ایجاد و انقیاد (binding) یک وابستگی، در خارج از کلاسی که به آن نیاز دارد صورت میگیرد. روشهای متفاوتی برای ارائه این وابستگی وهله سازی شده در خارج از کلاس به آن وجود دارد که در ادامه مورد بررسی قرار خواهند گرفت.
یک مثال: بسته غذایی را که با خود به سر کار میبرید درنظر بگیرید. تمام اجزای مورد نیاز تشکیل دهنده یک بسته غذا عموما داخل آن قرار گرفته و حمل میشوند. حالت عکس آن زمانی است که در محل کار به شما غذا میدهند. این دقیقا همان حالتی است که تزریق وابستگیها کار میکند؛ یک سری سرویسهای خارجی، نیازهای کلاس جاری را برآورده میسازند.
در تصویر فوق یک طراحی مبتنی بر معکوس سازی کنترلها را مشاهده میکنید. وابستگیهای یک کلاس توسط اینترفیسی مشخص شده و سپس کلاسی وجود دارد که این وابستگی را پیاده سازی کرده است.
همچنین در این تصویر یک خط منقطع از کلاس MyClass به کلاس Dependency رسم شده است. این خط، مربوط به حالتی است که خود کلاس، مستقیما کار وهله سازی وابستگی مورد نیاز خود را انجام دهد؛ هر چند اینترفیسی نیز در این بین تعریف شده باشد. بنابراین اگر در بین کدهای این کلاس، چنین کدی مشاهده شد:
IDependency dependency= new Dependency();
در اینجا یک کلاس دیگر به نام Injector اضافه شده است که قابلیت تزریق وابستگیها را به کلاس نیازمند آن به روشهای مختلفی دارا است. کار کلاس Injector، وهله سازی MyClass و همچنین وابستگیهای آن میباشد؛ سپس وابستگی را دراختیار MyClass قرار میدهد.
تزریق وابستگیها در سازنده کلاس
یکی از انواع روشهای تزریق وابستگیها، Constructor Injection و یا تزریق وابستگیها در سازنده کلاس میباشد که متداولترین نوع آنها نیز بهشمار میرود. در این حالت، وابستگی پس از وهله سازی، از طریق پارامترهای سازنده یک کلاس، در اختیار آن قرار میگیرند.
یک مثال:
public class Shopper { private readonly ICreditCard _creditCard; public Shopper(ICreditCard creditCard) { _creditCard = creditCard; } }
ICreditCard creditCard = new MasterCard(); Shopper shopper = new Shopper(creditCard);
تزریق در خواص عمومی کلاس
Setter Injection و یا تزریق در خواص عمومی یک کلاس، یکی دیگر از روشهای تزریق وابستگیها است (setter در اینجا منظور همان get و set یک خاصیت میباشد).
در حالت تزریق وابستگیها در سازنده کلاس، امکان وهله سازی آن کلاس بدون ارسال وابستگیها به سازنده آن ممکن نیست؛ اما در اینجا خیر و امکان وهله سازی و استفاده از یک کلاس، پیش از اعمال وابستگیها نیز وجود دارد. بنابراین امکان تغییر و تعویض وابستگیها پس از وهله سازی کلاس نیز میسر است.
public class Shopper { public ICreditCard CreditCard { get; set; } } ICreditCard creditCard = new MasterCard(); Shopper shopper = new Shopper(); shopper.CreditCard = creditCard;
تزریق اینترفیسها
تزریق اینترفیسها نیز یکی دیگر از روشهای تزریق وابستگیها است؛ اما آنچنان استفاده گستردهای برخلاف دو روش قبل نیافته است.
در این روش نه از سازنده کلاس استفاده میشود و نه از یک خاصیت عمومی. ابتدا یک اینترفیس که بیانگر ساختار کلاسی که قرار است تزریق شود ایجاد میگردد. سپس این اینترفیس را در کلاس وابستگی مورد نظر پیاده سازی خواهیم کرد. در این اینترفیس نیاز است متد خاصی تعریف شود تا کار تزریق وابستگی را انجام دهد.
یک مثال:
public interface IDependOnCreditCard { void Inject(ICreditCard creditCard); } public class Shopper : IDependOnCreditCard { private ICreditCard creditCard; public void Inject(ICreditCard creditCard) { this.creditCard = creditCard; } } ICreditCard creditCard = new MasterCard(); Shopper shopper = new Shopper(); ((IDependOnCreditCard)shopper).Inject(creditCard);
در ادامه کلاس خریدار، اینترفیس IDependOnCreditCard را پیاده سازی کرده است. به این ترتیب کلاس Injector تنها کافی است بداند تا این متد خاص را باید جهت تنظیم و تزریق وابستگیها فراخوانی نماید. این روش نسبتا شبیه به روش Setter injection است.
این روش بیشتر میتواند جهت فریم ورکهایی که قابلیت یافتن کلیه کلاسهای مشتق شده از IDependOnCreditCard را داشته و سپس میدانند که باید متد Inject آنها را فراخوانی کنند، مناسب است.
نکاتی که باید حین کار با تزریق وابستگیها درنظر داشت
الف) حین استفاده از تزریق وابستگیها، وهله سازی به تاخیر افتاده وابستگیها میسر نیست. برای مثال زمانیکه یک وابستگی قرار است در سازنده کلاسی تزریق شود، وهله سازی آن باید پیش از نیاز واقعی به آن صورت گیرد. البته امکان استفاده از اشیاء Lazy دات نت 4 برای مدیریت این مساله وجود دارد اما در حالت کلی، دیگر همانند قبل و روشهای متداول، وهله سازی تنها زمانیکه نیاز به آن وابستگی خاص باشد، میسر نیست. به همین جهت باید به تعداد وابستگیهایی که قرار است در یک کلاس استفاده شوند نیز جهت کاهش مصرف حافظه دقت داشت.
ب) یکی از مزایای دیگر تزریق وابستگیها، سادهتر شدن نوشتن آزمونهای واحد است. زیرا تهیه Mocks در این حالت کار با اینترفیسها بسیار سادهتر است. اما باید دقت داشت، کلاسی که در سازنده آن حداقل 10 اینترفیس را به عنوان وابستگی دریافت میکند، احتمالا دچار مشکلاتی در طراحی و همچنین مصرف حافظه میباشد.
- Custom tasks with default contextType via tasks.vs.json are broken in Open Folder.
- No snapshot created for C++ native code in Memory Usage tool in the Diagnostic Tools window while debugging.
- Crash in VS 16.1.0 when pressing the link "search online".
- Fixed the issue where sometimes certain features (i.e. Find All References) in LiveShare guest session do not work.
- Fixed an issue where the devenv.exe process could hang around after shutdown of Visual Studio for up to 30 seconds.