AOP for C# dotnet core 2.0, access method paramete

2020-06-25 05:25发布

问题:

This is my method, I am trying to validate componentToSave (or access method parameter values) and throw an exception before method body even runs.

public Component SaveComponent(Component componentToSave) {
    ...
}
  1. I tried using PostSharp but it is not free and also there were other libraries that rely on AutoFac as IoC but in my current setup I am using dotnet core's built-in dependency injection.

  2. I tried NConcern and it relies on CNeptune and CNeptune itself relies on a .exe file for post-compile binding and I currently use Linux for both development and production so I cannot use it, even I tried testing with it on windows but could not get it to work with dotnet core.

  3. I tried this approach (i.e. ActionFilter and ServiceFilter) but I only got it working if [ServiceFilter(typeof(LoggingActionFilter))] is over controller not any other method (i.e. SaveComponent method).

  4. I tried using RealProxy but apparently, it is not supported in dotnet core.

I am just lost, maybe I am over complicating the problem but there should be a way. Any help would be greatly appreciated.

回答1:

Look into CQS, decorators and SimpleInjector. If you promote methods to classes, you can have the class dedicated to one thing (SOLID). Then you can add cross cutting concerns on decorators that will have the same interface as the implementation, but they essential chain the calls. if validation decorator fails, then your main logic won't ever be called. You can even add all exception handling here and any logging or retry logic or caching.

Edit

Sorry, was on mobile before! :)

For an example, I'll use your method here. Normally with CQS, you'd have a generic interface for all your queries (read-only) and commands (change state). That way, all your logic ends up going through a IQueryHandler or ICommandHandler so you can add cross cutting concerns to ALL your logic all at once. However, I'll make an example specific to your question.

public interface ISaveComponent
{
    Component SaveComponent(Component componentToSave);
}

public class SaveComponent : ISaveComponent
{
    public Component SaveComponent(Component componentToSave)
    {
        // Do your work here
    }
}

public class SaveComponentValidation : ISaveComponent
{
    private readonly ISaveComponent _saveComponent;

    public SaveComponentValidation(ISaveComponent saveCompnent)
    {
        _saveComponent = saveCompnent;
    }

    public Component SaveComponent(Component componentToSave)
    {
        // Do Validation here
        return _saveComponent.SaveComponent(componentToSave);
    }
}

If you let SimpleInjector (IoC/DI) handle the decorations for you, then you just have to register them in one line of code like this:

 container.RegisterDecorator(typeof(ISaveComponent), typeof(SaveComponentValidation));

Otherwise, you would have to manually create them like this:

public class Program
{
    public static void Main()
    {
        ISaveComponent handler = new SaveComponentValidation(new SaveComponent());
        handler.SaveComponent(new Component());
    }
}


回答2:

Validating method arguments before the method body even runs can be achieved by creating a dynamic proxy, which intercepts method calls and executes your validation logic.

One dynamic proxy implementation that is supported in .NET Core is provided by Castle.Core package.

However, in my personal experience, implementing these dynamic proxies takes some getting used to and often consists of quite some boiler plate code.

To make the process of dynamically decorating your methods simpler, I created a wrapper package Decor.NET and released it under MIT licence. Following are the instructions on how to achieve the behaviour you are asking.


  1. Install packages.

Install-Package Decor

Install-Package Decor.Extensions.Microsoft.DependencyInjection

  1. Create a decorator, which contains validation logic.
public class ComponentValidator : IDecorator
{    
    public async Task OnInvoke(Call call)
    {
        var componentToSave = (Component)call.Arguments[0];

        if (/* Your validation logic */)
            throw new Exception("Something's not right.");

        await call.Next();
    }
}
  1. Add [Decorate(typeof(ComponentValidator))] attribute to the method(s) you want to validate.
[Decorate(typeof(ComponentValidator))]
public virtual Component SaveComponent(Component componentToSave) {
    ...
}
  1. Register Decor.NET, validation decorator and decorated class using the extension methods provided by Decor.Extensions.Microsoft.DependencyInjection.
services.AddDecor()
    .AddTransient<ComponentValidator>()
    .AddScoped<SomeService>().Decorated();

Notice that the decorated method has to be overridable (marked as virtual or implemented from interface). This is needed for dynamic proxy to override method's implementation.