How to prevent MVC3 input validation in custom binder? - c#

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.

Related

Not entering Controller while validating with Data Annotations

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.

Do I need to active .NET Core MVC Model Validation or is it activated by default

Testing my web API (nuget package Microsoft.AspNetCoreAll 2.0.5) I run into strange issues with the model validation using annotations.
I have (for example) this controller:
[HttpPost]
public IActionResult Create([FromBody] RequestModel request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// create
request.Name.DoSomething();
return Created(...);
}
I defined my RequestModel as follows:
public class RequestModel
{
[Required]
public string Name {get; set};
}
My problem although I defined RequestModel.Name as [Required] it is null (if Name is not present in the json from the body. Which I thought should not happen since it is marked as [Required] and be automatically appear as ModelState error.
Given this link to the specs they use Bind(....).
So my question?
Do I have to enable it everytime or should it work out of the box or how is it intended to be used?
If I annotate it with [Required] I would assume that at least ModelState.IsValid returns false if it is not present.
Using Bind in the link seems a bit complicated for me in cases where I have multiple objects nested into each other.
Edit 1: created a MVC data validation test bed
To better visualize what I mean and so everyone can easily experiment on their own I created the small demo .NET Core MVC data validation test bed on GitHub.
You can download the code, start it with VS 2017 and try it out your own using the swagger ui.
Having this model:
public class StringTestModel2
{
[Required]
public string RequiredStringValue { get; set; }
}
And testing it with that controller:
[HttpPost("stringValidationTest2")]
[SwaggerOperation("StringValidationTest2")]
public IActionResult StringValidationTest2([FromBody] StringTestModel2 request)
{
LogRequestModel("StringValidationTest2", request);
if (!ModelState.IsValid)
{
LogBadRequest(ModelState);
return BadRequest(ModelState);
}
LogPassedModelValidation("StringValidationTest2");
return Ok(request);
}
The results are far way from expected:
Giving a null (not the string "null") is allowed and return 200 OK
Giving an int is allowed an returns 200 OK (it gets converted to a string)
Giving a double is allowed and returns 200 OK (if possible it gets converted to string, if not convertible (mixing points and semicolons return 400 Bad Request)
if you just send empty curly brackets and leave RequiredStringValue undefined it passes and returns 200 OK (with the string as null).
Leaving me (for now) with one of the follwoing conclusions:
either MVC data validation does not work out of the box
either does not work as expected (if one marks a property as required it should be made sure it is there)
either MVC data validation is broken
either MVC data validation is completly useless
we are missing some important point (like the Bind[])
You get ModelValidation automatically as part of using/deriving from controller (I believe it is in the MVC middleware) but, unfortunately, this does not include null checks. So you need to explicitly check the parameter is NULL as well as the ModelState check.
[HttpPost]
public IActionResult Create([FromBody] RequestModel request)
{
if (request == null || !ModelState.IsValid)
{
return BadRequest(ModelState);
}
...
I assume you use
services.AddMvc();
so it should work by default.
But it doesn't work just as you expect: instead of returning 400 status code it invalidates model state and lets you manage action result.
You can create an attribute class to automatically return "Bad request"
internal class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(
new ApiError(
string.Join(" ",
context.ModelState.Values
.SelectMany(e => e.Errors)
.Select(e => e.ErrorMessage))));
}
}
}
where ApiError is a custom ViewModel for error results.
Now you can mark controllers or actions with this attribute to achieve a behavior you expect to have by default.
If you want this behavior for all methods just change your AddMvc line to something like this:
services.AddMvc(config => config.Filters.Add(new ValidateModelAttribute()));
After further experimenting around I found the answer.
Does the data validation need to be activated?
Answer: it depends on your configure services method:
No, it does not need to be activated if you use
services.AddMvc();
Yes, it needs to be activated if you use
services.AddMvcCore()
.AddDataAnnotations(); //this line activates it
This article brought me to the answer.

Why does the model binder need an empty constructor

