[AOP Series]Autofac+Castle Implements AOP Transactions

Keywords: C# Attribute Spring Unity

1. Preface

Recent new projects in the company need to be developed with architectures, which need to ensure transaction consistency. After searching, we found that many of the posts are AOP implemented through Spring.Net, Unity, PostSharp, Castle Windsor.But that's not what I want, so after a search, use Autofac, DynamicProxy to implement AOP.

2. Advantages of using AOP

Bloggers find its advantages mainly in:

  • By pulling common functionality out of business logic, you can omit a lot of duplicate code, which is beneficial to the operation and maintenance of code.
  • In software design, extract common functions (facets) to facilitate the modularization of software design and reduce the complexity of software architecture.That is to say, a common function is a separate module, the design code of which can not be seen in the main business of the project.

3. Reference Library

  • Autofac: 4.6
  • Autofac.Extras.DynamicProxy: 4.1.0
  • Castle.Core: 3.2.2

4. Ideas for implementation

4.1 Defining attributes

Defines an attribute that is included in the current method to determine whether an open transaction is open, and if it exists, the transaction is opened, otherwise the transaction is ignored.
Transaction properties can set timeout, transaction scope, and transaction isolation level.
The code is as follows:

/// <summary>
///Open Transaction Properties
/// </summary>
[AttributeUsage(AttributeTargets.Method,Inherited = true)]
public class TransactionCallHandlerAttribute:Attribute
{
    /// <summary>
    ///Timeout
    /// </summary>
    public int Timeout { get; set; }

    /// <summary>
    ///Transaction Scope
    /// </summary>
    public TransactionScopeOption ScopeOption { get; set; }

    /// <summary>
    ///Transaction isolation level
    /// </summary>
    public IsolationLevel IsolationLevel { get; set; }

    public TransactionCallHandlerAttribute()
    {
        Timeout = 60;
        ScopeOption=TransactionScopeOption.Required;
        IsolationLevel=IsolationLevel.ReadCommitted;
    }
}

4.2 Face Realization

Gets whether the current method contains the TransactionCallHandlerAttribute property, and if it does, opens the transaction.
I add the development mode judgment here to solve the problem that MSDTC is not set up to cause exceptions if it is not needed.
In addition, the logging function can be implemented by itself.
The code is as follows:

/// <summary>
///Transaction Interceptor
/// </summary>
public class TransactionInterceptor:IInterceptor
{
    //Self-implemented logger, ignored here
    /// <summary>
    ///Logger
    /// </summary>
    private static readonly ILog Logger = Log.GetLog(typeof(TransactionInterceptor));

    // Is development mode or not
    private bool isDev = false;
    public void Intercept(IInvocation invocation)
    {
        if (!isDev)
        {
            MethodInfo methodInfo = invocation.MethodInvocationTarget;
            if (methodInfo == null)
            {
                methodInfo = invocation.Method;
            }
                            
            TransactionCallHandlerAttribute transaction =
                methodInfo.GetCustomAttributes<TransactionCallHandlerAttribute>(true).FirstOrDefault();
            if (transaction != null)
            {
                TransactionOptions transactionOptions = new TransactionOptions();
                //Set Transaction Isolation Level
                transactionOptions.IsolationLevel = transaction.IsolationLevel;
                //Set transaction timeout to 60 seconds
                transactionOptions.Timeout = new TimeSpan(0, 0, transaction.Timeout);
                using (TransactionScope scope = new TransactionScope(transaction.ScopeOption, transactionOptions))
                {
                    try
                    {
                        //Implement transactional work
                        invocation.Proceed();
                        scope.Complete();
                    }
                    catch (Exception ex)
                    {
                        // Record exceptions
                        throw ex;
                    }
                    finally
                    {
                        //Release Resources
                        scope.Dispose();
                    }
                }
            }
            else
            {
                // Execute method directly without transaction
                invocation.Proceed();
            }
        }
        else
        {
            // Development mode skips interception directly
            invocation.Proceed();
        }
    }
}

4.3 Section Injection

The blogger encapsulated Autofac, which may not be the same as your configuration, but the content of the Load(ContainerBuilder builder) method is consistent, so the injection method is consistent.
By defining an IDependency empty interface, classes that need to be injected can inherit that interface.
The code is as follows:

/// <summary>
///Application IOC Configuration
/// </summary>
public class IocConfig : ConfigBase
{
    // Override Load Configuration
    protected override void Load(ContainerBuilder builder)
    {
        var assembly = this.GetType().GetTypeInfo().Assembly;
        builder.RegisterType<TransactionInterceptor>();
        builder.RegisterAssemblyTypes(assembly)
            .Where(type => typeof(IDependency).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract)
            .AsImplementedInterfaces()
            .InstancePerLifetimeScope()
            .EnableInterfaceInterceptors()
            .InterceptedBy(typeof(TransactionInterceptor));
    }
}

5. Examples

/// <summary>
///Add Article
/// </summary>
/// <param name="name"></param>
[TransactionCallHandler]
public void AddArticle(string name)
{
    BasArticle model=new BasArticle();
    model.ArticleID = Guid.Empty;//Deliberately repeat to determine if rollback will occur.
    model.Code = TimestampId.GetInstance().GetId();
    model.Name = name;
    model.Status = 1;
    model.Creater = "test";
    model.Editor = "test";
    this._basArticleRepository.Insert(model);            
}

Posted by neclord81 on Wed, 12 Jun 2019 10:24:41 -0700