OData attribute routing for deleting relationship between m:m entities - asp.net-web-api

The scenario is an application that uses OData v4, server-side API based on an Entity Framework model using ODataLib, client-side using the OData client code generator extension for Visual Studio
I'm failing to get OData attribute routing working for deleting relationships for entities in m:m relationships, for which the client generates DELETE requests in the form:
http://developer4:8080/odata/tblTestRestaurant(241)/tblTestDishes/$ref?$id=http://developer4:8080/odata/tblTestDish(1)
Attribute routing for POST for creating a link works just fine - the related entity identifier is encoded in the body, and the following controller action method declaration works (the controller itself has [ODataRoutePrefix("tblTestRestaurant")]):
[ODataRoute("({pRestaurantID})/tblTestDishes/$ref")]
[HttpPost]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public async Task<IHttpActionResult> PostAttachtblTestDishes([FromODataUri] int pRestaurantID,
[FromBody] Uri uri) { ... }
But I can't get something similar working for DELETE where the ID of the related entity is specified using the $ref?id=... syntax in the URL.
I have tried the following using the ODataRoute attribute:
[ODataRoute("({pRestaurantID})/tblTestDishes/$ref")]
[HttpDelete]
public async Task<IHttpActionResult> TestRemoveRef1([FromODataUri] int pRestaurantID,
[FromODataUri] Uri relatedUri)
{
throw new NotImplementedException();
}
[ODataRoute("({pRestaurantID})/tblTestDishes/$ref")]
[HttpDelete]
public async Task<IHttpActionResult> TestRemoveRef2([FromODataUri] int pRestaurantID,
[FromODataUri] string relatedUri)
{
throw new NotImplementedException();
}
[ODataRoute("({pRestaurantID})/tblTestDishes/$ref?$id={pRelated}")]
[HttpDelete]
public async Task<IHttpActionResult> TestRemoveRef3([FromODataUri] int pRestaurantID,
[FromODataUri] string pRelated)
{
throw new NotImplementedException();
}
[ODataRoute("({pRestaurantID})/tblTestDishes/$ref?$id={pRelated}")]
[HttpDelete]
public async Task<IHttpActionResult> TestRemoveRef4([FromODataUri] int pRestaurantID,
[FromODataUri] Uri pRelated)
{
throw new NotImplementedException();
}
But none of the above controller actions get hit in response to a DELETE request to http://developer4:8080/odata/tblTestRestaurant(241)/tblTestDishes/$ref?$id=http://developer4:8080/odata/tblTestDish(1).
The only way I can get it working is not to use attribute routing but instead to rely on the OData routing conventions, ie
[HttpDelete]
public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key,
[FromODataUri] string relatedKey, string navigationProperty)
{
throw new NotImplementedException();
}
This method relies on testing the string navigationProperty to work out which collection navigation property on the entity to modify - instead I would prefer to use attribute routing and have a separate action method in my controller for each collection navigation property.
I've used a number of tutorials and documentation in particular https://damienbod.wordpress.com/2014/06/10/getting-started-with-web-api-and-odata-v4/
I have also been through some of the OData WebApi test cases, particularly this one which uses a mixture of attribute routing and OData routing conventions - but doesn't contain an example for attribute routing for deleting links.
So my question is - what ODataRoute attribute syntax and method parameters should I be using, assuming that the ODataRoute attribute does support this ...$ref?id=... syntax in the URL for deletes; and if it doesn't then what alternatives are there?

Web API OData parse the Uri in $Id to create a key segment appended to the origin path segments. So, If you change the template as below, it should work:
[ODataRoute("({pRestaurantID})/tblTestDishes({pRelated})/$ref")]
[HttpDelete]
public async Task<IHttpActionResult> TestRemoveRef([FromODataUri] int pRestaurantID, [FromODataUri] int pRelated)
{
...
}
You can refer to my sample project here. Hope it can help you. Thanks.

Related

Web API Binding Always Null when Premitive type and application/x-www-form-urlencoded used

Following is default method from Web API template. Most of the time I am using application/json as Content-Type but when I used application/x-www-form-urlencoded and pass data to api as value=test. It is failed to recognize or bind.
public void Post([FromBody]string value)
{
}
This thing work when I pass value as =test instead of value=test but if I pass same thing to MVC controller it is working.
If I do something like this then it is working.
public class TestModel
{
public string value {get;set;}
}
public void Post([FromBody]TestModel model)
{
}
What is issue with first method and why it is not working ? Why it is working with MVC Controller or Binding and not with Web API Parameter Binding ?
When you use form data (application/x-www-form-urlencoded) put [FormForm] instead of [FromBody].

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/

Why do we have to specify FromBody and FromUri?

