close
The Wayback Machine - https://web.archive.org/web/20201207222001/https://github.com/dotnet-architecture/eShopOnContainers/issues/1248
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
dcs3spp opened this issue Feb 26, 2020 · 4 comments
Labels

Comments

@dcs3spp
Copy link

@dcs3spp dcs3spp commented Feb 26, 2020

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.

public static IServiceCollection AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddOptions();
            services.Configure<OrderingSettings>(configuration);
            services.Configure<ApiBehaviorOptions>(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

        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

 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<,>));
        }
    }
@sughosneo sughosneo added the question label Aug 10, 2020
@sughosneo
Copy link
Contributor

@sughosneo sughosneo commented Sep 1, 2020

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 !

@sughosneo
Copy link
Contributor

@sughosneo sughosneo commented Oct 9, 2020

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

Thank you

@dcs3spp
Copy link
Author

@dcs3spp dcs3spp commented Oct 14, 2020

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

@sughosneo
Copy link
Contributor

@sughosneo sughosneo commented Oct 14, 2020

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

@sughosneo sughosneo closed this Oct 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.