ASP.Net Web API model binding not working like it does in MVC 3 - asp.net-web-api

I was under the impression that model binding in the ASP.Net Web API was supposed to support binding with the same minimum level of functionality supported by MVC.
Take the following controller:
public class WordsController : ApiController
{
private string[] _words = new [] { "apple", "ball", "cat", "dog" };
public IEnumerable<string> Get(SearchModel searchSearchModel)
{
return _words
.Where(w => w.Contains(searchSearchModel.Search))
.Take(searchSearchModel.Max);
}
}
public class SearchModel
{
public string Search { get; set; }
public int Max { get; set; }
}
I'm requesting it with:
http://localhost:62855/api/words?search=a&max=2
Unfortunately the model does not bind as it would in MVC. Why is this not binding as I would expect? I'm going to have a lot of different model types in my application. It would be nice if binding just worked, like it does in MVC.

Take a look at this: How WebAPI does Parameter Binding
You need to decorate your complex parameter like so:
public IEnumerable<string> Get([FromUri] SearchModel searchSearchModel)
OR
public IEnumerable<string> Get([ModelBinder] SearchModel searchSearchModel)

I have found the entire Web API 2 to be a difficult learning curve with lots of "Gotchas" I have read a few of the key books that cover many arcane nuances of this rich product offering. But basically, I thought there must be some core functionality that could take advantage of the best of the features. So, I set out to do four straight forward tasks.
1. Accept a query string, from a browser, into an Api2 Client and populate a simple .NET model.
2. Have the Client submit an async Post to an Api2 Server encoded in JSON extracted from the prior Model
3. Have the Server do a trivial conversion on the Post Request from the Client.
4. Pass it all back up to the Browser. This is it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Combined.Controllers // This is an ASP.NET Web Api 2 Story
{
// Paste the following string in your browser -- the goal is to convert the last name to lower case
// The return the result to the browser--You cant click on this one. This is all Model based. No Primitives.
// It is on the Local IIS--not IIS Express. This can be set in Project->Properties=>Web http://localhost/Combined with a "Create Virtual Directory"
// http://localhost/Combined/api/Combined?FirstName=JIM&LastName=LENNANE // Paste this in your browser After the Default Page it displayed
//
public class CombinedController : ApiController
{
// GET: api/Combined This handels a simple Query String request from a Browser
// What is important here is that populating the model is from the URI values NOT the body which is hidden
public Task<HttpResponseMessage> Get([FromUri]FromBrowserModel fromBrowser)
{
//
// The Client looks at the query string pairs from the Browser
// Then gets them ready to send to the server
//
RequestToServerModel requestToServerModel = new RequestToServerModel();
requestToServerModel.FirstName = fromBrowser.FirstName;
requestToServerModel.LastName = fromBrowser.LastName;
// Now the Client send the Request to the Server async and everyone awaits the Response
Task<HttpResponseMessage> response = PostAsyncToApi2Server("http://localhost/Combined/api/Combined", requestToServerModel );
return response; // The response from the Server should be sent back to the Browser from here.
}
async Task<HttpResponseMessage> PostAsyncToApi2Server(string uri, RequestToServerModel requestToServerModel)
{
using (var client = new HttpClient())
{
// Here the Method waits for the Request to the Server to complete
return await client.PostAsJsonAsync(uri, requestToServerModel)
.ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode());
}
}
// POST: api/Combined This Handles the Inbound Post Request from the Client
// NOTICE THE [FromBody] Annotation. This is the key to extraction the model from the Body of the Post Request-- not the Uri ae in [FromUri]
// Also notice that there are no Async methods here. Not required, async would probably work also.
//
public HttpResponseMessage Post([FromBody]RequestToServerModel fromClient)
{
//
// Respond to an HttpClient request Synchronously
// The model is serialised into Json by specifying the Formatter Configuration.Formatters.JsonFormatter
// Prep the outbound response
ResponseToClientModel responseToClient = new ResponseToClientModel();
//
// The conversion to lower case is done here using the Request Body Data Model
//
responseToClient.FirstName = fromClient.FirstName.ToLower();
responseToClient.LastName = fromClient.LastName.ToLower();
//
// The Client should be waiting patiently for this result
//
using (HttpResponseMessage response = new HttpResponseMessage())
{
return this.Request.CreateResponse(HttpStatusCode.Created, responseToClient, Configuration.Formatters.JsonFormatter); // Respond only with the Status and the Model
}
}
public class FromBrowserModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class RequestToServerModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class ResponseToClientModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
}

