Ignore all parameters in binding except those excplicitly specified in actions signature - asp.net-web-api

I have a web api controller like:
public HttpResponseMessage MyAction(string param1, string param2)
{
//do whatever
}
If i have requests like
Controller/MyAction?param1=a&param2=b
its all well and good.
But if i have something like
Controller/MyAction?param1=a&param2=b&excessParam=c
then i get that no action was found. I understand its due to web api binding rules/priorities.
What i am interested in is if there is some way of simply ignoring all parameters submitted in request except those explicitly specified in controller action signature?
ps.
not talking about optional params as i still have to specify them in actions signature.
i understand it is a bit against nature of web api bindings and i am sure it can potentially cause problems of overlapping/ambigious bindings but i am just curious if there is some solution to this.

But if i have something like
Controller/MyAction?param1=a&param2=b&excessParam=c
then i get that no action was found.
No, that's not quite true. You will get a 404 if you do not provide some of the known parameters:
Controller/MyAction?param2=b&excessParam=c
Excess parameters are ignored by the Web API and will not cause you any troubles.
In order to avoid those 404 errors you could make those parameters optional:
public IHttpActionResult MyAction(string param1 = null, string param2 = null)
{
}
Alternatively write a model containing the known parameters:
public class MyModel
{
public string Param1 { get; set; }
public string Param2 { get; set; }
}
and then:
public IHttpActionResult MyAction([FromUri] MyModel model)
{
}

Related

web api controller action methods

I am trying to get a web api call to work: I want to submit an email address and then on the server, the method will validate and return null or a message.
This is what I tried:
[Post]
public string validate(string email) {
return this._contextProvider.ValidateEmail(email);
}
However, I get this message returned to the client: No HTTP resource was found that matches the request URI 'https://localhost:44300/breeze/data/validate
The payload looks like this: {email: "Greg#gmail.com"}
The problem, it turns out, was the parameter binding.
In the Web API, binding is handling differently than MVC. By default, simple types are extracted from the URI, not the body. Complex types are extracted from the body of the message.
I then added the [FromBody] Attribute to the Action Method, and it then found the action method. But alas, the email parameter was null.
public string validate([FromBody]string email) {
return this._contextProvider.ValidateEmail(email);
}
Turns out when using this trick, the body must NOT be json, but constructed like a querystring - email=greg#gmail.com. I didn't want do do that, so ended up creating a class to accept the parameter, and that worked as expected.
public class ParameterizedAction {
public string Parameter { get; set; }
}
public string validate(ParameterizedAction arg) {
return this._contextProvider.ValidateEmail(arg.Parameter);
}
This article has more info: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection
as well as this one: http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/

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?

Web API ModelBinders - how to bind one property of your object differently

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.

ASP.Net Web API - How can I make it so that prefixes are not required when model binding from the query string?

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

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