Web API model binding - c#

Given the ASP.NET Web API route:
example/{Id}
Which maps to the following ApiController action method:
public void Example(Model m)
{
...
}
With the model class defined as:
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
When I post JSON { "Name": "Testing" } to the URL /example/123 then Id property of the Model object is not bound. It remains as 0 instead of 123.
How can I make the model binding also include values from the route data? I'd prefer not having to write custom model binders for what seems like a common use case. Any ideas would be greatly appreciated. Thanks!

The easiest way to accomplish what you're looking for is just to add another parameter to your method, and specify which parameter comes from which part of the request.
Example:
[HttpPost]
public void Example([FromUri]int id, [FromBody]Model m) {
// ...
}
Now your route of /examples/{id} will successfully be able to bind - because the Example method has a parameter called id which it is looking for in the URI. To populate your model, you can either pass the ID in there as well, or do something like the following:
[HttpPost]
public void Example([FromUri]int id, [FromBody]Model m) {
m.Id = id; // Make sure the model gets the ID as well.
}
Then simply post in { "Name": "Testing" } to /example/123 as you mentioned above.

Our built-in ModelBinding infrastructure doesn't support partial binding of object from both URI and body. You can, however, implement a custom IActionValueBinder to accomplish this task. For example, please refer to the blog post http://blogs.msdn.com/b/jmstall/archive/2012/04/18/mvc-style-parameter-binding-for-webapi.aspx.

The suggested process for this is normally to post to /example instead of /example/{id}. (though with Web API they normally use /api/{controller})
Check out the table half way down this page: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Also check this link on "Inserting a record with ASP.NET Web API" - http://stephenwalther.com/archive/2012/03/05/introduction-to-the-asp-net-web-api.aspx

I don't think the default binder can associate your Id route value to your Model Id property, so whatever d1k_is answer is as good as it gets I'm afraid; either that or writing your own binder (you might be able to write a more generic binder maybe? -if you have like a DomainEntity super class or something like that...)

Related

How do I use WebAPI/Rest correctly when other params are needed

I am new to WebAPI and rest and am trying to do things correctly. By default if I were to access something such as User I would call api/user/5 if I wanted user 5. This would go to my User controller to Get(int num) I think. But I know I will often need other params passed as well. Currently I have Get(JObject data), but that data param is for other parameters. I will need other optional params whether I am sending an ID or wanting a list of everything. How do I go about organizing methods properly with WebAPI? Am I misunderstanding something?
To clarify:
This question is more about REST than dynamic objects, though they play a part:
How do I get a single resource vs a list of resources when I need additional params. I see those concepts as two separate methods, but the additional params complicate it in my mind when routing is involved.
Use attribute routing
For example -
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
or
[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }
if you need to return a list, create a method that returns a list, otherwise return the specific item requested
Look into using JToken or the even more dynamic 'dynamic' (Taken from here)
"
JSON and JavaScript is really dynamic, though, and often it's a hassle to try to "deserialize" really dynamic JSON objects into strongly-typed .NET structures. JSON.NET and ASP.NET Web API's model binding offer a happy medium - a middle ground - called JToken.
public class ContactController : ApiController
{
public JToken Post(JToken contact)
{
return contact;
}
}
Using JToken gives a dynamic container but also a DOM-like navigation model. But if that's not dynamic enough for me, why can't my method's parameter just take a "dynamic."
C# is statically typed, sure, but that doesn't mean I can't statically type something dynamic. ;)
Again, note the watch window.
Using dynamic to catch JSON post payloads
public class ContactController : ApiController
{
public dynamic Post(dynamic contact)
{
return contact;
}
}
"
I think you should make a new object for each WebAPI function that will handle the request. You can make the parameters optional with nullable properties.
[HttpPost]
public void SampleFunction(SampleFunctionModel model)
{
}
where SampleFunctionModel is:
public class SampleFunctionModel
{
public int? Id { get; set; }
public string Name { get; set; }
}

How to "hook" into the pipe where the argument/object gets passed to the action-method