I need some help with some fundamentals here...
I have this controller that serves up my view with an instance of a class (at least that's how I think it works). So since I am giving my view a new instance of the object, why does it have to create a NEWer one for the model binding for my post back? Please look at the below example.
[HttpGet]
public ActionResult Index(){
int hi = 5;
string temp = "yo";
MyModel foo = new MyModel(hi, temp);
return View(foo);
}
[HttpPost]
public ActionResult Index(MyModel foo){
MyModel poo = foo;
if(poo.someString == laaaa)
return RedirctToAction("End", "EndCntrl", poo);
else
throw new Exception();
}
View:
#model myApp.models.MyModel
#html.EditorFor(m => m.hi)
<input type="submit" value="hit"/>
Model:
public class MyModel {
public int hi {get; set;}
public string someString {get; set;}
public stuff(int number, string laaaa){
NumberforClass = number;
someString = laaaa;
}
}
Why do I need a blank constructor? Furthermore, if I had an parameterless constructor, why would poo.someString change by the time I got to RedirctToAction("End", "EndCntrl", poo)?
Why do I need a blank constructor?
because of
[HttpPost]
public ActionResult Index(MyModel foo){ ... }
You asked the binder to give you a concrete instance on Post, so the binder needs to create that object for you. Your original object does not persist between the GET and POST actions, only (some of) its properties live on as HTML fields. Thats what "HTTP is stateless" means.
It becomes a little more obvious when you use the lower level
[HttpPost]
public ActionResult Index(FormCollection collection)
{
var Foo = new MyModel();
// load the properties from the FormCollection yourself
}
why would poo.someString change by the time I got to RedirctToAction("End", "EndCntrl", poo)?
Because someString isn't used in your View. So it will always be blank when you get the new model back. To change that:
#model myApp.models.MyModel
#html.HiddenFor(m => m.SomeString)
#html.EditorFor(m => m.hi)
<input type="submit" value="hit"/>
this will store the value as a hidden field in the HTML and it will be restored for you on POST.
There is no direct connection between model you pass to view and model that you receive in request. In ultimate case the code for initial request and response will run in different instance of IIS or even different machines.
So when request come back ASP.Net MVC need to recreate all objects (controller, model,...). Having default constructor allows run-time to create new object without knowledge of particular arguments to your custom constructor.
Side note: Similar reconstruction for constructor exist for generics where you can only specify where T:new() for default constructor.
I am a little confused by the question but instead have you tried this:
[HttpPost]
public ActionResult Index(MyModel foo){
if(foo.someString == "laaaa")
return RedirctToAction("End", "EndCntrl", foo);
else
throw new Exception();
}
You only need a parameterless constructor if you added a parameterized constructor.
Ex: MyObject item = new MyObject();
It doesn't need a parameter less constructor as long you do not define any constructors. If you define a constructor with parameters, you need then a parameter less constructor as it is the one used by the model binder..
when you postback the values, the binder will map your request in a typed object, it first creates the object, and then tries to map your posted values to some property.
If you can not have a parameter less constructor... if code is not Under your control, then you have to create a Custom binder.

ActionResult cast parameter base class to derived class

I've written a base class and some classes which derive from it.
I want to use these classes in one ActionResult, but if I'm trying to cast PSBase to PS1 I'm getting a System.InvalidCastException that type PSBase can not be converted to PS1.
Classes:
public class PSBase
{
public int stationId { get; set; }
public string name { get; set; }
}
public class PS1 : PSBase
{
public string reference { get; set; }
}
public class PS2 : PSBase
{
}
ActionResult:
[HttpPost]
public ActionResult ProductionStep(PSBase ps)
{
if (ModelState.IsValid)
{
var product = db.Product.FirstOrDefault(.........);
switch (ps.stationId )
{
case 1:
{
product.Reference = ((PS1)ps).reference;
db.SaveChanges();
break;
}
}
}
return View();
}
As I don't want to have for each class a own ActionResult (repeating much of the same code many times) I wanted put all this to one ActionResult. Any Ideas how I could implement this?
What you are trying to do will never work without custom ModelBinder (and even then it will be a huge mess I'd not recommend to implement), sorry.
Only when you are passing a model from Controller to View it remembers what type it was originally (including inheritance, etc.) because at that point you are still on the server side of the page and you are merely passing an object.
Once you enter a view and submit a form all that does it creates some POST request with body containing list of values based on input names.
In your case if you have a form based on PS1 and used all the fields as inputs, you would get something like:
POST:
stationId = some value
name = some value
reference = some value
(there is no mention of the original type, controller, method, etc.)
Now, what MVC does is that it checks what argument you are using in the header of the method (in your case ProductionStep(PSBase ps)).
Based on the argument it calls a model binder. What the default model binder does is that it creates new instance of that class (in your case PSBase) and goes via reflection through all the properties of that class and tries to get them from the POST body.
If there are some extra values in the POST body those are forgotten.
Unless you write a custom model binder for this default MVC implementation can't help you there.
I'd recommend creating two separate methods, one of each accepting different implementation of PSBase.
If you want to read more on Model Binders check this out http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
EDIT:
By creating two separate methods I mean something like this:
[HttpPost]
public ActionResult ProductionStepA(PS1 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
[HttpPost]
public ActionResult ProductionStepB(PS2 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
then you have to distinguish between them in the view via different form action.

ASP.net c#, Same name method that takes different argument type [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Can you overload controller methods in ASP.Net MVC?
I need to 2 methods that takes different type of argument. so I tried this,
[HttpPost]
public ActionResult ItemUpdate(ORDER ln)
{
// do something
}
[HttpPost]
public ActionResult ItemUpdate(List<ORDER> lns)
{
// Do Something
}
but it does not work.
No error while compiling, but when run, it makes an error.
How I write the code to make that works?
Thanks!
[Edit]
[HttpGet]
public ActionResult ItemUpdate(string name)
{
return Content(name);
}
[HttpGet]
public ActionResult ItemUpdate(int num)
{
return Content(num.ToString());
}
and when I call /Test/ItemUpdate/
it make an error,
Server Error in '/' Application.
The current request for action 'ItemUpdate' on controller type 'OrderController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult ItemUpdate(System.String) on type Ecom.WebUI.Controllers.OrderController
System.Web.Mvc.ActionResult ItemUpdate(Int32) on type Ecom.WebUI.Controllers.OrderController
[EDIT]
It does not match with ORDER even single parameter.
if (lns.GetType() == typeof(ORDER))
{
// always false
}else{
// also I can not cast the object.
ORDER ln = (ORDER)lns; //Unable to cast object of type 'System.Object' to type 'ORDER'
}
Overloaded actions are not supported in MVC. The dispatcher can not tell the difference between the two Actions. You can get around this by giving one of your actions the [HttpGet] attribute and the other one the [HttpPost] attribute.
If that isn't an option (or if you have three or more overloads), you can always dispatch the Action yourself, by using an object parameter and using run time type identification to select the correct function to call. E.g.:
[HttpPost]
public ActionResult ItemUpdate(object arg)
{
if (arg.GetType() == typeof(ORDER))
{
return ItemUpdateOrder((Order)arg);
}
else if (arg.GetType() == typeof(List<ORDER>))
{
return ItemUpdateList((List<Order>)arg);
}
}
public ActionResult ItemUpdateOrder(ORDER ln)
{
//...
}
public ActionResult ItemUpdateList(List<ORDER> lns)
{
//...
}
You can't do that in a controller. You will have to change the second method name.
[HttpPost]
public ActionResult ItemUpdates(List<ORDER> lns)
{
// Do Something
}
public ActionResult ItemUpdates(object myInputValue)
{
if (myInputValue.GetType() == typeof(string)
// Do something
else if (myInputValue.GetType() == typeof(List<ORDER>))
// Do something else
}
You can then cast the object to your type of choice and manipulate normally.
In ASP.NET it's not possible to have overloaded methods without an ActionFilter attribute to distinguish these actions. The reason for this is that the ActionInvoker (a class used inside of the Controller base class to invoke actiosn) cannot determine which method to call since it would need to "ask" a ModelBinder (which are responsible to construct action argument objects) for every overload if the ModelBinder could construct that argument object from the HTTP request passed. For simple scenarios this would work but in more complex scenarios this would fail because the ModelBinder would succeed in binding arguments of multiple overloads. Not to allow overloads in ASP.NET MVC is quite clever design decision.
To solve your problems you can fix ItemUpdate HTTP GET action by just leaving the second action away and having just one action, because a ModelBinder does not mind if a value that is passed as URL parameter for example is a string or an int.
[HttpGet]
public ActionResult ItemUpdate(string name)
{
return Content(name);
}
For the ItemUpdate HTTP POST version I'd recommend to rename one of these actions or to have only one action, the list version because updating a single ORDER is only a specific case of updating multiple ORDER objects.

Categories