I have a controller marked with [Route("api/entities")]. There is a method for getting all entities:
[Audit(ActionType.EntityList)] // custom annotation for audit
[Authorize]
[HttpGet]
public IActionResult GetEntities()
{
// ...
}
As you can see, by using annotations I save the request to some audit and authorize the request only to allowed users.
Now, I want to enhance this endpoint so it can return the top N entities. Example request: /api/entities?top=5. I have found that I should use an optional parameter for the method and use if to detect the case.
However, I need to save such call in audit as differnt type (e.g. [Audit(ActionType.EntityTop)]) and I do not need an authorization there (everyone can fetch the top entities).
How can I map the /api/entities request to one method and /api/entities?top=N to another? In Spring from Java I would use a params field of #RequestMapping.
I do not want to change the URL of this endpoint, because the top parameter modifies only the list that is being returned so one should use GET parameters for that. By having the same URL I also do not change the semantic meaning of the response (it is still list of the same entities). It is important when using RESTful frontend framework like Restangular.
Are you sure you need same action? It looks like you want to solve different tasks here, so I'd suggest you to create another action
[Audit(ActionType.EntityTop)]
[HttpGet("top/{top:int}")] //you can specify route via Annotations
public IActionResult GetEntities(int top)
{
// ...
}
Request with top entities will match /api/entities/top/5
Related
this my update endpoint, I want to do something before performing the update, so how I can detect which properties will update or which properties from request body?
public override async Task<IActionResult> Put([FromBody] CategoryTree update)
{
// check which properties update
return await base.Update(update);
}
Compare the request body values against the object you have stored in the database if you want to use it for other logic.
If you're just wanting to update the modified values for an object, Entity Framework handles most of that with change tracking.
I can think in some different approaches:
1️⃣ Using a reduced ViewModel of CategoryTree for your endpoint with only the fields involved in the request.
==> This can be painful since you will need to use different endpoints for different Forms/pages, but maybe is the more ordered way of doing it.
2️⃣ Inspect the Request object to check which are the Form parameters send to the endpoint (not an easy task, but can be done)
3️⃣ Implement some Custom Model Binding to be able to convert your endpoint to something like this:
public override async Task<IActionResult> Put([FromBody] CategoryTree update, List<string> involvedProperties)
Then, in your custom Model Binder you have to fill the List<string> involvedProperties parameter according to what is coming in the request.
Let's say I have
[HttpPost]
public ActionResult<Object> Login([FromBody]LoginViewModel loginViewModel)
{
....
}
[HttpPost]
public ActionResult Logout()
{
....
}
in the same controller.
And I am getting AmbiguousActionException: Multiple actions matched.
The following actions matched route data and had all constraints
satisfied:
...Login
...Logout
I can simply fix it by using the route attribute but I don't understand why core doesn't bind it. I mean the signature is different. ?
The routing mechanism for web api, both Core and .NET framework works the same way. First they take into account controller name, then they look for proper http method and finally they look if the query string parameters match (or parameters contained withing url specified with Route). So if your 2 actions would differ by parameters taken from url, then there is no ambiguity. Parameters carried by body are not analyzed by routing mechanism, even the fact they are or there are none. This stems from a fact that parameters coming by url are just plain strings - easy to compare against. Whereas body is a json and it is more tricky to analyze it. In fact in Login method case there is no parameter loginViewModel in the request - the whole request body gets serialized to LoginViewModel object.
I have an Api controller with the following method:
[Route("{app}")]
public IHttpActionResult Put(string app, Property setting){ //do stuff }
I want to over load it with:
[Route("{app}")]
public IHttpActionResult Put(string app, Property[] settings){
foreach (Property property in settings)
{
Put(app, property);
}
return Ok("Settings Updated");
}
This overload causes a routing conflict. I don't want to change the route. How can I achieve what I want?
You're using attribute routing here with two put requests. Firstly I'd suggest to use a [FromBody] atrribute before the Property[] settings parameter here.
Based on the way you used Route Attributes here points to the same action where you have multiple assigned already. So I can suggest two things here.
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection.
Just use the second action that you mentioned that takes Property[] settings and just check whether the array is empty or not and you really don't have to worry about one setting change or more as essentially it's the same underneath. :)
And as you're using Asp.net Web Api 2 this could contain important information.
Is it possible to omit writing [FromBody]?
[HttpPost]
public string SomeMethod([FromBody]SomeModel model)
{
return "OK";
}
Should I apply some global attribute? How to achieve this?
The model binding attributes in MVC 5 specify a "BindingSource" for each parameter on an action and can specify for each property on a controller, too. You can see specifically the code where it picks it up for the FromBody attribute in the BodyModelBinder
Let me say first that you should beware that as of beta6 (and I think some point in beta5), having multiple parameters/properties with BindingSource.Body is not allowed, and you will not be able to use your actions if you have multiple parameters left as the default. This might actually be desirable if you want to annotate more; my guess is that you do not.
You can see in the DefaultApiDescriptionProvider where the default source is set; unfortunately, there is no hook at this point to tie into, and the method itself is private and not virtual; you'll need to re-implement the entire class.
Once you have, though, it's rather easy to register it in your ConfigureServices to use it instead:
services.TryAdd(ServiceDescriptor.Transient<IApiDescriptionProvider, YourApiDescriptionProvider>());
You can see the default being registered in the source, too.
probably more than one way to achieve this.. here is one way: a custom ActionValueBinder..
Write a custom class to extend from DefaultActionValueBinder.
public class FromBodyParameterBindingActionValueBinder : DefaultActionValueBinder
Override the GetParameterBinding method.
protected override HttpParameterBinding
GetParameterBinding(HttpParameterDescriptor parameter)
{
return parameter.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Post)
|| parameter.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Put) ?
parameter.BindWithAttribute(new FromBodyAttribute()) :
base.GetParameterBinding(parameter);
}
basically what we're saying is that if the Web API system gets a POST or PUT call, make FromBody as the default parameter binding methodology.
Once we have our custom binder, register it globally.
GlobalConfiguration.Services.Replace(typeof(IActionValueBinder), new FromBodyParameterBindingActionValueBinder());
what this does is that for every POST/PUT request, Web API will try to create the model from the request Body. you don't need to provide the [FromBody] attribute explicitly.
having said that, you need to understand the implications of what you're requesting..
Web API has some default rules on model binding. (can be changed)
primitive action parameters are first, looked into the query string
then request body.
complex action parameters are first, looked into the request body.
then query string.
also, web api can only read the first [FromBody] action parameter.. subsequent parameters cannot be read from the body.
the reason i am enumerating the above rules, is so that you're aware of the behavior when you expect some model to be created, but web api does not seem to create it from the body.
for me, explicitly providing [FromBody] is a lot more readable and predictable.. but if you're in full control of your APIs in terms of Http Verbs and expect all requests to be via the Body, it is a perfectly valid thing to do.
I have WebApi controller that receives the request Get all entries:
public IEnumerable <client> GetAllClients ();
I need this same controller method:
public IEnumerable <clients> GetMaxPaymentClient ();
This method also available on request Get, how do I describe this method and how to call the client page?
It won't work having two methods with the same method signature and same HTTP verb at the start of their name in the same controller if you just use the default Web API routing. This operates by convention based on method names and there's no way for it to tell which method you intend to reach using a GET request to your controller.
Ways of differentiating the two methods could be:
Add a [route] attribute to one or both of your methods to differentiate them. More detail on this is at http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2. E.G. you could add [Route("clients/maxpayment")] to your second method.
Move one of the methods to a different controller. This is what I'd recommend if you're writing a REST style API. Max payment sounds like it should be a different resource from just general details for clients
Amend the method signature of the methods so that they're different, such as by adding an ID argument to one of them. Web API will be able to handle separate routes for methods like Get() and Get(string id). Presumably this isn't an option for you as I assume you have your desired method signatures already.