simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.22k stars 152 forks source link

Using ActionFilterAttribute with Simple injector #919

Closed sarabu07 closed 3 years ago

sarabu07 commented 3 years ago

We are using fluent validation library and use ActionFilterAttribute for custom handling the validation events. Please see our code below. Let me know how to integrate Simple Injector with this example.

public class ValidateModelAttribute : ActionFilterAttribute
{
    private readonly IValidatorFactory _factory; //Using FluentValidation
    private readonly ILogger _logger; // using Serilog

    public ValidateModelAttribute(IValidatorFactory factory, ILogger logger)
    {
        _factory = factory;
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var result = new ValidationResult();

        if (!context.ActionArguments.Any() && !context.ModelState.IsValid)
        {

            var failures = new List<ValidationFailure>();

            foreach (var key in context.ModelState.Keys)
            {
                failures.Add(new ValidationFailure(key,
                    "can't process the input data. please validate your input"));
            }

            result = new ValidationResult(failures);
        }

        if (context.ActionArguments.Any())
        {
            foreach (var arg in context.ActionArguments)
            {
                //skip null values
                if (arg.Value == null)
                    continue;

                var validator = _factory.GetValidator(arg.Value.GetType());

                //skip objects with no validators
                if (validator == null)
                    continue;

                //validate
                result = validator.Validate(arg.Value);
            }
        }
    }
}
dotnetjunkie commented 3 years ago

Does this help? If not, please provide me with more information on what it is you are trying to achieve, what you tried, and what you're stuck with.

sarabu07 commented 3 years ago

@dotnetjunkie We are using Asp.Net core and .Net core 3.1. The above example seems to be for .Net framework. Is it still valid for .Net core 3.1?

dotnetjunkie commented 3 years ago

Is it still valid for .Net core 3.1?

Don't know. Did you check?

sarabu07 commented 3 years ago

@dotnetjunkie I get

'FluentValidationModelValidatorProvider' is inaccessible due to its protection level error.

Also, I get

System.InvalidOperationException: Unable to resolve service for type 'Serilog.ILogger' while attempting to activate

and didn't get any error for IValidatorFactory interface. We use below code to use Fluent Validation.

services.AddMvc(options =>
    {
        options.Filters.Add(typeof(ValidateModelAttribute));
        options.EnableEndpointRouting = false;
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
    .AddXmlSerializerFormatters()
    .AddFluentValidation(options =>
    {
        options.RegisterValidatorsFromAssemblyContaining<Startup>();
        // Avoid MVC modelstate validation
        options.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
    });
dotnetjunkie commented 3 years ago

Unable to resolve service for type 'Serilog.ILogger' while attempting to activate'

How is that related to the integration Fluent Validation? Does Fluent Validation depend on and use Serilog? Or do you usr Serilog in your application?

sarabu07 commented 3 years ago

@dotnetjunkie we use Serilog in our ValidateModelAttribute class to log the errors. Please refer to my first post for code.

public ValidateModelAttribute(IValidatorFactory factory, ILogger logger)
{
    _factory = factory;
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
sarabu07 commented 3 years ago

@dotnetjunkie We are using global action filter with the name 'ValidateModelAttribute' and if i remove Ilogger from construction the code is working.

public ValidateModelAttribute(IValidatorFactory factory)
{
    _factory = factory;
    //_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

In simple injector we could not pass any additional parameter to global filter constructor ? or Im missing something

dotnetjunkie commented 3 years ago

You are adding your filter attribute to MVC's Filters collection, but the list of filters is tighly coupled to the built-in DI container. To circumvent this, you'll need to wrap the type in a proxy class, similar to the proxy described in this Stack Overflow answer: https://stackoverflow.com/a/43569535

sarabu07 commented 3 years ago

@dotnetjunkie Thanks, When i implemented in my code i get

Argument 1: cannot convert from 'T.SimpleInjectiorAsyncActionFilterProxy' to 'System.Type'

services.AddMvc(mvc =>
    {
        mvc.Filters.Add(new SimpleInjectiorAsyncActionFilterProxy<ValidateModelAttribute>(container);
        mvc.EnableEndpointRouting = false;
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
    .AddFluentValidation(options =>
    {
        options.RegisterValidatorsFromAssemblyContaining<Startup>();
        // Avoid MVC modelstate validation
        options.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
    });
dotnetjunkie commented 3 years ago

Try this:

mvc.Filters.Add(typeof(SimpleInjectiorAsyncActionFilterProxy<ValidateModelAttribute>));
sarabu07 commented 3 years ago

@dotnetjunkie Thanks. Now the DI is injecting for Logger but getting

The type 'D.SimpleInjectiorAsyncActionFilterProxy`1[[D.CustomValidator, D, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' must derive from 'Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata'. (Parameter 'filterType')

Please see below my proxy class.

public sealed class SimpleInjectiorAsyncActionFilterProxy<TAsyncActionFilter>
    where TAsyncActionFilter : ActionFilterAttribute, IFilterMetadata
{
    private readonly Container container;
    public SimpleInjectiorAsyncActionFilterProxy(Container container)
    {
        this.container = container;
    }

    public void OnActionExecuting(ActionExecutingContext c)
    {
        container.GetInstance<TAsyncActionFilter>().OnActionExecuting(c);
    }
}
dotnetjunkie commented 3 years ago

Change the definitioncof your proxy to:

public sealed class SimpleInjectiorAsyncActionFilterProxy<TAsyncActionFilter
    : IFilterMetadata
    where TAsyncActionFilter : ActionFilterAttribute, IFilterMetadata
sarabu07 commented 3 years ago

@dotnetjunkie Thanks now no errors, I see DI injected but OnActionExecuting is not getting fired

sarabu07 commented 3 years ago
public sealed class SimpleInjectiorAsyncActionFilterProxy<TAsyncActionFilter>
    : IFilterMetadata where TAsyncActionFilter : ActionFilterAttribute, IFilterMetadata
{
    private readonly Container container;
    public SimpleInjectiorAsyncActionFilterProxy(Container container)
    {
        this.container = container;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        container.GetInstance<TAsyncActionFilter>().OnActionExecuting(context);
    }
}
sarabu07 commented 3 years ago

DI code

container.Register<ValidateModelAttribute >();

dotnetjunkie commented 3 years ago

I'm sorry, but I'm on holiday roght now, so am unable to help you any further at the moment. Hopefully you'll be able to figure out how to get this working. Ley me know if you figured it out.

Cheers

sarabu07 commented 3 years ago

@dotnetjunkie Sorry to bother in your vacation. Happy vacation!.

sarabu07 commented 3 years ago

@dotnetjunkie The below code for simple injector action proxy worked. Thanks for your help.

public sealed class SimpleInjectiorAsyncActionFilterProxy<T>
    : IFilterFactory where T : ActionFilterAttribute, IFilterMetadata
{
    private readonly Container container;
    public SimpleInjectiorAsyncActionFilterProxy(Container container)
    {
        this.container = container;
    }

    bool IFilterFactory.IsReusable { get; }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        container.GetInstance<T>().OnActionExecuting(context);
    }

    IFilterMetadata IFilterFactory.CreateInstance(IServiceProvider serviceProvider)
    {
        return container.GetInstance<T>();
    }
}