Related

ASP.NET Web API - how to pass unknown number of form-encoded POST values

The front-end of my application can send unknown number of POST values inside a form. Fro example in some cases there will be 3 values coming from certain textboxes, in some cases there will be 6 values coming from textboxes, dropdowns etc. The backend is ASP.NET Web API. I know that a simple .NET value can be passed in URI parameter to a "POST Action" using FromURI attribute and a complex type can be passed in body and fetched using FromBody attribute, in any POST Action. But in my case the number of form data values will NOT be constant rather variable and I can't use a pre-defined class to hold values using 'FromBody' attribute.
How can I tackle this situation?
You can use the FormDataCollection from the System.Net.Http.Formatting namespace.
public class ApiFormsController : ApiController
{
[HttpPost]
public IHttpActionResult PostForm(FormDataCollection form)
{
NameValueCollection items = form.ReadAsNameValueCollection();
foreach (string key in items.AllKeys)
{
string name = key;
string val = items[key];
}
return Ok();
}
}
Try to send this properties as list of properties. Make model something like this:
public class PostModel
{
public IEnumerable<PropertyModel> Properties { get; set; }
}
public class PropertyModel
{
public string Value { get; set; }
public string Source { get; set; }
// etc.
}
And action:
public IHttpActionResult Post(PostModel model)
{
//Omited
return Ok();
}

REST API routing standards for complex objects

For example, lets say I have the following model that is used to update a person's details via Web API:
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Are there any standards that dictate how I should structure the API routes for this endpoint when posting this data from the browser?
Would I need to have:
[Route("people/person/{personId}")]
[HttpPost]
public IHttpActionResult SavePerson(int personId, Person personDetails)
Or should I just use:
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson(Person personDetails)
If PersonId is 0 then it is assumed that this is new data and hence a new record will be created, otherwise an update will be performed.
BAsed on my experience the best method to post (or PUt) complex object is:
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson([FromBody]Person personDetails){
// then you can check here if model is valid and if id is ont set ..then call insert CRUD method or UPDATE method..something like:
if(!ModelState.IsValid) return BadRequest(ModelState);
return Ok(personDetails.Id == 0 ? _repository.insert(personDetails) : _repository.update(personDetails));
}
Hope it Help you ..
p.s other REST method for what you described is PATCH
It's not required specify the '[FromBody]' in post action. Rest api send's the data in form body only,if it is complex object.
[Route("people/person")]
[HttpPost]
public IHttpActionResult SavePerson(Person personDetails){
// then you can check here if model is valid and if id is ont set ..then call insert CRUD method or UPDATE method..something like:
if(!ModelState.IsValid) return BadRequest(ModelState);
return Ok(personDetails.Id == 0 ? _repository.insert(personDetails) : _repository.update(personDetails));
}

Serialization attributes on TableEntity in Azure Table Storage

