Commit e1abb5b0 authored by 阿星Plus's avatar 阿星Plus

UnitOfWorkRegistrar.Initialize

parent 56ba1ef1
...@@ -3,6 +3,7 @@ using System.Collections.Generic; ...@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
namespace Plus.Collections namespace Plus.Collections
{ {
...@@ -11,6 +12,11 @@ namespace Plus.Collections ...@@ -11,6 +12,11 @@ namespace Plus.Collections
/// </summary> /// </summary>
public static class Extensions public static class Extensions
{ {
public static bool IsAsync(this MethodInfo method)
{
return method.ReturnType == typeof(Task) || (method.ReturnType.GetTypeInfo().IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>));
}
public static List<T> SortByDependencies<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies) public static List<T> SortByDependencies<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies)
{ {
var sorted = new List<T>(); var sorted = new List<T>();
......
using System;
using System.Transactions;
namespace Plus.Domain.Uow
{
/// <summary>
/// This attribute is used to indicate that declaring method is atomic and should be considered as a unit of work.
/// A method that has this attribute is intercepted, a database connection is opened and a transaction is started before call the method.
/// At the end of method call, transaction is committed and all changes applied to the database if there is no exception,
/// otherwise it's rolled back.
/// </summary>
/// <remarks>
/// This attribute has no effect if there is already a unit of work before calling this method, if so, it uses the same transaction.
/// </remarks>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)]
public class UnitOfWorkAttribute : Attribute
{
/// <summary>
/// Scope option.
/// </summary>
public TransactionScopeOption? Scope { get; set; }
/// <summary>
/// Is this UOW transactional?
/// Uses default value if not supplied.
/// </summary>
public bool? IsTransactional { get; set; }
/// <summary>
/// Timeout of UOW As milliseconds.
/// Uses default value if not supplied.
/// </summary>
public TimeSpan? Timeout { get; set; }
/// <summary>
/// If this UOW is transactional, this option indicated the isolation level of the transaction.
/// Uses default value if not supplied.
/// </summary>
public IsolationLevel? IsolationLevel { get; set; }
/// <summary>
/// Used to prevent starting a unit of work for the method.
/// If there is already a started unit of work, this property is ignored.
/// Default: false.
/// </summary>
public bool IsDisabled { get; set; }
/// <summary>
/// Creates a new UnitOfWorkAttribute object.
/// </summary>
public UnitOfWorkAttribute()
{
}
/// <summary>
/// Creates a new <see cref="UnitOfWorkAttribute"/> object.
/// </summary>
/// <param name="isTransactional">
/// Is this unit of work will be transactional?
/// </param>
public UnitOfWorkAttribute(bool isTransactional)
{
IsTransactional = isTransactional;
}
/// <summary>
/// Creates a new <see cref="UnitOfWorkAttribute"/> object.
/// </summary>
/// <param name="timeout">As milliseconds</param>
public UnitOfWorkAttribute(int timeout)
{
Timeout = TimeSpan.FromMilliseconds(timeout);
}
/// <summary>
/// Creates a new <see cref="UnitOfWorkAttribute"/> object.
/// </summary>
/// <param name="isTransactional">Is this unit of work will be transactional?</param>
/// <param name="timeout">As milliseconds</param>
public UnitOfWorkAttribute(bool isTransactional, int timeout)
{
IsTransactional = isTransactional;
Timeout = TimeSpan.FromMilliseconds(timeout);
}
/// <summary>
/// Creates a new <see cref="UnitOfWorkAttribute"/> object.
/// <see cref="IsTransactional"/> is automatically set to true.
/// </summary>
/// <param name="isolationLevel">Transaction isolation level</param>
public UnitOfWorkAttribute(IsolationLevel isolationLevel)
{
IsTransactional = true;
IsolationLevel = isolationLevel;
}
/// <summary>
/// Creates a new <see cref="UnitOfWorkAttribute"/> object.
/// <see cref="IsTransactional"/> is automatically set to true.
/// </summary>
/// <param name="isolationLevel">Transaction isolation level</param>
/// <param name="timeout">Transaction timeout as milliseconds</param>
public UnitOfWorkAttribute(IsolationLevel isolationLevel, int timeout)
{
IsTransactional = true;
IsolationLevel = isolationLevel;
Timeout = TimeSpan.FromMilliseconds(timeout);
}
/// <summary>
/// Creates a new <see cref="UnitOfWorkAttribute"/> object.
/// <see cref="IsTransactional"/> is automatically set to true.
/// </summary>
/// <param name="scope">Transaction scope</param>
public UnitOfWorkAttribute(TransactionScopeOption scope)
{
IsTransactional = true;
Scope = scope;
}
/// <summary>
/// Creates a new <see cref="UnitOfWorkAttribute"/> object.
/// </summary>
/// <param name="scope">Transaction scope</param>
/// <param name="isTransactional">
/// Is this unit of work will be transactional?
/// </param>
public UnitOfWorkAttribute(TransactionScopeOption scope, bool isTransactional)
{
Scope = scope;
IsTransactional = isTransactional;
}
/// <summary>
/// Creates a new <see cref="UnitOfWorkAttribute"/> object.
/// <see cref="IsTransactional"/> is automatically set to true.
/// </summary>
/// <param name="scope">Transaction scope</param>
/// <param name="timeout">Transaction timeout as milliseconds</param>
public UnitOfWorkAttribute(TransactionScopeOption scope, int timeout)
{
IsTransactional = true;
Scope = scope;
Timeout = TimeSpan.FromMilliseconds(timeout);
}
internal UnitOfWorkOptions CreateOptions()
{
return new UnitOfWorkOptions
{
IsTransactional = IsTransactional,
IsolationLevel = IsolationLevel,
Timeout = Timeout,
Scope = Scope
};
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Plus.Domain.Uow
{
internal static class UnitOfWorkDefaultOptionsExtensions
{
public static UnitOfWorkAttribute GetUnitOfWorkAttributeOrNull(this IUnitOfWorkDefaultOptions unitOfWorkDefaultOptions, MethodInfo methodInfo)
{
var attrs = methodInfo.GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Length > 0)
{
return attrs[0];
}
attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Length > 0)
{
return attrs[0];
}
if (unitOfWorkDefaultOptions.IsConventionalUowClass(methodInfo.DeclaringType))
{
return new UnitOfWorkAttribute(); //Default
}
return null;
}
public static bool IsConventionalUowClass(this IUnitOfWorkDefaultOptions unitOfWorkDefaultOptions, Type type)
{
return unitOfWorkDefaultOptions.ConventionalUowSelectors.Any(selector => selector(type));
}
}
}
\ No newline at end of file
using System.Reflection;
namespace Plus.Domain.Uow
{
/// <summary>
/// A helper class to simplify unit of work process.
/// </summary>
internal static class UnitOfWorkHelper
{
/// <summary>
/// Returns true if given method has UnitOfWorkAttribute attribute.
/// </summary>
/// <param name="memberInfo">Method info to check</param>
public static bool HasUnitOfWorkAttribute(MemberInfo memberInfo)
{
return memberInfo.IsDefined(typeof(UnitOfWorkAttribute), true);
}
}
}
\ No newline at end of file
using Castle.DynamicProxy;
using Plus.Collections;
using System.Reflection;
using System.Threading.Tasks;
namespace Plus.Domain.Uow
{
/// <summary>
/// This interceptor is used to manage database connection and transactions.
/// </summary>
internal class UnitOfWorkInterceptor : IInterceptor
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IUnitOfWorkDefaultOptions _unitOfWorkOptions;
public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkDefaultOptions unitOfWorkOptions)
{
_unitOfWorkManager = unitOfWorkManager;
_unitOfWorkOptions = unitOfWorkOptions;
}
/// <summary>
/// Intercepts a method.
/// </summary>
/// <param name="invocation">Method invocation arguments</param>
public void Intercept(IInvocation invocation)
{
MethodInfo method;
try
{
method = invocation.MethodInvocationTarget;
}
catch
{
method = invocation.GetConcreteMethod();
}
var unitOfWorkAttr = _unitOfWorkOptions.GetUnitOfWorkAttributeOrNull(method);
if (unitOfWorkAttr == null || unitOfWorkAttr.IsDisabled)
{
//No need to a uow
invocation.Proceed();
return;
}
//No current uow, run a new one
PerformUow(invocation, unitOfWorkAttr.CreateOptions());
}
private void PerformUow(IInvocation invocation, UnitOfWorkOptions options)
{
if (invocation.Method.IsAsync())
{
PerformAsyncUow(invocation, options);
}
else
{
PerformSyncUow(invocation, options);
}
}
private void PerformSyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
using (var uow = _unitOfWorkManager.Begin(options))
{
invocation.Proceed();
uow.Complete();
}
}
private void PerformAsyncUow(IInvocation invocation, UnitOfWorkOptions options)
{
var uow = _unitOfWorkManager.Begin(options);
try
{
invocation.Proceed();
}
catch
{
uow.Dispose();
throw;
}
if (invocation.Method.ReturnType == typeof(Task))
{
invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(
(Task)invocation.ReturnValue,
async () => await uow.CompleteAsync(),
exception => uow.Dispose()
);
}
else //Task<TResult>
{
invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult(
invocation.Method.ReturnType.GenericTypeArguments[0],
invocation.ReturnValue,
async () => await uow.CompleteAsync(),
exception => uow.Dispose()
);
}
}
}
}
\ No newline at end of file
using Castle.Core;
using Castle.MicroKernel;
using Plus.Dependency;
using System.Linq;
using System.Reflection;
namespace Plus.Domain.Uow
{
internal static class UnitOfWorkRegistrar
{
/// <summary>
/// Initializes the registerer.
/// </summary>
/// <param name="iocManager">IOC manager</param>
public static void Initialize(IIocManager iocManager)
{
iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
{
var implementationType = handler.ComponentModel.Implementation.GetTypeInfo();
HandleTypesWithUnitOfWorkAttribute(implementationType, handler);
HandleConventionalUnitOfWorkTypes(iocManager, implementationType, handler);
};
}
private static void HandleTypesWithUnitOfWorkAttribute(TypeInfo implementationType, IHandler handler)
{
if (IsUnitOfWorkType(implementationType) || AnyMethodHasUnitOfWork(implementationType))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
}
}
private static void HandleConventionalUnitOfWorkTypes(IIocManager iocManager, TypeInfo implementationType, IHandler handler)
{
if (!iocManager.IsRegistered<IUnitOfWorkDefaultOptions>())
{
return;
}
var uowOptions = iocManager.Resolve<IUnitOfWorkDefaultOptions>();
if (uowOptions.IsConventionalUowClass(implementationType.AsType()))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor)));
}
}
private static bool IsUnitOfWorkType(TypeInfo implementationType)
{
return UnitOfWorkHelper.HasUnitOfWorkAttribute(implementationType);
}
private static bool AnyMethodHasUnitOfWork(TypeInfo implementationType)
{
return implementationType
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Any(UnitOfWorkHelper.HasUnitOfWorkAttribute);
}
}
}
\ No newline at end of file
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace Plus
{
internal static class InternalAsyncHelper
{
public static async Task AwaitTaskWithFinally(Task actualReturnValue, Action<Exception> finalAction)
{
Exception exception = null;
try
{
await actualReturnValue;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}
public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
try
{
await actualReturnValue;
await postAction();
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}
public static async Task AwaitTaskWithPreActionAndPostActionAndFinally(Func<Task> actualReturnValue, Func<Task> preAction = null, Func<Task> postAction = null, Action<Exception> finalAction = null)
{
Exception exception = null;
try
{
if (preAction != null)
{
await preAction();
}
await actualReturnValue();
if (postAction != null)
{
await postAction();
}
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction?.Invoke(exception);
}
}
public static async Task<T> AwaitTaskWithFinallyAndGetResult<T>(Task<T> actualReturnValue, Action<Exception> finalAction)
{
Exception exception = null;
try
{
return await actualReturnValue;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}
public static object CallAwaitTaskWithFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Action<Exception> finalAction)
{
return typeof(InternalAsyncHelper).GetMethod("AwaitTaskWithFinallyAndGetResult", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(taskReturnType).Invoke(null, new object[2]
{
actualReturnValue,
finalAction
});
}
public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction)
{
Exception exception = null;
try
{
T result = await actualReturnValue;
await postAction();
return result;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction(exception);
}
}
public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Func<Task> action, Action<Exception> finalAction)
{
return typeof(InternalAsyncHelper).GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(taskReturnType).Invoke(null, new object[3]
{
actualReturnValue,
action,
finalAction
});
}
public static async Task<T> AwaitTaskWithPreActionAndPostActionAndFinallyAndGetResult<T>(Func<Task<T>> actualReturnValue, Func<Task> preAction = null, Func<Task> postAction = null, Action<Exception> finalAction = null)
{
Exception exception = null;
try
{
if (preAction != null)
{
await preAction();
}
T result = await actualReturnValue();
if (postAction != null)
{
await postAction();
}
return result;
}
catch (Exception ex)
{
exception = ex;
throw;
}
finally
{
finalAction?.Invoke(exception);
}
}
public static object CallAwaitTaskWithPreActionAndPostActionAndFinallyAndGetResult(Type taskReturnType, Func<object> actualReturnValue, Func<Task> preAction = null, Func<Task> postAction = null, Action<Exception> finalAction = null)
{
return typeof(InternalAsyncHelper).GetMethod("AwaitTaskWithPreActionAndPostActionAndFinallyAndGetResult", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(taskReturnType).Invoke(null, new object[4]
{
actualReturnValue,
preAction,
postAction,
finalAction
});
}
}
}
\ No newline at end of file
...@@ -3,6 +3,7 @@ using Castle.MicroKernel.Registration; ...@@ -3,6 +3,7 @@ using Castle.MicroKernel.Registration;
using Plus.Configuration.Startup; using Plus.Configuration.Startup;
using Plus.Dependency; using Plus.Dependency;
using Plus.Dependency.Installers; using Plus.Dependency.Installers;
using Plus.Domain.Uow;
using Plus.Modules; using Plus.Modules;
using Plus.Runtime.Validation.Interception; using Plus.Runtime.Validation.Interception;
using System; using System;
...@@ -99,7 +100,7 @@ namespace Plus ...@@ -99,7 +100,7 @@ namespace Plus
public void AddInterceptorRegistrars() public void AddInterceptorRegistrars()
{ {
ValidationInterceptorRegistrar.Initialize(IocManager); ValidationInterceptorRegistrar.Initialize(IocManager);
//UnitOfWorkRegistrar.Initialize(IocManager); UnitOfWorkRegistrar.Initialize(IocManager);
} }
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment