Comments (5)
Thank you for your response and the proposed workaround.
Unfortunately, I think what you proposed with checking model state and returning custom response goes against the web API story with features like [ApiController]/ModelStateInvalidFilter. If I had to go to every controller to adjust this, I'd rather just make the request optional and add a custom attribute + schema filter for swashbuckle to show them as required in the swagger doc than to write model state checks everywhere.
The custom ProblemDetailsFactory, I think will need a lot of work to detect these cases reliably. I'm not even sure if the routing validation metadata are accessible from there through HttpContext. Not to mention that the original type is internal so I'd have to reimplement it and keep it up to date with each release. This does not seem like a good trade-off to me.
I think the idea of @jonasof fits really well with the existing conventions model and also reflects the exact requirements/conventions for the case. I'll consider it if the official answer is negative.
As already stated, I'm in no hurry to have this but I'd be really glad if the validation story got more polished so that we don't have to debug and hack together a bunch of workarounds to get a world class web API.
from aspnetcore.
Hi @zdenek-jelinek, In the last week I've spent some time with the same issue. One thing that seems to solve this is to make the request parameter optional with the nullable operator:
public IActionResult CreatePersonAsync(PersonRequest? personRequest)
{
if (personRequest == null)
{
return BadRequest();
}
return Ok();
}
But it seems "ugly" because the request variable is not meant to be optional.
After some debugging, I created a workaround to remove the body parameter from the errors response array by disabling the "isRequired" metadata of the request body parameter:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.Routing;
/// <summary>
/// When ImplicitRequiredAttributeForNonNullableReferenceTypes is enabled, the aspnet framework
/// automatically marks the FromBody parameters as required. This is not a problem except that,
/// when some DTO field is incorrect, the response body comes as:
///
/// {
/// "errors": {
/// -> "request": [
/// -> "The request field is required."
/// -> ],
/// "someActualMissingJsonParam": [
/// "The input was not valid."
/// ]
/// }
/// }
///
/// The "The request field is required" error is misleading to the consumers,
/// because it's not an actual JSON property, but the internal request variable name.
///
/// To remove that error from the response body, this class sets the IsRequired validation metadata to false
/// when it's coming from the [FromBody] attribute, or when it's coming from a complex field with no
/// BindingSource attribute (FromQuery, FromPath, ...).
///
/// That metadata is being set by the aspnet framework at:
/// https://github.com/dotnet/aspnetcore/blob/v6.0.25/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsMetadataProvider.cs#L412-L415
/// </summary>
public class BodyModelRequiredMetadataRemoverProvider : IValidationMetadataProvider
{
private readonly ModelMetadataProvider _modelMetadataProvider;
public BodyModelRequiredMetadataRemoverProvider()
{
_modelMetadataProvider = new EmptyModelMetadataProvider();
}
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
if (!IsAParameterFromAControllerAction(context))
{
return;
}
if (HasFromBodyAttribute(context) || IsModelBeingImplicitlyParsedFromBody(context))
{
context.ValidationMetadata.IsRequired = false;
}
}
private bool IsAParameterFromAControllerAction(ValidationMetadataProviderContext context)
{
if (context.Key.ParameterInfo == null)
{
return false;
}
return context.Key.ParameterInfo.Member.CustomAttributes.Any(
at => at.AttributeType.BaseType?.Equals(typeof(HttpMethodAttribute)) ?? false
);
}
private bool HasFromBodyAttribute(ValidationMetadataProviderContext context)
{
return context.Attributes.Any(
at => at.GetType().Equals(typeof(FromBodyAttribute))
);
}
private bool IsModelBeingImplicitlyParsedFromBody(ValidationMetadataProviderContext context)
{
return !HasAnyBindingSourceAttribute(context) && IsComplexType(context);
}
/// <summary>
/// Checks for other BindingSource attributes like "FromQuery", "FromPath"...
/// If they are present, returns false.
///
/// This check is needed because for these it's not intended to override IsRequired to false.
/// </summary>
private bool HasAnyBindingSourceAttribute(ValidationMetadataProviderContext context)
{
return context.Attributes.Any(
at => at.GetType().GetInterfaces().Any(
i => i.Equals(typeof(IBindingMetadataProvider))
)
);
}
/// <summary>
/// Checks if the model being validated is from a complex type.
///
/// It's needed because the DTO classes with multiple attributes are considered complex types,
/// for they we want to omit the errors in the response.
///
/// But for the simple types, like int, string... Is not intended to hide them from the response errors.
/// </summary>
private bool IsComplexType(ValidationMetadataProviderContext context)
{
return _modelMetadataProvider.GetMetadataForType(context.Key.ModelType).IsComplexType;
}
}
Then add it in the controllers option "ModelMetadataDetailsProviders":
builder.Services.AddControllers((options) => options.ModelMetadataDetailsProviders.Add(new BodyModelRequiredMetadataRemoverProvider()))
Now the output is:
{
"errors": {
"someActualMissingJsonParam": [
"The input was not valid."
]
}
}
Instead of:
{
"errors": {
"request": [
"The request field is required."
],
"someActualMissingJsonParam": [
"The input was not valid."
]
}
}
As I said it's a workaround, I haven't tested all the scenarios but it worked as expected so far.
I think a proper option to omit those error messages in the asp.net core framework is needed. As you mentioned, returning the internal variable name is misleading to the API consumers.
from aspnetcore.
Regarding priority: This has been happening for years, I don't need this tomorrow. On the other hand, I don't want to be explaining this cryptic error to some grumpy dev 10 years from now.
If there's anything I could do to move this forward, I am happy to. But I'm lacking in knowledge of use-cases such as MVC/Pages (especially form contents) that also come into play here.
from aspnetcore.
@jonasof Thanks for sharing your workarounds here.
Another possible option is to to implement a custom ValidationProblemDetails
response object that is returned directly from the action when the model state is invalid.
You can also create a custom implementation of the ProblemDetailsFactory
to modify the return objects as needed. See here for more info.
Given the complex nature of MVC's model binding/validation layer, I dunno that we would change the built-in behavior here or add a specific flag for this. But I think the customization options that exist can help with this.
from aspnetcore.
This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.
See our Issue Management Policies for more information.
from aspnetcore.
Related Issues (20)
- Need help with SSE and .NET events
- Perf improvement: caching, json, mapaction
- Perf improvement: fortunes
- Perf regression: json, json, mvc
- Perf regression: mapaction, mapaction, plainTextNoParamsEmptyFilter
- Performance Challenges of Blazor Server Applications in Low Bandwidth Environments Compared to MVC and Traditional Web Applications HOT 2
- Adding Roles in Blazor Web App Gives Exception
- Explicitly calling `context.Fail(....)` in authorization policy requirement handler results in empty `policyAuthorizationResult.AuthorizationFailure.FailedRequirements` HOT 2
- Perf improvement: fortunes_dapper
- Perf regression: endpoint, json, plaintext
- Perf improvement: ApiCrudDeleteProduct
- Exception triggered by AuthorizeAttribute on the first page loaded in a circuit HOT 5
- Perf regression: fortunes_ef, json
- Perf regression: fortunes_ef, plaintext
- Allow more granular return types in signalR HOT 7
- ability to override `CssClass` property in `InputBase<T>` HOT 4
- NavigationException thrown in Static Server Render HOT 2
- Unhandled exception rendering component: Could not load file or assembly HOT 4
- ASP.NET Core with Angular throws Exception on startup after publish
- Consider increasing the default iteration count for PBKDF2 to follow OWASP recommendation
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from aspnetcore.