Custom validation fires if in model class but not on page property - asp.net-core

I have a contact Razor page implemented in ASP.NET Core 2.0. I am using model binding and custom validation.
If I use custom validation on a separate model class, the validation method is called. If I use custom validation on a property on the PageModel, the validation method is not called. However, all properties are successfully bound.
Here's the PageModel class and the separate model class:
public class ContactModel : PageModel
{
[BindProperty]
public ContactMessageModel ContactMessageModel { get; set; }
[BindProperty, CustomValidation]
public string SomeData { get; set; }
public IActionResult OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return RedirectToPage("MessageSent");
}
}
public class ContactMessageModel
{
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
[Required, CustomValidation]
public string Message { get; set; }
}
A test validation attribute class is as follows:
public class CustomValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return ValidationResult.Success;
}
}
The validation attribute is called for the ContactMessageModel.Message property, but it isn't called for ContactModel.SomeData property.
Why is this and how do I fix it?

I was running into this too. In my case my issue was because I missed the addition of the [BindProperty] attribute when I moved the properties from a wrapper class model into the PageModel directly. I see that's not the case for you.
In your case I do see that you have the [Required] attribute on all properties except the ContactModel.SomeData. Maybe adding [Required] there would get things acting as you would expect?

Related

How to automatically assign values to aspnetcore controller layer method parameters

Code
Web Api Interface
I now have an interface
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController: ControllerBase
{
private readonly ICustomSession _session;
public TestController(ICustomSession session)
{
_session = session;
}
public async Task<CustomResult<string>> Search(TestSearch testSearch)
{
return new CustomResult<string>(string.Empty);
}
}
My CustomSession
public interface ICustomSession
{
public int SessionId { get; set; }
public Guid UserId { get; set; }
public string UserName { get; set; }
}
My TestSearch
public class SessionInfo
{
public Guid UserId { get; set; }
public string UserName { get; set; }
}
public class TestSearch: SessionInfo
{
public string Keyword { get; set; }
}
Problem
ICustomSession can only be used in the Controller layer, can not go deep into the business, but the business is the need for SessionInfo related information, and SessionInfo is actually read from the ICustomSession.
Now I need to inherit SessionInfo on the parameter class to handle it, but now I don't want to assign the value manually in each api interface
testSearch.UserId = _session.UserId;
I want to use an automatic assignment method that detects the class that inherits SessionInfo and automatically assigns the value from ICustomSession to it
Translated with www.DeepL.com/Translator (free version)
I want to use an automatic assignment method that detects the class that inherits SessionInfo and automatically assigns the value from ICustomSession to it

FromBody binds property with BindNever attribute

I have created a DTO:
public class MyDto :
{
[Required]
public string Name{ get; set; }
[BindNever]
public int X { get; set; }
}
When I sent body to the enpoint:
{ "Name": "name", "X": "9,}
the X is 9 instead of 0, I would expect that [FromBody] will not bind this property.
How to prevent X to be initialized when constructed and pased to a controller action?
From the documentation about [Bind], [BindRequired], [BindNever] :
These attributes affect model binding when posted form data is the source of values. They do not affect input formatters, which process posted JSON and XML request bodies.
That's why you need to add a attribute depending the serializer used. By default in ASP.NET Core, the serializer is "System.Text.Json" and the attribute expected is [JsonIgnore] :
public class MyDto :
{
[Required]
public string Name{ get; set; }
[BindNever]
[JsonIgnore]
public int X { get; set; }
}

ASP.NET Core Webapi (3.x) Custom ModelBinder Always Returns Empty Object

I've been attempting to create a custom model binder and am running into an issue where the object after going through the binder is ALWAYS empty.
I have a good reason for using a custom model binder. For the purposes of this question, assume I HAVE to use a custom model binder. In every case where i've used a custom binder provider with either stock or custom modelbinders I've run into this issue, so I've dumbed this down a LOT to demonstrate, but it happens in this specific example as well, and I really need to know why.
I have a simple controller action and DTO class:
[HttpPost]
[Route("{id}")]
public async Task<ActionResult<QueryServicesDto>> UpdateQueryService(int id,
[FromBody] QueryServicesDtoLight dto)
{
}
public class QueryServicesDtoLight
{
public long QueryServicesId { get; set; }
public DateTime? CreationDate { get; set; }
public long? ModifiedDate { get; set; }
public long? PropagationDate { get; set; }
public int? CabinetListNumber { get; set; }
public string GameCode { get; set; }
public string Status { get; set; }
}
Can anyone tell me why when posting valid JSON to this action with no custom binder providert I get a DTO with the proper values, but if I inject the custom modelBinderProvider below I get a newed up model with no values?
public class QueryServiceModelBinderBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType== typeof(QueryServicesDtoLight))
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
ModelMetadata theProp = context.Metadata.Properties[i];
var binder = context.CreateBinder(theProp);
propertyBinders.Add(theProp, binder);
}
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ComplexTypeModelBinder(propertyBinders, loggerFactory);
}
else
{
return null;
}
}
}
Binder provider is added like this::
services.AddControllersWithViews(o=>o.ModelBinderProviders.Insert(0, new QueryServiceModelBinderBinderProvider())).AddNewtonsoftJson();
Sample JSON Data
{"queryServicesId":14,"creationDate":"2021-03-08T17:06:36.053","modifiedDate":16176433093000000,"propagationDate":0,"cabinetListNumber":996,"gameCode":"PGA2006","status":"AC"}