Is there any way to alter the passed object to an action-method before it actually enters/(gets passed to) the action-metod?.. For instance...
By default I want the Index()-action-method to always take an argument of MyClass..
So.. if a user visit mydomain.com/AController/
The triggerd action-method should be
public ActionResult Index(MyClass arg)
{
return View();
}
Im not really sure of how to explain this.. but hopefully you get it..
What I actually want to do is the same thing that Umbraco does in thier MVC-project.. where they always pass-along a RenderModel-object, except that I want to have my own type not the RenderModel..
Thanks in advance!
As I understand your question, you want your action being invoked with a default arg value (if none is provided)
Considering your MyClass as :
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
}
You may define your route like :
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{arg}",
defaults: new { controller="Home",
action = "Index",
arg = new MyClass() { Id = 1, Name = "test" }
}
);
I guess another option would be to have a custom ModelBinder or, at lower level, a custom ValueProvider.
Provided your class is simple enough, there should be no need to intercept the call to the action.
For example, if the class has a properties of int, string and DateTime, even IEnumerable<int> etc, then MVC will map form post data or a querystring to the class automatically.
So, a URL like example.com/your-path/?id=1&categories=3,4,56&tags=this,that would automatically map to a class like:
public YourClass
{
public int Id { get; set; }
public IEnumerable<int> Categories { get; set; }
public IEnumerable<string> Tags { get; set; }
}
If you need to do additional work with the data, for example, if you are posting XML/JSON and need to deserialize it before populating your class, then #jbl's answer is the one for you.
From an Umbraco perspective, if you are hijacking your controllers, then you could simply add your class to the Index() action as an additional parameter, e.g. Index(RenderModel model, YourClass arg). This would pick up and populate any additional data pass to your page.
The problem with #jbl's approach with Umbraco is that Umbraco handles the routing for you, so you can not specify your own routing without bypassing Umbraco's controllers entirely - which defeats the purpose of using Umbraco :)

WebAPI Media Type Formatters not binding JSON?

I'm quite frustrated that I can't figure this out considering I've done some binding in the past with MVC2+3 I have a method as such:
[System.Web.Http.HttpPost]
public ResponseModel Handler([FromBody]RequestModel tableRequest)
{
return CreateTableResponse(tableRequest);
}
Which is fine except that I have no idea how its binding to this model - because I'm not sure how its binding I don't know how to apply custom binding by specifying different names for the values sent (sent with JSON 'application/json'):
public class RequestModel
{
[JsonProperty(PropertyName = "sName")]
public String Name {get;set;}
public List<AEntity> RequestEntities {get;set;}
}
I know that the RequestEntities are going to need some work in binding but why doesn't RequestModel.Name not bind to sName in the JSON? The RequestModel.RequestEntities are really awkward because the JSON being sent is in the form: RequestModel.RequestEntities[0].ID corresponds to iID_0 Great huh? I am unable to change how the JSON is sent.
{
"RequestModel" :
{
"sName" : "john",
"iID_0" : 1,
"iID_1" : 2,
"iID_2" : 3
}
}
Is it possible to specify a MediaTypeFormatter or obtain the JSON as the parameter? How do I bind to that model?
All this magic behind the scenes does not help.
The default ASP.NET ModelBinder can't process this kind of complex object. So, theres two ways to make this work.
1) Implementing an own ModelBinder, you can take a look here: ASP.NET MVC Model Binding, that will take a bit of work to implement.
2) Making a little change to and action (I think that you should do this way!):
Making our data posted with ajax like this:
data: JSON.stringify({ name: 'name here', requestEntities: [...content as an array] }),
Obs: Make sure that every name in JSON matches properties names in the Model, or the default modelbinder will fail.
Sending name and Request separately:
[HttpPost]
public ResponseModel Handler(string name, List<AEntity> requestEntities)
{
//Populate RequestModel here..
return CreateTableResponse(name, tableRequest);
}
Hopes this help you!

Decoration on ViewModel property to use a different name for binding

On MVC3, is there a way to decorate a ViewModel property in order to get the DefaultModelBinder to use a different name for it in the request?
For example, suppose you have the following view model:
public class SomeModel
{
public string Direction {get;set;}
}
But the parameter coming in is Dir from an external source (such as some third-party component, for example).
I know a custom model binder could handle that, but I assume there must be a way to decorate the property, similar to the way action parameters can use Bind(Prefix="...") in order to define that mapping.
You could always create another Property:
public class SomeModel
{
public string Direction {get;set;}
public string Dir
{
get { return this.Direction; }
set { this.Direction = value; }
}
}
I'd also mention that the ViewModel used in a view (cshtml/vbhtml) does not have to be the same ViewModel used on the Post Method.
OK, so after more research looking at similar questions and seeing the feedback here as well, it seems that the answer to my question is basically "NO".
There is no out-of-the-box way, so either custom binders must be used or or the properties should be renamed.
A similar question with a more detailed answer can be found here: How to bind URL parameters to model properties with different names
I was able to accomplish this in ASP.NET MVC Core using the FromForm attribute.
public class DataTableOrder
{
public int Column { get; set; }
[FromForm(Name = "Dir")]
public string Direction { get; set; }
}
Documentation: https://docs.asp.net/en/latest/mvc/models/model-binding.html#customize-model-binding-behavior-with-attributes
However, depending if you do a GET or a POST, you might want to use [FromQuery] instead of [FromForm] I suppose.