Im using web API to return data in azure table storage. Im returning a class that I 'm inheriting TableEntity in a class and adding properties but want to keep to the .Net convention of capitalized property names but also keep to the JavaScript/json convention of lowercase properties names.
I've tried adding the Json.net property attributes to the class but it appears to be ignored. E.g.:
[JsonProperty("id")]
public string ID {get;set;}
If the instance has a value set on ID, null is represent in the serialized result.
According to your description, I tested this issue on my side and found it works well on my side and Azure. Here is my detailed steps, you could refer to it.
Create a controller named UserInfoController in the Web API application with the Get function like this:
// GET: api/UserInfo
[HttpGet]
public async Task<string> Get()
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(<your-Storage-ConnectionString>);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable cloudTable = tableClient.GetTableReference("UserInfo");
TableQuery<User> query = new TableQuery<User>()
.Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Grade Four"));
var results =await cloudTable.ExecuteQuerySegmentedAsync(query, null);
//Serialize the object to string by using the latest stable version of Newtonsoft.Json
string jsonString = JsonConvert.SerializeObject(results);
return jsonString;
}
Entity
public class User : TableEntity
{
public User(string partitionKey, string rowKey)
{
this.PartitionKey = partitionKey;
this.RowKey = rowKey;
}
public User() { }
[JsonProperty("id")]
public long ID { get; set; }
[JsonProperty("username")]
public string UserName { get; set; }
[JsonProperty("phone")]
public string Phone { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
}
Result
Deploy the Web API application to Azure, then you could find the following result by calling the function via Fiddler.
In summary, please try to check the version of Json.NET you are using. If you aren't using the latest (9.0.1), then please try to upgrade to the latest version and run your application again to find whether it could work as expected.
FYI - while this doesn't answer the direct answer of how to get TableEntity to respect JSON.net attributes... I was able to solve the use case by overriding the ReadEntity and WriteEntity method in the inherited class:
e.g.
public class User : TableEntity{
//Upper case name
public string Name {get; set};
public override void ReadEntity(IDictionary<string, AzureTableStorage.EntityProperty> properties, OperationContext operationContext){
base.ReadEntity(properties, operationContext);
//lower case
this.Name = properties["name"];
}

WebAPI OData v4 - Using a class to represent an action's parameters

I want to add an action to my OData controller. I'll be calling this action with the request body matching the following structure, and with the following validation requirements:
public class PublishModel
{
[Required, EnumDataType(typeof(JobEventType))]
public JobEventType Type { get; set; }
[Required, StringLength(100)]
public string ExternalRef { get; set; }
public DateTimeOffset? DateTime { get; set; }
}
With a normal ApiController, I'd normally have my controller method simply take an argument of this type, and it'd work. With OData, it seems I have to implement my method using a ODataActionParameters argument.
I can't figure out how I'm supposed to tell OData that the body of the request should match the above. The closest I've got is to have it expect it in a parameter:
var pa = mb.EntityType<Edm.JobEvent>().Collection.Action("publish");
pa.ReturnsFromEntitySet<Edm.JobEvent>("jobevent");
pa.Parameter<PublishModel>("evt");
But this requires me to send
{"evt":{"type":"...","externalRef":"...","dateTime":"..."}}
When what I want to send is just
{"type":"...","externalRef":"...","dateTime":"..."}
I understand that I can just specify the properties of my class as individual parameters, but that'll be harder to maintain and I'll lose the data annotation validation. Is there a way to handle this?

MVC3 REST service - how do I access the request body content for a PUT or POST request?

I am creating an ASP.NET MVC3 restful web service to allow reports to be uploaded from a set of servers. When a new report is created, I want the client app to do a PUT to
http://MyApp/Servers/[ServerName]/Reports/[ReportTime]
passing the content of the report as XML in the body of the request.
My question is: how do I access the content of the report in my controller? I would imagine that it is available somewhere in the HttpContext.Request object but I am reluctant to access that from my controller as it is not possible(?) to unit test that. Is it possible to tweak the routing to allow the content to be passed as one or more parameters into the controller method? The outcome needs to be RESTful, i.e. it has to PUT or POST to a URL like the one above.
Currently my routing is:
routes.MapRoute(
"SaveReport",
"Servers/{serverName}/Reports/{reportTime",
new { controller = "Reports", action = "Put" },
new { httpMethod = new HttpMethodConstraint("PUT") });
Is there any way to modify this to pass content from the HTTP request body into the controller method?
The controller method is currently:
public class ReportsController : Controller
{
[HttpPut]
public ActionResult Put(string serverName, string reportTime)
{
// Code here to decode and save the report
}
}
The object I am trying to PUT to the URL is:
public class Report
{
public int SuccessCount { get; set; }
public int FailureOneCount { get; set; }
public int FailureTwoCount { get; set; }
// Other stuff
}
This question looks similar but doesn't have any answer.
Thanks in advance
Seems like you just need to use the standard ASP.NET MVC model binding capability with the slight wrinkle that you would doing an HTTP PUT instead of the more common HTTP POST. This article series has some good samples to see how model binding is used.
Your controller code would then look like:
public class ReportsController : Controller
{
[HttpPut]
public ActionResult Put(Report report, string serverName, string reportTime)
{
if (ModelState.IsValid)
{
// Do biz logic and return appropriate view
}
else
{
// Return invalid request handling "view"
}
}
}
EDIT: ====================>>>
Jon added this code to his comment as part of the fix so I added it to the answer for others:
Create a custom ModelBinder:
public class ReportModelBinder : IModelBinder
{
public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var xs = new XmlSerializer(typeof(Report));
return (Report)xs.Deserialize(
controllerContext.HttpContext.Request.InputStream);
}
}
Modify the Global.asax.cs to register this model binder against the Report type:
ModelBinders.Binders[typeof(Report)] = new Models.ReportModelBinder();

Resources