ardalis / Result

A result abstraction that can be mapped to HTTP response codes if needed.
MIT License
847 stars 100 forks source link

Converting Invalid Result to ActionResult throws System.ArgumentNullException when Identifier is not set on ValidationError #179

Closed uflowie closed 4 months ago

uflowie commented 4 months ago

Summary

When returning an InvalidResult that is lacking an identifier (ie it has been instantiated with the constructor that takes just the ErrorMessage as a string), converting it into an ActionResult using the TranslateResultToActionResult attribute, the server will throw a System.ArgumentNullException and return StatusCode 500. I have created a minimal reproducible example here: https://github.com/uflowie/InvalidResult.

Expected Behavior

Using a parameterized constructor should instantiate a result that can be translated to an ActionResult without throwing unexpected Exceptions.

Version

Ardalis.Result: 8.0 Ardalis.Result.AspNetCore: 8.0.0 dotnet: net8.0

ardalis commented 4 months ago

Confirmed. This should not throw (or should not be allowed):

        return Result<int>.Invalid(new ValidationError("foo"));

Stack trace from error:

System.ArgumentNullException: Value cannot be null. (Parameter 'key')
   at System.ArgumentNullException.Throw(String paramName)
   at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
   at Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary.AddModelError(String key, String errorMessage)
   at Ardalis.Result.AspNetCore.ResultStatusMap.BadRequest(ControllerBase controller, IResult result)
   at Ardalis.Result.AspNetCore.ResultStatusOptions.<>c__DisplayClass17_0`1.<With>b__0(ControllerBase ctrlr, IResult result)
   at Ardalis.Result.AspNetCore.ResultExtensions.ToActionResult(ControllerBase controller, IResult result)
   at Ardalis.Result.AspNetCore.TranslateResultToActionResultAttribute.OnActionExecuted(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
uflowie commented 3 months ago

Awesome, thanks!