how do I get asp.net webapi to look at the route data and body to bind to a complex object?
Using the following route "api/products/{productid}/manufacturers/{manufacturerId}" I need the productId and manufacturerId to bind to a model, so my controller method is as
public IHttpActionResult Create(CreateProductManufacturerModel
createProductManufacturerModel)
and my model is
public class CreateProductManufacturerModel
{
public int ProductId { get; set; }
public int ManufacturerId { get; set; }
public bool IsDefault { get; set; }
public string ManufacturerCode { get; set; }
public string Description { get; set; }
}
I know I could change my method to be as below, but I am using fluentvalidation to validate the whole createproductmanufacturermodel, this is done automatically (see- http://www.justinsaraceno.com/2014/07/fluentvalidation-with-webapi2/). So the productId and manufacturerId would not be validated correctly as the are set as zero.
public IHttpActionResult Create(int productId, int manufacturerId, CreateProductManufacturerModel
createProductManufacturerModel)
I've have tried a modelbinder but it then does not fire the fluentvalidation automatically. Not too sure if this matters, but the body being posted is in a json format.
Thanks in advance.
Paul
Inherit System.Web.Http.Filters.ActionFilterAttribute.
Override OnActionExecuting()
Extract routing data using actionContext.RequestContext.RouteData.Values
Extract model using actionContext.ActionArguments
Validate and assign routing data to model properties
Decorate your action with the new attribute you created.
If this is a less specific use case, you can use reflection to assign routing data to model properties according to param names.
Can you change your action signature to be:
public IHttpActionResult Create([FromBody]CreateProductManufacturerModel
createProductManufacturerModel){}
and then validate the 2 values you need: createProductManufacturerModel.ProductId and createProductManufacturerModel.ManufacturerId ?
Related
I am using asp.net core v2.1, I have a controller inheriting from Controller that contains an action with a parameter decorated with FromQuery based on the following model:
public class PagingControl<T>
{
public ColumnSort[] ColumnSorts { get; set; }
public T ColumnFilters { get; set; }
public int Page { get; set; }
public int PerPage { get; set; }
}
public class ColumnSort
{
public string Field { get; set; }
public SortType Type { get; set; }
}
public enum SortType
{
Asc = 0,
Desc
}
The generic parameter represents a flat poco with nullable properties that provide well defined columns and values to filter with. The PagingControl<T> model describes all parameters required to implement paging with the action.
The ColumnSorts property is a collection as multiple successive column sorting is possible.
I have read Array or List in query string does not get parsed however if I understand this, I cannot have a single model that accepts all query parameters.
In order to successfully implement the full functionality of paging, all parameters are required. This worked fine when ColumnSorts was not a collection, consistent with single column sorting.
Does anyone know of a workaround with a query string for this scenario?
The issue you described above has already been fixed. In addition, even if it's is not fixed, you could walk around it by [FromQuery(Name="xxx")]. See dougbu's walkaround.
It seems that you're using the [ApiController], I test it with 2.1.302 and 2.1.402, it works flawlessly.
Let's say you want to query against MyColoumnFilter, which will be used as your T ColumnFilters in the PagingControl<T> class :
public class MyColumnFilter
{
public string FieldA { get; set; }
public string FieldB { get; set; }
}
and your action method on server side is :
[Route("api/[controller]")]
[ApiController]
public class MyController : Controller
{
// GET api/values
[HttpGet]
public IActionResult Get([FromQuery]PagingControl<MyColumnFilter> pc)
{
return new JsonResult(pc);
}
// ...
}
If you send a request as below :
GET https://localhost:5001/api/my?page=1&perPage=10&columnFilters.fieldA=1&columnFilters.fieldB=2&columnSorts[0].Field=cs1&columnSorts[0].Type=Asc&columnSorts[1].Field=cs2&columnSorts[1].Type=Desc HTTP/1.1
it will work as expected :
The querystring can be divided into 4 parts:
page : an int of 1
perPage : an int of 10
columnFilters : columnFilters.fieldA=1&columnFilters.fieldB=2
columnSorts[] : since the ColumnSorts is an array, we should construct the parameter like columnSorts[0].Field=xx&columnSorts[0].Type=Asc&columnSorts[1].Field=...
As a side note, it will make the querystring rather complicated if you use the GET http method. See Chris Pratt's comment under my another answer.
I'm using the below model to bind the query string with my controller using [FromUri]
namespace MyApi.Models
{
public class Product
{
public int Id { get; set; }
public decimal Price { get; set; }
}
}
The controller action method code is
public ActionResult ActionMethod1( [FromUri] Product product)
The URL incoming is http://myapplication:2020/ActionMethod1/?Id=1&Cost=20
Now, internally the Cost in the query param should be mapped to Price in the Product class.
How can I do that ? I know I can use custom model binding but instead something like,
Is there any attribute that I can apply to the above class? Something like,
[BindingName("Cost")]
public decimal Price {get;set}
I catch post request from 3rd-side static page (generated by Adobe Muse) and handle it with MVC action.
<form method="post" enctype="multipart/form-data">
<input type="text" name="Name">
...
</form>
Routing for empty form action:
app.UseMvc(routes => routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}"));
But in according action I have model with every property is empty
Action:
[HttpPost]
public void Index(EmailModel email)
{
Debug.WriteLine("Sending email");
}
Model:
public class EmailModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Company { get; set; }
public string Phone { get; set; }
public string Additional { get; set; }
}
Request.Form has all values from form, but model is empty
[0] {[Name, Example]}
[1] {[Email, Example#example.com]}
[2] {[Company, Hello]}
[3] {[Phone, Hello]}
[4] {[Additional, Hello]}
Be careful not to give an action parameter a name that is the same as a model property or the binder will attempt to bind to the parameter and fail.
public async Task<IActionResult> Index( EmailModel email ){ ... }
public class EmailModel{ public string Email { get; set; } }
Change the actions parameter 'email' to a different name and it will bind as expected.
public async Task<IActionResult> Index( EmailModel uniqueName ){ ... }
I'm not sure it is same case, but I had same problem and nothing really looks to work for me.
The problem in my case was that I had a property called Model in my view model class
public string Model { get; set; }
When I renamed the property to ModelName everything was working fine again, even without FromForm attribute.
Looks like some special property names could be a bit of a problem for asp.net mvc model binding.
So, my advice is to check your model properties and maybe try renaming them one by one in order to check if the problem is there.
Hope this helps.
ASP.Net Core 3.1
In my case, I was using a complex model (a model that contains other models, like a shared model) posted by Ajax, so the inputs in the view were automatically named like this "ChildModel.PropertyName" (see code)
[HttpPost]
[ValidateAntiForgeryToken] // ("AUVM.PropertyName")
public async Task<JsonResult> AddUser([FromForm]AUVM aUVM) //Parameter name must match the first part of the input name in order to bind
{
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<JsonResult> AddUser([FromForm]AUVM someUniqueName) //This is wrong and won't bind
{
}
I'm having the same problem
this docs helps me to understand Model Binding
https://docs.asp.net/en/latest/mvc/models/model-binding.html
I solved my problem by making sure that the property name is exact match in form field name
and I also add [FromForm] attribute to specify exactly the binding source.
I ran into this today, and though in hindsight it seems obvious, just thought I'd add that you need to make sure your access modifiers for the Properties on the model you're binding are correct. I had public MyProperty { get; internal set; } on some and they would not bind. Removed internal and they worked just fine.
Change void to ActionResult.
[HttpPost]
public ActionResult Index(EmailModel email)
And don't forget verifying AntiForgeryToken from your view and action.
// to your form in view
#Html.AntiForgeryToken()
// ------------
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(EmailModel email)
This issue can also happen if one or more of your properties in the request model fail to bind into an acceptable on the request model.
In my case, I was supposed to pass a List<string> type to a property, but accidentally passed a string. This resulted in the entire request model becoming null
In my case, I was using the MVC 4.0 convention of naming the object you are posting. E.g.,
js: $http.post("SaveAnswer", { answer: answerValues })
C#: public ActionResult SaveAnswer([FromBody] AnswerVM answer) {...}
When I changed the js to $http.post("SaveAnswer", answerValues), everything works.
I have run in to this issue where the model does not bind when I have more than one constructor on the underlying model like:
public class EmailModel
{
public EmailModel()
{}
public EmailModel(string _name, string _company)
{
Name = _name;
Company = _company;
}
public string Name { get; set; }
public string Email { get; set; }
public string Company { get; set; }
public string Phone { get; set; }
public string Additional { get; set; }
}
Fixed by removing all but one constructor like this:
public class EmailModel
{
public EmailModel()
{}
public string Name { get; set; }
public string Email { get; set; }
public string Company { get; set; }
public string Phone { get; set; }
public string Additional { get; set; }
}
I had the same problem and I want to share what happened in my case and how I got the solution. Perhaps, someone may find it useful to know.
My context was ASP.NET Core 3.1 Razor pages.
I had a handler as
public async Task<JsonResult> OnPostLogin([FromForm] LoginInput loginData)
{
}
and also I had a property in my ViewModel as
public LoginInput LoginInput { get; set; }
And in my Razor page, I have used the form like this:
<form asp-page-handler="Login" method="post">
<input asp-for="LoginData.UserNameOrEmail">
.....
So, notice that, in my form, I used the property LoginInput.UserNameOrEmail, but in my handler's parameter, I used the parameter name loginData. How will the ASP.NET Core know that, this LoginInput.UserNameOrEmail is actually loginData.UserNameOrEmail. It can't, right? Therefore, it did not bind my form data.
Then, when I renamed my ViewModel property "LoginInput" to the same name as the handler parameter, like this:
public LoginInput LoginData { get; set; }
The ASP.NET Core then found that the form data was matching the parameter name and then it started to bind properly. My problem was solved.
I would like to bind submission of JSON like this
{
"id": 1,
"name": "bob",
"phone": "(425) 882-8080"
}
to...
class Model
{
public int Id { get; set; }
public string Name { get; set; }
public PhoneNumber Phone { get; set; }
}
where the PhoneNumber class is able to bind to the phone string in the JSON.
The idea was the use a json.net custom converter like:
class Model
{
public int Id { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(PhoneNumberCoverter))]
public PhoneNumber Phone { get; set; }
}
The problem is that MVC is not even trying to use the ReadJson method. Model.Phone == null.
I have tried a few things. Hoping that if I had implicit operative overrides to and from string for the PhoneNumber class, it may just do it automatically. Nope.
What is the correct way to customize model binding for this scenario?
I think you expect that when your action method like
public ActionResult Post(Model myModel)
{
// you expect that myModel should has used JSonConverter and provide you data.
}
Above thing will not work as you expected and reason for this JsonConvertor attribute is specific Json.net and not MVC attribute so DefaultModelBinder will not consider that attribute to bind your data.
Possible correct and simple way to do (But not strongly type)
public ActionResult Post(string jsonData)
{
// Now here use Json.net JsonConvert to deserialize string to object of Type Model.
}
Another solution to is to build your own custom modelbinder and use Json.net inside that.
Why might a developer use the Bind attribute on a ViewModel object in an ASP.NET MVC project and can this have a detrimental effect an application?
[Bind(Include = "Id,Name")]
[MetadataType(typeof (MyViewModelValidation))]
public class MyViewModel
{
public string CustomerProductUserName { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
}
public class MyViewModelValidation
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
[Required]
public string Name{ get; set; }
}
First of all, you don't need to create a MetadataType class for a ViewModel. You can use data annotation attributes directly in your ViewModel. MetadataType classes are used for Models automatically generated by EF or other ORMs, so that you can use data annotation attributes without touching the auto-generated code.
The Bind attribute does not have to be used either - unless you want to use Include or Exclude properties of the Bind attribute, to include or exclude properties in your Model in or from binding, respectively.
For example, in the code in your question, only the Id and Name properties will be bound when submitting your Model from your View. Even if you have an input in your View for CustomerProductUserName, when you submit your form, the property will always be null. This can be useful in cases like where you don't want an auto-generated ID field to be included in binding.
Properties excluded from binding are also excluded from validation, because validation is done as part of model binding. Also, you may use the Bind attribute for security reasons; for instance, when you want to make sure nothing but the properties in your model are being posted to the controller.
The purpose of using bind attribute is to prevent attacker from assigning property value while posting of request or control what properties you want to bind.
Let us suppose, you have a class called Member and a create method that saves member. But you do not want user to send a value for MemberType property.
Class Member
{
public int MemberId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MemberType { get; set; }
}
[HttpPost]
Public ActionResult Create(Member member)
{
Save(member);
}
Let's say for now, you are only offering Regular member type which is the default value. You might think that you can prevent the user to send a value for MemberType property by not allowing input for MemberType Property. But when a user posts the member object, an attacker may intercept the request and send the MemberType value in request, as
MemberId=1&FirstName=Chandra&LastName=Malla&MemberType=Premium and save the member as a Premium member. To prevent this, you can decorate Member class with Bind attribute.
[Bind(Include="MemberId,FirstName,LastName")]
Class Member
{
...
or
[Bind(Exclude="MemberType")]
Class Member
{
...
Now if Member object is posted, MemberType property value will not be posted.
If you are using ViewModel, you might not necessarily have to use bind attribute because you can omit MemberType properties in your ViewModel.
Class Member
{
public int MemberId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MemberType { get; set; }
}
Class MemberViewModel
{
public int MemberId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
[HttpPost]
Public ActionResult Create(MemberViewModel memberviewmodel)
{
Save(memberviewmodel);
}
If you do not nicely design your model and/or ViewModel and do not use bind attribute to avoid posting of property you do not want, that might have detrimental effect.
In addition, if you want to prevent refactoring issues when renaming properties from your model you could do something like:
public async Task<ActionResult> Create([Bind(Include = nameof(Foo.Bar1)+","+nameof(Foo.Bar2)+","+nameof(Foo.Bar3))] Foo fooObj)
If you now e.g. rename "Bar1" your Include Bindings will still work.
You can use the Bind attribute to control how a model binder converts a request into an
object. The most common way that you use the Bind attribute is when you exclude an Id property from binding. For example, the Persons database table includes a column named Id
that is an Identity column. Because the value of an Identity column is generated by the
database automatically, you don’t want to bind a form field to this property.
On the other hand, imagine that a property of a model is especially sensitive, which a malicious user could simply append it in a URL when submitting a form. If this were done, the model binder would happily discover and use the data value in the binding process. By Bind attribute you can protect your application from this kind of attack.
Using the Bind attribute could make problem(s) when you, for example, are going to update an entity and the ID is important for you.