How to disable model validation to allow fluent validation checking - c#

I have the following controller:
[Route("api/[controller]")]
public class ReleaseController : BaseController
{
[HttpPut]
public Task<ReleaseModel> UpdateAsync([FromBody] UpdateReleaseForm form, CancellationToken cancellationToken = default)
=> _releaseService.UpdateAsync(form, cancellationToken);
}
The form among other properties has enum property ReleaseStatus:
public class UpdateReleaseForm
{
// other props omitted for brevity
public ReleaseStatus Status { get; set; }
}
I use FluentValidation to create a rule for the Status:
internal sealed class UpdateReleaseFormValidator : AbstractValidator<UpdateReleaseForm>
{
public UpdateReleaseFormValidator()
{
// other rules omitted for brevity
RuleFor(u => u.Status)
.ReleaseStatusValidation();
}
}
public static class RuleBuildersExtensions
{
public static IRuleBuilderOptions<T, ReleaseStatus> ReleaseStatusValidation<T>(
this IRuleBuilder<T, ReleaseStatus> rule)
{
return rule.
IsInEnum()
.WithMessage(
string.Format("Status should be one of the following values: `{0}`",
string.Join(", ", Enum.GetNames(typeof(ReleaseStatus)))));
}
}
The problem is that when a wrong Status passed in to the action method, I get the error:
{
"errors": {
"status": [
"Error converting value \"WrongStatus\" to type '...ReleaseStatus'. Path 'Status', line 4, position 28."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|3405bc61-4f67bebc131b8dc8."
}
I want to get my custom message defined in validation rule instead.
I tried to use NullObjectModelValidator from disable-validation.
Also I tried this:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
But get the following:
System.ArgumentNullException: Cannot pass null model to Validate.
(Parameter 'instanceToValidate')

Just an idea, but you could try to make the UpdateReleaseForm form parameter in the UpdateAsync method nullable:
[Route("api/[controller]")]
public class ReleaseController : BaseController
{
[HttpPut]
public Task<ReleaseModel> UpdateAsync([FromBody] UpdateReleaseForm? form, CancellationToken cancellationToken = default)
=> _releaseService.UpdateAsync(form, cancellationToken);
}
Maybe that will allow the parameter to be null.

Related

Swashbuckle/Swagger - I need to remove incorrect parameters

I have a number of Get operations in my Api. Currently the swagger document includes the following parameters:
"parameters": [
{
"in": "body",
"name": "body",
"schema": { }
}
],
Is there any way to remove these using an OperationFilter? I've tried the following but the operations do not have any parameters
public class RemoveBodyParametersFilter : IOpenApiOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var parametersToRemove = operation.Parameters.Where(p => p.Name == "body").ToList();
foreach (var parameter in parametersToRemove)
{
operation.Parameters.Remove(parameter);
}
}
}
The endpoint looks like this:
[HttpGet(Name = "GetOperationStatus")]
[SwaggerConsumes("application/json")]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 200)]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 202)]
[ProducesResponseType(typeof(DtoBaseMgmtResponse), 400)]
[Metadata("Get an Operation Status", "Get the operation status for the operation ID provided.", VisibilityType.Internal)]
public async Task<IActionResult> GetOperationStatus(string operationId)

How to bind custom fluent validation message to ModelState.Values.Errors

I want to return my custom message when take invalid values my model.Therefore I use the Fluentvalidation.Create validators and asyncActionfilter. But Method returned default fluent message instead of my custom message. Why?
My AsyncActionFilter Class
public class ModelValidatorFilterAttribute : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
var errorMessages =
context.ModelState.Values
.Select(
x =>
new ExceptionResponse.ExceptionMessage { Message = x.Errors.First().ErrorMessage })
.ToList();
throw new ValidationException(errorMessages);
}
await next();
}
}
My Validator
public class MyValidator : AbstractValidator<MyRequestDto>
{
public MyValidator()
{
RuleFor(s => s.ProductId).NotEmpty().NotNull()
.WithMessage(x => "my custom message");
}
}
My Startup config
services.AddFluentValidation();
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Response
{
"exceptionMessages": [
{
"message": "'Product Id' must not be empty.",
"duration": 0,
"type": "Type"
}
]
}

Azure CosmosDB + NetCore 3.1 : System.Text.Json.JsonException: A possible object cycle was detected which is not supported