asp.net core custom model binder just for one property

I have a simple model for my asp.net core controller:
[HttpPost]
public async Task<DefaultResponse> AddCourse([FromBody]CourseDto dto)
{
var response = await _courseService.AddCourse(dto);
return response;
}
My model is :
public class CourseDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Genre { get; set; }
public string Duration { get; set; }
public string Level { get; set; }
public string AgeRange { get; set; }
public string Notes { get; set; }
public bool Active { get; set; }
public string OrganisationCode { get; set; }
}
I'm trying to set value of "OrganisationCode" using a custom mode binder or action filter, but had no success.
I would be thnakful if you advise whats the right way to updat ethe model before executing the action.
Thanks.
I will show you here a very simple custom model binder I have just written (and tested in .Net Core 2.0):
My model binder:
public class CustomModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var value = valueProviderResult.FirstValue; // get the value as string
var model = value.Split(",");
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
My model (and notice, only one property has my custom model binder annotation):
public class CreatePostViewModel
{
[Display(Name = nameof(ContentText))]
[MinLength(10, ErrorMessage = ValidationErrors.MinLength)]
public string ContentText { get; set; }
[BindProperty(BinderType = typeof(CustomModelBinder))]
public IEnumerable<string> Categories { get; set; } // <<<<<< THIS IS WHAT YOU ARE INTERESTER IN
#region View Data
public string PageTitle { get; set; }
public string TitlePlaceHolder { get; set; }
#endregion
}
What it does is: it receives some text like "aaa,bbb,ccc", and converts it into array, and return it to the ViewModel.
I hope that helps.
DISCLAIMER: I am not an expert in model binders writing, I have learnt that 15 minutes ago, and I found your question (with no helpful answer), so I tried to help. This is a very basic model binder, some improvements are surely required. I learned how to write it from the official documentation page.
The [FromBody] attribute you are using on the action parameter. means that you direct the default behavior of Model Binding to use the formatters instead. That is why your custom Model Binder does not work.
And [FromBody] is reading the content (request body). So you won't get the request body from your Action Filter, as the request body is a non-rewindable stream, so it suppose to be read only once (I'm assuming that you are trying to read the request body from Action Filter).
My suggestion is to use your custom model binder and remove the FromBody Attribute.

WebApi, Custom model binder and Data annotations

So, is it possible to combine the 3?
When I send a request to the server that does not conform to the model annotated validation (empty email in this case) ModelState.IsValid is true.
Model
public class UpdateModel
{
[Required]
public string Email { get; set; }
public string Name { get; set; }
}
Model Binder
public class MyModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var data = actionContext.Request.Content.ReadAsStringAsync().Result;
var model = JsonConvert.DeserializeObject<UpdateModel>(data);
bindingContext.Model = model;
return true;
}
}
Action in ApiController
public IHttpActionResult Update(UpdateModel model)
{
if (!ModelState.IsValid)
{
// this never happens :-(
}
}
Related but for MVC not WebApi: Custom model binding, model state, and data annotations
Validation through DataAnnotation was treated in Model Binder. If you use your own ModelBinder, you must explicit call validation over DataAnnotation in the ModelBinder - if you wish automatic validation over DA.