Eshoponcontainers: Help Wanted - Struggling To Understand Domain Validations - No Fluid Validation Error Messages Displayed In JSON Response

Created on 26 Feb 2020  路  4Comments  路  Source: dotnet-architecture/eShopOnContainers

Hi,

I am trying to understand DDD architecture and thus investigating the code for eShopContainers to aid my understanding.

I am struggling to understand validation of command data models using FluidValidation. Without adapting the source code further, the validation errors are not being displayed in a JSON response, however they are logged from within the ValidationBehaviour, so the FluentValidation rules are evidently receiving and validating the command. An example response is:

{
  "errors": {
    "DomainValidations": [
      "Command Validation Errors for type CreateCourseCommand" - no fluid validation errors displayed
    ]
  },
  "title": "One or more validation errors occurred.",
  "status": 400,
  "detail": "Please refer to the errors property for additional details.",
  "instance": "/api/v1/courses"
}

I have noticed that there is an extension method, AddCustomConfiguration. This initialises validation problem details from controller model state. I have tried inserting some console log statements before returning the bad request result but the InvalidModelStateReponseFactory handler does not seem to be activating, even though the AddCustomConfiguration extension method is invoked from ConfigureServices in startup.

``` c#
public static IServiceCollection AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();
services.Configure(configuration);
services.Configure(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Instance = context.HttpContext.Request.Path,
Status = StatusCodes.Status400BadRequest,
Detail = "Please refer to the errors property for additional details."
};

                return new BadRequestObjectResult(problemDetails)
                {
                    ContentTypes = { "application/problem+json", "application/problem+xml" }
                };
            };
        });

        return services;
    }
The only way that I could get the fluid validation errors displayed is to modify the global exception filter as follows:

**HttpGlobalExceptionFilter.cs**
``` c#
        public void OnException(ExceptionContext context)
        {
            logger.LogError(new EventId(context.Exception.HResult),
                context.Exception,
                context.Exception.Message);

            if (context.Exception.GetType() == typeof(CoursesDomainException))
            {
                var problemDetails = new ValidationProblemDetails()
                {
                    Instance = context.HttpContext.Request.Path,
                    Status = StatusCodes.Status400BadRequest,
                    Detail = "Please refer to the errors property for additional details."
                };

                if(context.Exception.InnerException != null && context.Exception.InnerException is ValidationException validationException)
                {
                    problemDetails.Errors.Add("DomainValidations", validationException.Errors.Select(err => err.ErrorMessage).ToArray());
                }
                else
                {
                    problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() });
                }

                context.Result = new BadRequestObjectResult(problemDetails);
                context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            }
     }

Not sure how validation is designed to work so that fluid validation error messages are displayed in JSON response. Is it the case that fluid adds the validation errors to the ModelState which then triggers the InvalidModelStateResponseFactory handler? If so, how do I configure fluid to do this since the handler is not activating? Why have both the response factory handler and the global exception handler?

I have double checked that the fluid validation behaviours are registered with MediatR

``` c#
public class MediatorModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly)
.AsImplementedInterfaces();

        // Register all the Command classes (they implement IRequestHandler) in assembly holding the Commands
        builder.RegisterAssemblyTypes(typeof(CreateCourseCommand).GetTypeInfo().Assembly)
            .AsClosedTypesOf(typeof(IRequestHandler<,>));

        // Register the DomainEventHandler classes (they implement INotificationHandler<>) in assembly holding the Domain Events

        // Register the Command's Validators (Validators based on FluentValidation library)
        builder
            .RegisterAssemblyTypes(typeof(CreateCourseCommandValidator).GetTypeInfo().Assembly)
            .Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
            .AsImplementedInterfaces();


        builder.Register<ServiceFactory>(context =>
        {
            var componentContext = context.Resolve<IComponentContext>();
            return t => { object o; return componentContext.TryResolve(t, out o) ? o : null; };
        });

        builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));
        builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>));
        builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
    }
}

```

question

All 4 comments

Hi @dcs3spp, thank you for reaching out.

There are multiple ways to implement validations for Domain layer. A detailed list can be found in Design validations in the domain model layer. Addition to that you would see Fluent Validation scenario has been implemented in different commands under Order.API for e.g : CancelOrderCommandValidator. Now, the exception can be seen as a different activity than validation and that's primarily get handled always by the HttpGlobalExceptionFilter.

The purpose of adding InvalidModelStateResponseFactory under ApiBehaviorOptions is basically doing the validation. Whereas all the domain specific exceptions or even custom exception gets handled by the HttpGlobalExceptionFilter. You could test that out locally by following below steps :

  1. Put a breakpoint in one of the class HttpGlobalExceptionFilter for e.g : Ordering\Ordering.API\Infrastructure\Filters\HttpGlobalExceptionFilter.cs
  2. Then run the eShopOnContainers from VS2019 by using F5.
  3. Go to the URL http://host.docker.internal:5102/swagger/index.html for browsing the swagger defintion of Order.API
  4. Then authorize the API.
  5. And then if you try out the POST method of API /api/v1/Orders/draft with {} params, you would notice it's actually calling OnException(ExceptionContext context) method of HttpGlobalExceptionFilter class.

image

  1. Now based on the exception type correct message gets returned.

Hope this helps !

Hi @dcs3spp , just checking if you had any further questions, or are we good for closure ?

Thank you

Hi @sughosneo
Thanks for the detailed response, appreciated :) Will have a read through and yes, good for close. Thanks again :)

Thanks for the update @dcs3spp . Closing it now. If you have any further questions, please feel free to reopen it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CESARDELATORRE picture CESARDELATORRE  路  5Comments

DavidNorena picture DavidNorena  路  4Comments

shubham2325 picture shubham2325  路  4Comments

andriyankrastevv picture andriyankrastevv  路  3Comments

anjoy8 picture anjoy8  路  5Comments