Why are the FromBody and FromUri attributes needed in ASP.NET Web API`?
What are the differences between using the attributes and not using them?
When the ASP.NET Web API calls a method on a controller, it must set values for the parameters, a process called parameter binding.
By default, Web API uses the following rules to bind parameters:
If the parameter is a "simple" type, Web API tries to get the value from the URI. Simple types include the .NET primitive types (int, bool, double, and so forth), plus TimeSpan, DateTime, Guid, decimal, and string, plus any type with a type converter that can convert from a string.
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
So, if you want to override the above default behaviour and force Web API to read a complex type from the URI, add the [FromUri] attribute to the parameter. To force Web API to read a simple type from the request body, add the [FromBody] attribute to the parameter.
So, to answer your question, the need of the [FromBody] and [FromUri] attributes in Web API is simply to override, if necessary, the default behaviour as described above. Note that you can use both attributes for a controller method, but only for different parameters, as demonstrated here.
There is a lot more information on the web if you google "web api parameter binding".
The default behavior is:
If the parameter is a primitive type (int, bool, double, ...), Web API tries to get the value from the URI of the HTTP request.
For complex types (your own object, for example: Person), Web API tries to read the value from the body of the HTTP request.
So, if you have:
a primitive type in the URI, or
a complex type in the body
...then you don't have to add any attributes (neither [FromBody] nor [FromUri]).
But, if you have a primitive type in the body, then you have to add [FromBody] in front of your primitive type parameter in your WebAPI controller method. (Because, by default, WebAPI is looking for primitive types in the URI of the HTTP request.)
Or, if you have a complex type in your URI, then you must add [FromUri]. (Because, by default, WebAPI is looking for complex types in the body of the HTTP request by default.)
Primitive types:
public class UsersController : ApiController
{
// api/users
public HttpResponseMessage Post([FromBody]int id)
{
}
// api/users/id
public HttpResponseMessage Post(int id)
{
}
}
Complex types:
public class UsersController : ApiController
{
// api/users
public HttpResponseMessage Post(User user)
{
}
// api/users/user
public HttpResponseMessage Post([FromUri]User user)
{
}
}
This works as long as you send only one parameter in your HTTP request. When sending multiple, you need to create a custom model which has all your parameters like this:
public class MyModel
{
public string MyProperty { get; set; }
public string MyProperty2 { get; set; }
}
[Route("search")]
[HttpPost]
public async Task<dynamic> Search([FromBody] MyModel model)
{
// model.MyProperty;
// model.MyProperty2;
}
From Microsoft's documentation for parameter binding in ASP.NET Web API:
When a parameter has [FromBody], Web API uses the Content-Type header
to select a formatter. In this example, the content type is
"application/json" and the request body is a raw JSON string (not a
JSON object). At most one parameter is allowed to read from the
message body.
This should work:
public HttpResponseMessage Post([FromBody] string name) { ... }
This will not work:
// Caution: This won't work!
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }
The reason for this rule is that the request body might be stored in a
non-buffered stream that can only be read once.
Just addition to above answers ..
[FromUri] can also be used to bind complex types from uri parameters instead of passing parameters from querystring
For Ex..
public class GeoPoint
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
[RoutePrefix("api/Values")]
public ValuesController : ApiController
{
[Route("{Latitude}/{Longitude}")]
public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
}
Can be called like:
http://localhost/api/values/47.678558/-122.130989
When a parameter has [FromBody], Web API uses the Content-Type header to select a formatter. In this example, the content type is "application/json" and the request body is a raw JSON string (not a JSON object).
At most one parameter is allowed to read from the message body. So this will not work:
// Caution: Will not work!
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }
The reason for this rule is that the request body might be stored in a non-buffered stream that can only be read once.
Please go through the website for more details:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

ObjectId Model Binding with Web Api 2

I'm using MongoDB with an ASP.NET Web Api (2) application, and want to accept ObjectId arguments in the Web Api methods.
I've written a custom model binder for the ObjectId type, and it when adding it to the Get method of a controller, everything works.
[Route("{id}")]
public HttpResponseMessage Get(String type, [ModelBinder(typeof(ObjectIdModelBinder))]ObjectId id) {
But I need to do this in several methods and controllers, so I would rather put it somewhere central. I've read that I can register the binder centrally like this:
public static void Register(HttpConfiguration config) {
var provider = new SimpleModelBinderProvider(typeof(ObjectId), new ObjectIdModelBinder());
config.Services.Insert(typeof(ModelBinderProvider), 0, provider);
}
But that's not working!
Any ideas? Not really sure what the config.Services collection should contain, but I'm having a hard time locating the binder I insert.
With WebAPI even if you register a model binder, you still need to attach the [ModelBinder] to the input parameter, you just don't have to specify the type anymore so your method now looks like this:
[Route("{id}")]
public HttpResponseMessage Get(String type, [ModelBinder]ObjectId id) {
If you want to skip adding the attribute every time you declare a parameter of this type, then you have to look at writing a custom IActionValueBinder (which can be made very simple if you just extend the DefaultActionValueBinder) which is the default implementation. You might want to look at this post for pointers:
http://www.strathweb.com/2013/04/asp-net-web-api-parameter-binding-part-1-understanding-binding-from-uri/

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