2

I tried to replace validation from my previous project on Asp.net MVC 4 to Asp.net Core. And have some problems. The flow in Asp.net Core project like that:

Middleware => ControllerCTOR => FluValidator => Filter => Action

Also when some of Rules in FluValidator failed it's just return response with errors through Middleware stack to client. But I need to have access to ModelState in Filter or in Action.

Why this don`t work correct? Or, if it's actually right flow, how to make it go deeper to Action?

Startup

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
                        {
                            options.Filters.Add(typeof(ValidateModelAttribute));
                        })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());

    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddNLog();
        env.ConfigureNLog("nlog.config");

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();

        // Enable middleware to serve generated Swagger as a JSON endpoint.
        app.UseSwagger();

        // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 
        // specifying the Swagger JSON endpoint.
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "CorpLight API V1");
        });

        app.UseMiddleware<RequestResponseLoggingMiddleware>();
        app.UseMiddleware<ErrorHandlingMiddleware>();
        app.UseMiddleware<AuthenticateMiddleware>();

        app.UseMvc();
    }

Middleware

    private readonly RequestDelegate _next;

    public ErrorHandlingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

Validator

public class CardInformationRequestValidator : AbstractValidator<RequestModel<CardInformationRequest>>
{
    public CardInformationRequestValidator()
    {
        RuleFor(x => x.Request.RU)
            .NotNull()
            .NotEmpty();

        RuleFor(x => x.Request.Currency)
            .NotNull()
            .NotEmpty();

        RuleFor(x => x.Request.AccountNumber)
            .NotNull()
            .NotEmpty();
    }
}

Controller

[Route("api/[controller]")]
[ApiController]
public class CardController : ControllerBase
{
    private readonly ICardRepo _cardRepo;
    private readonly IMapper _mapper;

    public CardController(ICardRepo cardRepo, IMapper mapper)
    {
        _cardRepo = cardRepo;
        _mapper = mapper;
    }

    [HttpPost]
    public async Task<MessageWithElements<CardInformation, CardInfo>> CardInformations(RequestModel<CardInformationRequest> request)
    {
        if (!ModelState.IsValid)
            throw new InvalidParametersException($"can't be empty");

         //logic

    }
}

Filter

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            //logic
        }
    }
}

Typical Valid Json:

{ 
  "request": {
    "ru": "string",
    "accountNumber": "string",
    "currency": 1
  }
}

Typical Invalid Json:

{ 
  "request": {
    "ru": "string",
    "accountNumber": "string",
    "currency": 0
  }
}

When currency NOT zero it's valid, and reach filter. But when it's zero, NotEmpty become failded and flow go back.

Typical response with Valid request:

{
  "elements": [
    {
      <object fields>
    }
  ],
  "messageText": "string",
  "messageNumber": 1
}

Typical response with Invalid request (400 Bad Request):

{
  "Request.Currency": [
    "'Request. Currency' must not be empty."
  ]
} 
4
  • Please specify what actually is not working correctly. Having access to the ModelState or something else? Describe the problem more clearly, it's too broad. Regards. Commented Feb 19, 2019 at 15:14
  • For example I start debug. If send valid request it will go through Middleware to ControllerCTOR, then Fluentvalidator starts check model by Rules. As model is valid property IsValid in ModelState become true. And request goes to Filter and then to Action where I can check is ModelState valid and make some format response. BUT if model is invalid. It just return response with error by failed rules without reaching Filter or Action. Commented Feb 19, 2019 at 15:21
  • Please add code where you app.UseYourMiddleware Commented Feb 19, 2019 at 15:44
  • Done. Add Configure method of Startup Commented Feb 19, 2019 at 15:58

2 Answers 2

1

Execution flow actually reaches both ValidateModelAttribute and action even if the model isn't valid. But there is a specific case when Request property is null and CardInformationRequestValidator throws an exception during validation. For example when the validator tries to check this rule

RuleFor(x => x.Request.RU)
    .NotNull()
    .NotEmpty();

it tries to get RU property value but it throws NullReferenceException because x.Request is null. So the solution is to update validation logic

public CardInformationRequestValidator()
{
    RuleFor(x => x.Request)
        .NotNull()
        .DependentRules(() =>
        {
            RuleFor(x => x.Request.RU)
                .NotNull()
                .NotEmpty();

            RuleFor(x => x.Request.Currency)
                .NotNull()
                .NotEmpty();

            RuleFor(x => x.Request.AccountNumber)
                .NotNull()
                .NotEmpty();
        });
}

Read more in docs and on github.

Sign up to request clarification or add additional context in comments.

11 Comments

Nope. Request object filled. Example: { "request": { "ru": "string", "accountNumber": "string", "currency": 0 } } When currency NOT zero it's valid, and reach filter. But when it's zero NotEmpty become failded and flow go back. I don't know why, maybe some configurations prevent going further if validator failed.
@RomanMakarenko Hmm this is strange because it works fine for me. And, if you are sending json you are missing [FromBody] before action parameter otherwise it's not binded I believe.
@RomanMakarenko Also, please add what output you have when the input is invalid.
added response body to question article.
Also I add [FromBody] and there is no changes.
|
1

I have found the solution. The problem was in Filter. Because of OnActionExecuting method request never reach. After validation if there are any failed Rule context goes straight to OnResultExecution and return response.

public class ValidateModelFilter : Attribute, IAsyncResultFilter
    {
        public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            if (!context.ModelState.IsValid)
                throw new InvalidParametersException(context.ModelState.StringErrors());

            await next();
        }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.