I have been running into an issue with a specific call on CosmosDB from my API ( Net core 3.1 )
I am getting a error back :
"fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.Text.Json.JsonException: A possible object cycle was detected which is not supported."
My User_Profile class is fairly simple :
using Newtonsoft.Json;
namespace API
{
public class User_Profile
{
[JsonProperty("id")]
public string Id { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
}
The Call on the database method :
public async Task<User_Profile> GetUserAsync (string id)
{
try
{
User_Profile foundUser = await ctner_Users.ReadItemAsync<User_Profile>(id, new PartitionKey(id));
return foundUser;
}
catch (Exception e)
{
_logger.LogWarning("AddUserAsync failed : " + e);
return null ;
}
}
This is the document shown in cosmos db explorer :
{
"id": "romain",
"Password": "Password",
"Role": "User",
"_rid": "VqgQAP51mcUCAAAAAAAAAA==",
"_self": "dbs/VqgQAA==/colls/VqgQAP51mcU=/docs/VqgQAP51mcUCAAAAAAAAAA==/",
"_etag": "\"4300e1b0-0000-0100-0000-5ee44ce00000\"",
"_attachments": "attachments/",
"_ts": 1592020192
}
That is the method in the controller :
[HttpPost("auth")]
public ActionResult<User_Profile> AuthUser ([FromBody] User_Profile authUser)
{
var user = _db.GetUserAsync(authUser.Id);
if (user != null)
{
return Ok(user);
}
else
{
return NotFound();
}
}
Been trying to debug / deglitch / found out where the issue is coming for quite some time, so i have removed a lot of the previous code i had, like password checking, model validation, ect ect...and tried to keep the very minimum to narrow it down, but even that gives me the same cycle error message.Knowing that my user proifile class only has 3 strings in it, i fail to see the cycle reference issue.
One thing i have tried was to to update json to last preview and add the reference handling , and set it to preserver : I instead got a very length error message, showing what seems liek to be every assembly versions possible.
To note : the other calls to the DB are working .. create user, or access other containers, read from them, ect... it really just this spefici call that gives me back that error, and i currently am at a complete loss on that one.Any help would be really appreciated.
EDIT : If i replace in the startup.cs with
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
Then the message that i get back when trying the same method on the controller is that very lengthy one ( i ve cut all the assembly section ) :
{
"stateMachine": {
"<>1__state": 0,
"<>t__builder": {},
"id": "romain",
"<>4__this": {}
},
//BIG CONTEXT SECTION HERE, ABOUT 0.5 MB of text...
"result": {
"id": "romain",
"password": "Password",
"role": "User"
},
"id": 1808,
"exception": null,
"status": 5,
"isCanceled": false,
"isCompleted": true,
"isCompletedSuccessfully": true,
"creationOptions": 0,
"asyncState": null,
"isFaulted": false
}
EDIT 2 : If i try to retreive a List instead of a single item , it works :
[HttpPost("auth2")]
public async Task<ActionResult<List<User_Profile>>> GetUsers ([FromBody] User_Profile authUser)
{
List<User_Profile> list = await _db.GetUsersAsync(authUser.Id);
return Ok(list);
}
public async Task<List<User_Profile>> GetUsersAsync (string id)
{
try
{
string queryString = "SELECT * FROM c WHERE c.id='"+id+"'";
var query = ctner_Users.GetItemQueryIterator<User_Profile>(new QueryDefinition(queryString));
List<User_Profile> results = new List<User_Profile>();
while (query.HasMoreResults)
{
var response = await query.ReadNextAsync();
results.AddRange(response.ToList());
}
return results;
}
catch (Exception e)
{
return null;
}
}
Issue seems to be related to the " ReadItemAsync" that i am either not using properly, or has an issue in the version i am using.
I found a solution, which is quite far from the reported error message.
I had to turn the controller into an async Task instead of the previous one.
The controller must be:
[HttpPost("auth")]
public async Task<ActionResult<User_Profile>> AuthUser ([FromBody] User_Profile authUser)
{
var user = await _db.GetUserAsync(authUser.Id);
if (user != null)
{
return Ok(user);
}
else
{
return NotFound();
}
}
Instead of:
[HttpPost("auth")]
public ActionResult<User_Profile> AuthUser ([FromBody] User_Profile authUser)
{
var user = _db.GetUserAsync(authUser.Id);
if (user != null)
{
return Ok(user);
}
else
{
return NotFound();
}
}
I am not sure whether I can explain that one, but it solved the issue.

How to return ModelState with Forbidden status web api

We can return ModelState with BadRequest from web api in following way:
return BadRequest(ModelState);
It provides following output:
{
"Message": "The request is invalid.",
"ModelState": {
"property": [
"error"
]
}
}
How can I return the same output with Forbidden status?
I tried following method:
return Content(HttpStatusCode.Forbidden, ModelState);
But it returns:
{
"property": {
"_errors": [
{
"<Exception>k__BackingField": null,
"<ErrorMessage>k__BackingField": "error"
}
],
"<Value>k__BackingField": null
}
}
Json serializing ModelSate is also not returning the same thing. How can I use the serialization method used by BadRequest() method for ModelState with other status codes?
You can achieve the desired output by using the ExecuteAsync method of InvalidModelStateResult (return type of BadRequest() method of ApiController) which actually serializes the ModelState.
So the idea is to create a new class which extends InvalidModelStateResult and override ExecuteAsync method to change the status code.
public class ModelStateResult : InvalidModelStateResult
{
private readonly HttpStatusCode _status;
public ModelStateResult(ModelStateDictionary modelState, ApiController controller, HttpStatusCode status) : base(modelState, controller)
{
_status = status;
}
public override Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = base.ExecuteAsync(cancellationToken).Result;
response.StatusCode = _status;
return Task.FromResult(response);
}
}
Use it like:
return new ModelStateResult(ModelState, this, HttpStatusCode.Forbidden); //this refers to ApiController here
I think this is just a work around, hope someone posts a better way to achieve it.
EDIT:
Without using InvalidModelStateResult:
public class ModelStateResult : IHttpActionResult
{
public HttpStatusCode Status { get; }
public ModelStateDictionary ModelState { get; }
public HttpRequestMessage Request { get; }
public ModelStateResult(HttpStatusCode status, ModelStateDictionary modelState, HttpRequestMessage request)
{
Status = status;
ModelState = modelState;
Request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = Request.CreateErrorResponse(Status, ModelState);
return Task.FromResult(response);
}
}

Use custom validation responses with fluent validation

Hello I am trying to get custom validation response for my webApi using .NET Core.
Here I want to have response model like
[{
ErrorCode:
ErrorField:
ErrorMsg:
}]
I have a validator class and currently we just check ModalState.IsValid for validation Error and pass on the modelstate object as BadRequest.
But new requirement wants us to have ErrorCodes for each validation failure.
My sample Validator Class
public class TestModelValidator : AbstractValidator<TestModel>{
public TestModelValidator {
RuleFor(x=> x.Name).NotEmpty().WithErrorCode("1001");
RuleFor(x=> x.Age).NotEmpty().WithErrorCode("1002");
}
}
I can use something similar in my actions to get validation result
Opt1:
var validator = new TestModelValidator();
var result = validator.Validate(inputObj);
var errorList = result.Error;
and manipulate ValidationResult to my customn Response object.
or
Opt2:
I can use [CustomizeValidator] attribute and maybe an Interceptors.
but for Opt2 I don't know how to retrieve ValidationResult from interceptor to controller action.
All I want is to write a common method so that I avoid calling Opt1 in every controller action method for validation.
Request to point me to correct resource.
try with this:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
I validate the model with fluentvalidation, after build the BadResquest response in a ActionFilter class:
public class ValidateModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Values.Where(v => v.Errors.Count > 0)
.SelectMany(v => v.Errors)
.Select(v => v.ErrorMessage)
.ToList();
var responseObj = new
{
Message = "Bad Request",
Errors = errors
};
context.Result = new JsonResult(responseObj)
{
StatusCode = 400
};
}
}
}
In StartUp.cs:
services.AddMvc(options =>
{
options.Filters.Add(typeof(ValidateModelStateAttribute));
})
.AddFluentValidation(fvc => fvc.RegisterValidatorsFromAssemblyContaining<Startup>());
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
And it works fine. I hope you find it useful
As for me, it's better to use the following code in ASP.NET Core project
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = c =>
{
var errors = string.Join('\n', c.ModelState.Values.Where(v => v.Errors.Count > 0)
.SelectMany(v => v.Errors)
.Select(v => v.ErrorMessage));
return new BadRequestObjectResult(new
{
ErrorCode = "Your validation error code",
Message = errors
});
};
});
Also take into account that instead of anonymous object you can use your concrete type. For example,
new BadRequestObjectResult(new ValidationErrorViewModel
{
ErrorCode = "Your validation error code",
Message = errors
});
In .net core you can use a combination of a IValidatorInterceptor to copy the ValidationResult to HttpContext.Items and then a ActionFilterAttribute to check for the result and return the custom response if it is found.
// If invalid add the ValidationResult to the HttpContext Items.
public class ValidatorInterceptor : IValidatorInterceptor {
public ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result) {
if(!result.IsValid) {
controllerContext.HttpContext.Items.Add("ValidationResult", result);
}
return result;
}
public ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext) {
return validationContext;
}
}
// Check the HttpContext Items for the ValidationResult and return.
// a custom 400 error if it is found
public class ValidationResultAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext ctx) {
if(!ctx.HttpContext.Items.TryGetValue("ValidationResult", out var value)) {
return;
}
if(!(value is ValidationResult vldResult)) {
return;
}
var model = vldResult.Errors.Select(err => new ValidationErrorModel(err)).ToArray();
ctx.Result = new BadRequestObjectResult(model);
}
}
// The custom error model now with 'ErrorCode'
public class ValidationErrorModel {
public string PropertyName { get; }
public string ErrorMessage { get; }
public object AttemptedValue { get; }
public string ErrorCode { get; }
public ValidationErrorModel(ValidationFailure error) {
PropertyName = error.PropertyName;
ErrorMessage = error.ErrorMessage;
AttemptedValue = error.AttemptedValue;
ErrorCode = error.ErrorCode;
}
}
Then in Startup.cs you can register the ValidatorInterceptor and ValidationResultAttribute like so:
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddTransient<IValidatorInterceptor, ValidatorInterceptor>();
services.AddMvc(o => {
o.Filters.Add<ValidateModelAttribute>()
});
}
}
Refer this link for answer: https://github.com/JeremySkinner/FluentValidation/issues/548
Solution:
What I've done is that I created a basevalidator class which inherited both IValidatorInterceptor and AbstractValidator. In afterMvcvalidation method if validation is not successful, I map the error from validationResult to my custom response object and throw Custom exception which I catch in my exception handling middleware and return response.
On Serialization issue where controller gets null object:
modelstate.IsValid will be set to false when Json Deserialization fails during model binding and Error details will be stored in ModelState. [Which is what happened in my case]
Also due to this failure, Deserialization does not continue further and gets null object in controller method.
As of now, I have created a hack by setting serialization errorcontext.Handled = true manually and allowing my fluentvalidation to catch the invalid input.
https://www.newtonsoft.com/json/help/html/SerializationErrorHandling.htm [defined OnErrorAttribute in my request model].
I am searching for a better solution but for now this hack is doing the job.
Similar to Alexander's answer above, I created an anonymous object using the original factory I could find in the source code, but just changed out the parts to give back a custom HTTP response code (422 in my case).
ApiBehaviorOptionsSetup (Original factory)
services.AddMvcCore()
...
// other builder methods here
...
.ConfigureApiBehaviorOptions(options =>
{
// Replace the built-in ASP.NET InvalidModelStateResponse to use our custom response code
options.InvalidModelStateResponseFactory = context =>
{
var problemDetailsFactory = context.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState, statusCode: 422);
var result = new UnprocessableEntityObjectResult(problemDetails);
result.ContentTypes.Add("application/problem+json");
result.ContentTypes.Add("application/problem+xml");
return result;
};
});
Here I tried
public async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Values.Where(v => v.Errors.Count > 0)
.SelectMany(v => v.Errors)
.Select(v => v.ErrorMessage)
.ToList();
var value = context.ModelState.Keys.ToList();
Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>();
foreach (var modelStateKey in context.ModelState.Keys.ToList())
{
string[] arr = null ;
List<string> list = new List<string>();
foreach (var error in context.ModelState[modelStateKey].Errors)
{
list.Add(error.ErrorMessage);
}
arr = list.ToArray();
dictionary.Add(modelStateKey, arr);
}
var responseObj = new
{
StatusCode="400",
Message = "Bad Request",
Errors = dictionary
};
context.Result = new BadRequestObjectResult(responseObj);
return;
}
await next();
}
Response Model:
{
"statusCode": "400",
"message": "Bad Request",
"errors": {
"Channel": [
"'Channel' must not be empty."
],
"TransactionId": [
"'TransactionId' must not be empty."
],
"Number": [
"'Number' must not be empty."
]
}
}

Categories

Resources