Not entering Controller while validating with Data Annotations - c#

I am using ASP.NET Core restful web API. My problem is that I have a server validation inside my controller to check for view model (Contact.cs) validation.
However when I test my POST action CreateContact in the controller, the action is never entered, but it is validated correctly according to the data annotations I have inside my Contact.cs class.
I don't understand why validation is happening before entering controller. I thought, that in web API, server validation will be checked in Controller according to the ModelState. Then my ModelState.IsValid check seems useless.
My view model:
public class Contact
{
[Required]
public int? Id { get; set; }
[MaxLength(20)]
public string FirstName { get; set; }
// ...
}
Piece of my Controller action which is never entered.
[Route("api/[controller]")]
[ApiController]
public class ContactsController : ControllerBase
{
[HttpPost]
public IActionResult CreateContact(ViewModels.Contacts.Contact contact)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// ...
}
}

The ApiController attribute that you have applied to your controller comes with certain conventions. One of that is that the passed model is automatically validated before entering your controller action.
So this effectively removes the need to check the ModelState.IsValid inside each method.
You can read more about the ApiController attribute in the official documentation and in this blog post both which also cover the other conventions the attribute includes.
If you don’t want this behavior and still want to be able to do this manually in your controller action, check out this question on disabling the functionality.

Related

.NET Core Web API posting formdata and getting 400 error

I believe the issue is with the null values. I have tested payloads that contained values such as
{
name: 'My Name',
householdSize: '3'
}
and the server would get the request. However, I want to still allow users to not input any data, however, when this occurs, the server is never hit (I have a breakpoint to test).
I am using a js frontend with a .NET Core Web API.
I have a payload that looks like
{
name: null,
householdSize: null
}
My view model is as such
public class MyViewModel
{
[Required]
public string Name { get; set; }
[Required]
public EHouseholdSize HouseholdSize { get; set; }
public enum EHouseholdSize
{
One = 1,
Two,
Three,
Four,
FivePlus
}
}
My controller is as such
public async Task<IActionResult> Add([FromBody]QuestionnaireViewModel viewModel)
{
if(!ModelState.IsValid)
{
return BadRequest();
}
// Do stuff
return Ok();
}
Your ModelState is not valid when sending null values because both of the property of your view model has [Required] attribute. Sending {"name": null, "houseHoldSize": null} is equivalent to `` or {}.
Therefore, not fulfilling the requirements and makes the model state invalid. That's why it's never pass your if block.
So if you want to allow posting null or empty values, you need to remove the [Required] attribute from your ViewModel properties.
When you use API Controller (has [ApiController] attribute), the action parameters will be automaticaly validated and you don' t need if ( !ModelState.IsValid ) since it will never be hitted if model state is invalid. Api controller will return BadRequest before it. So remove the [Required] attribute from your ViewModel properties or make them not null
Another thing is if your project has enabled nullable (mostly net 6 projects)
<Nullable>enable</Nullable>
you will need to make a property Name nullable too
public string? Name { get; set; }
Otherwise you will get a BadRequest "The Name field is required.", even if you remove [Required] attribute.

NETCORE MVC - How to work with nested, multi-parameterized routes

Looking for best practices when working with nested routes in .NET Core MVC.
Let's say CampusController.cs works with a base model:
[Route("api/campus/")]
public class CampusController : Controller
{
...
[HttpGet]
[Route("{campusId}")]
public IActionResult GetCampusInfo ([FromQuery]int campusId) { ... }
}
And BuildingController.cs works with a child model:
[Route("api/campus/{campusId}/building")]
public class BuildingController : Controller
{
...
[HttpGet]
[Route("{buildingId}")]
public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
[Route("{buildingId}/")]
public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
....
(more Action Methods)
}
If buildingId maps directly to the database it could retrieved even if the provided campusId isn't the parent. To keep the URL clean when calling /api/campus/{campusId}/building/{buildingId} I'd like to validate {campusId} and return a 4xx coded IActionResult if it's invalid. There has to be a better way than including validation logic in every Action Method inside BuildingController.
Is there a way to cascade multiple Action methods on different controllers? So that a validation method on CampusController would be called first and in turn call a method onBuildingController?
Is there a way to have a controller-level verification of campusId that could short circuit and return a ActionResult if validation fails?
EDIT: When I refer to validation logic I mean API signals; not the business-logic that actually determines if campusId is/isn't valid.
Thanks in advance!
If using placeholder in the route prefix you would also need to include it in the action itself
[Route("api/campus/{campusId:int}/building")]
public class BuildingController : Controller {
//...
[HttpGet]
[Route("{buildingId:int}")] // Matches GET api/campus/123/building/456
public IActionResult GetBuilding ([FromRoute]int campusId, [FromRoute]int buildingId) {
//... validate campus id along with building id
}
}
If concerned about repeated code for validation then create a base controller for campus related request and have a shared validation method.
Another option is to have a service/repository that can be used to verify campus id and its relation to the provided building id if needed.
It sounds like you want your users to provide a campusId when talking to the BuildingController, and your BuildingController to validate campusId in a DRY kind of way.
If that's the case, you can create an input model for your BuildingController methods:
public class BuildingIdInput
{
[Required]
public int? CampusId { get; set; }
[Required]
public int? BuildingId { get; set; }
}
Then you can let MVC bind user input to this model.
[Route("api/campus")]
public class BuildingController : Controller
{
[HttpGet]
[Route("{campusId}/building/{buildingId}")]
public IActionResult GetBuilding (BuildingIdInput input)
{
if (ModelState.IsValid)
{...}
}
}