asp.NET MVC 2 DataAnnotations UpdateModel<T> validation

I'm trying to use DataAnnotations to add validation to my models in asp.NET MVC 2 RC2, using TryUpdateModel
var user = UserManager.Find(id);
this.TryUpdateModel<IProvisioningObject>(user, form.ToValueProvider());
This updates the model, but the validation is never called. I tried using TryUpdateModel as well (which is the direct type of user), not using the form value provider, using ProvisioningObject directly (which has the validation metadata), to no avail.
Googling for examples only gives me ways to use DataAnnotations by binding through a parameter
public ActionResult Update(User user)
Which I dislike for update scenarios.
Any tips and/or solutions?
EDIT
My objects are auto-generated objects from a WCF service.
I made partials to be able to add DataAnnotations.
I call TryUpdateModel three times because it apparently doesn't support inheritance, which I think is also my problem with DataAnnotations. I specify the validation attributes for ProvisioningObject, and the binding doesn't look for inherited stuff like that.
[MetadataType(typeof(ProvisioningObjectMetadata))]
public partial class ProvisioningObject : IProvisioningObject
{
public string DisplayNameInvariant { get { return string.IsNullOrEmpty(this.DisplayName) ? this.Name : this.DisplayName; } }
}
[MetadataType(typeof(UserMetadata))]
public partial class User : IUser
{
}
public class ProvisioningObjectMetadata
{
[DisplayName("Country")]
public string CountryIsoCode { get; set; }
[Required(ErrorMessageResourceType = typeof(Properties.Validation), ErrorMessageResourceName = "DisplayNameIsRequired")]
[TempValidator]
public string DisplayName { get; set; }
}
public class UserMetadata
{
[DisplayName("Username")]
public string Name { get; set; }
}
// Controller action
public ActionResult Update(string id, FormCollection form)
{
var user = UserManager.Find(id);
this.TryUpdateModel<IUser>(user.User, form.ToValueProvider());
this.TryUpdateModel<IPerson>(user.User, form.ToValueProvider());
this.TryUpdateModel<IProvisioningObject>(user.User, form.ToValueProvider());
if (ModelState.IsValid) // always true
{
return Redirect;
}
else
{
return View();
}
}
If I add the metadata for DisplayName in UserMetadata, it works as expected, but that seems very redundant for nothing. And it would mean I would also have to copy/paste all my inherited interfaces so TryUpdateModel behaves appropriately.
I guess I'm looking for a way that doesn't require me to copy and paste my validation attributes to inherited classes.
New Answer:
"My objects are auto-generated objects from a WCF service."
Autogenerated objects won't have any attributes on them. Are you defining your objects and their attributes on the server side or on the client side?
Old Answer:
If your metadata is not on IProvisioningObject then no validation will be called. The MVC2 default model binder only knows how to find "extra" [MetadataType(buddyClass)] validation information.
For update scenarios bind against DTOs and then map the DTOs, if IsValid() to your main model classes.
Implement IDataErrorInfo interface in your partial class
You will have to write custom validation for each field(where you can use data annotation class to validate each required property)
If you need code example then let me know. I will write it for you!
source: http://www.asp.net/(S(pdfrohu0ajmwt445fanvj2r3))/learn/mvc/tutorial-37-cs.aspx
How do you know that the validation is not being called? Are you checking ModelState.IsValid in your update controller and finding that it is erroneously coming back true?
A typical update pattern is:
UpdateModel(model);
if(!ModelState.IsValid) return View(model);
return RedirectToAction("Index");
If you are expecting some "IsValid" on your model to automatically be called, that will not happen. The data annotations work behind the scenes with the ModelState dictionary on the Controller base class.

Categories

Resources