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?
Related
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();
}
I have the following action signature
[ValidateInput(false)]
public HttpResponseMessage PostParam(Param param)
With Param looking something like this:
public class Param {
public int Id { get; set;}
public string Name { get; set; }
public string Choices { get; set; }
}
Here's the hitch - what comes over the wire is something like this
{
Id: 2,
Name: "blah",
Choices: [
{
foo: "bar"
},
{
blah: "blo"
something: 123
}
]
}
I don't want "Choices" to deserialize - I want it stored as a string (yes, I understand the security implications). Understandably, I get an error because since the default binder does not know this.
Now with Asp Mvc creating a specific ModelBinder would be fairly simple. I'd
inherit DefaultModelBinder
override the property deserialization with my own
set the binder in my Application_Start using Binders.Add
Seems like with Web Api this is a different process - the System.Web.DefaultModelBinder doesn't have anything to override and that I can't hook things up using Binders.Add. I've tried looking around but couldn't find much on how to actually do what I want. This is further complicated since apparently the ModelBinders api changed quite a bit over Beta and RTM so there's a lot of outdated information out there.
In Web API you have to distinguish three concepts - ModelBinding, Formatters and ParameterBinding. That is quite confusing to people moving from/used to MVC, where we only talk about ModelBinding.
ModelBinding, contrary to MVC, is responsible only for pulling data out of URI. Formatters deal with reading the body, and ParameterBinding (HttpParameterBinding) encompasses both of the former concepts.
ParameterBinding is really only useful when you want to revolutionize the whole mechanism (i.e. allow two objects to be bound from body, implement MVC-style binding and so on) - for simpler tasks modifying binders (for URI specific data) or formatters (for body data) is almost always more than enough.
Anyway, to the point - what you want to achieve can very easily be done with a custom JSON.NET converter (JSON.NET is the default serialization library behind Web API JSON formatting engine).
All you need to do is:
public class Param
{
public int Id { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(CustomArrayConverter))]
public string Choices { get; set; }
}
And then add the converter:
internal class CustomArrayConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var array = JArray.Load(reader);
return JsonConvert.SerializeObject(array);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, JArray.Parse(value as string));
}
}
In this case we are telling JSON.NET in the converter to store Choices as string (in the read method), and when you return the Param object with the Choices property to the client (in the write method) we take the string and serialize to an array so that the output JSON looks identical to the input one.
You can test it now like this:
public Param PostParam(Param param)
{
return param;
}
And verify that the data coming in is like you wished, and the data coming out is identical to the original JSON.
I want to know if it is possible or how can I mark fields on my class used as a parameter on my Web API call to be required? I obviously can do this manually once I have received the message, but I was hoping there was something built in the pipeline (like in MVC in combination with jQuery that uses required field annotations to automatically kick back to UI showing required field notations) so I don't have to check everything manually.
Let's say I have the following ViewModel class:
public class PersonViewModel
{
public string FirstName {get; set;}
public string MiddleName {get; set;}
public string LastName {get; set;}
}
Here is my simple Post method on a PersonController
public HttpResponseMessage Post(PersonViewModel person)
{
}
Let's say the FirstName and LastName fields are required but not MiddleName. What I want to know is will the call automatically respond back to the client with a HTTP 400 Bad Request or similar if the Person object does not have one of the required fields populated?
Essentially do I have to do all of this work manually, or is there a way to have the framework handle notated fields automatically, so I don't have a lot of boilerplate validation code for required fields?
Manual Way I'm trying to avoid:
if (ModelState.IsValid)
{
if (person.LastName == string.empty)
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
Any help is appreciated, thanks!
WebAPI does have a validation feature. You should be able to mark the FirstName and LastName properties as [Required] and then use the action filter at the bottom of this blog post to send back an appropriate response:
http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx
You can read more about WebAPI validation here:
http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
Consider the following model which uses XmlSerializer and JSON.net to serialize the object to and from the respective formats.
[XmlRoot("my_model")]
[JsonObject("my_model")]
public class MyModel {
[JsonProperty("property1")]
[XmlElement("property1")]
public string Property1 { get; set; }
[JsonProperty("important")]
[XmlElement("important")]
public string IsReallyImportant { get; set; }
}
Now consider the following ASP.NET MVC 3 action that accept JSON or XML requests and returns model in the respective format (based on the accept header).
public class MyController {
public ActionResult Post(MyModel model) {
// process model
string acceptType = Request.AcceptTypes[0];
int index = acceptType.IndexOf(';');
if (index > 0)
{
acceptType = item.Substring(0, index);
}
switch(acceptType) {
case "application/xml":
case "text/xml":
return new XmlResult(model);
case "application/json":
return new JsonNetResult(model);
default:
return View();
}
}
}
Custom ValueProviderFactory implementations exist for JSON and XML inputs. As it stands the IsReallyImportant is being ignored when the input is being mapped to MyModel. However, if I define the attributes of IsReallyImportant to use "isreallyimportant", then information is correctly serialized.
[JsonProperty("isreallyimportant")]
[XmlElement("isreallyimportant")]
public string IsReallyImportant { get; set; }
As expected the default binder uses the property name when mapping incoming values to the model. I had a look at the BindAttribute, however its not supported on properties.
How does one tell ASP.NET MVC 3 that the property IsReallyImportant should be bound to "important" in the incoming request?
I have too many models to write a custom binder for each. Note that I don't use ASP.NET Web API.
You can do only one custom ModelBinder which will look for JSonProperty and XMLElement attributes to map the right properties. This way you could use it everywhere and you won't have to develop a modelbinder for each model. Unfortunately, there is no other option to modify the property bindings than custom modelbinders.
In ASP.Net Web API (RC) I have a test model class like so:
[ModelBinder]
public class TestRequest
{
public string Foo { get; set; }
public string Bar { get; set; }
}
My controller looks like this:
public class TestController : ApiController
{
public TestRequest Get(TestRequest model)
{
return model;
}
}
Now if I invoke the action via:
http://.../test?foo=abc&bar=xyz
neither values bind, because the model binder is expecting model prefixes, such that I actually need to call:
http://.../test?model.foo=abc&model.bar=xyz
I can understand that this is so that other action parameters can bind correctly, but in my case the model is a clean way of encapsulating all the possible action parameters so that I don't need to have a nasty action method signature with a whole lot of optional parameters. It also allows for easy model validation.
Is there any easy way to cause model binding to behave the same way as it would in MVC, or in a POST request?
Removing the ModelBinder attribute from your model class should work in the example you've posted. You'll run into issues for more complex method signatures, see Rick Strahl's comment: http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx#10302750