How to validate GET url parameters through ModelState with data annotation

I have a Web API project... I would like to respect the REST principles, so I should have just a GET method and just a POST method...
I have to do a search, so i think this matches the GET method, because after the search I obtain the result and I show it in the page... If I do not find anything I must create the object... this action is a POST...
Now I have a problem... I must validate the filters of the search, because filters are a tax code and a alpha-numeric code (6 chars)... I have already done a client side validation. Now I should do a Server Side validation.
Untill now, we have used data annotation to validate the request, but this is a GET... so my method has this signature:
[HttpGet]
public IHttpActionResult GetActivationStatus(string taxCode, string requestCode)
{
if (ModelState.IsValid)
{
...
}
}
But how can I validate my ModelState with Data Annotation?
Thank you
Create your own model...
public class YourModel
{
[//DataAnnotation ...]
public string taxCode { get; set; }
[//DataAnnotation ...]
public string requestCode { get; set; }
}
And change your signature of your server side controller:
[HttpGet]
public IHttpActionResult GetActivationStatus([FromUri] YourModel yourmodel)
{
if (ModelState.IsValid)
{
...
}
}
If your client side code already worked you don't have to change it... Please, note that the properties of your Model are the same of the parameter you are passing now (string taxCode, string requestCode)... and they are case sensitive...
EDIT:
I mean that you can call your controller in this way:
http://localhost/api/values/?taxCode=xxxx&requestCode=yyyy

How to route MVC so that I can post a form with a model.action parameter?

Hopefully this is an easy one for somebody out there.
I am trying to post a form to my MVC controller that happens to have an "action" property on the model.
Unfortunately, the model.action is resolving to the controller action, not the posted model's action property.
public class PostModel
{
public string action { get; set; }
public string username { get; set; }
public string password { get; set; }
}
public ActionResult DoSomething(string id, PostModel model)
{
// id == 98f4
// model.username == "TEST"
// model.password == "TEST"
// model.action == "DoSomething" NOT "TEST" as I was expecting.
}
Here is what I post:
POST -> http://localhost:7832/Forms/DoSomething/98f4?username=TEST&password=TEST&action=TEST
Please keep in mind I have no control over the form data being posted, so I cannot change the model's action property. I need to be able to address this problem on the MVC server side.
How do I overwrite the setting of the action property in my model to the acction of the controller? I would only need this functionality for one particular controller in my project.
Any suggestions?
Ok I figured out the problem. Actually it was with my use of fiddler2 to hit the controller action. When I had the action as part of the URL, MVC would replace with the controller action.
When I made the action part of the request.body, and added the "Content-Type: application/x-www-form-urlencoded" to the header, then the controller model had the expected values.

How to prevent MVC3 input validation in custom binder?

EDIT:
Found the answer here: ASP.NET MVC 3 ValidateRequest(false) not working with FormCollection
Turns out I needed to add System.Web.Helpers so I could use the Unvalidated() extension method on the Request object. That gives you a request that won't throw exceptions on unsafe-looking inputs.
--
So here's the context in which my problem is occurring:
I have a model class which contains a collection of child objects
I've written a constructor which will parse FORM inputs so that I can post the model to an action method
I've set up a binder which grabs the Form object from the posted Request and passes it to my model's constructor
As some of the child objects can accept string inputs which may contain HTML, I need to disable MVC's input validation. I've set a [ValidateInput(false)] attribute on the action method, but HttpRequestValidationException is still being thrown in my model's constructor. On a whim I even tried putting a [ValidateInput] attribute on my model's binder and on the model itself, but that didn't solve the issue either.
I'm at a loss here. How do I go about handling these exceptions in such a way that I can still pull information from the form? Or, what is the appropriate way to go about disabling MVC's input validation in this situation?
Class sketch follows:
public class FooController : ControllerBase {
[HttpPost]
[ValidateInput(false)]
public ActionResult FooAction(FooModel model) { //do stuff; }
}
//tried [ValidateInput(false)] here as well, to no avail
public class FooBinder : BinderBase {
public override object BindModel(...) {
return new FooModel(controllerContext.HttpContext.Request.Form);
}
}
//tried [ValidateInput(false)] here, too....again, no success
public class FooModel {
public FooModel(NameValueCollection formData) {
//do some initialization stuff
var keys = formData.AllKeys; //exception thrown here when inputs contain '<' or '>'
//do some object construction stuff
}
public IEnumerable<FooChid> ChildCollection { get; set; }
}
Try putting the [ValidationInput(false)] on the Post method (where the exception is being thrown) and additionally adding [AcceptVerbs(HttpVerbs.Post)]. But if this is going to be a public website, you're opening yourself up to XXS, which is ill-advised.

Categories