Here is what I have now: One route and all controllers so far confirm to it and work great. We want to keep those as is.
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "DitatApi",
routeTemplate: "api/{controller}/{action}"
Now we created new controller but need to route it differently. Below is a controller code and how those methods should be routed. How can I setup such route?
public class CarrierController : ApiController
{
[HttpGet]
public object Get(string id, int? key, string direction)
{
return null;
}
[HttpPost]
public object Update()
{
return null;
}
[HttpDelete]
public object Delete(int key)
{
return null;
}
[HttpGet]
public object GenerateRandomObject(int randomParam)
{
return null;
}
}
GET /api/carrier?id=<id>&key=<key>&direction=<direction>
POST /api/carrier
DELETE /api/carrier?key=<key>
GET /api/carrier/random?randomParam=<random>
WebApi v2 introduced the Route Attributes and those can be used along with your Controller class and can facilitate the routing configuration.
For example:
public class BookController : ApiController{
//where author is a letter(a-Z) with a minimum of 5 character and 10 max.
[Route("html/{id}/{newAuthor:alpha:length(5,10)}")]
public Book Get(int id, string newAuthor){
return new Book() { Title = "SQL Server 2012 id= " + id, Author = "Adrian & " + newAuthor };
}
[Route("json/{id}/{newAuthor:alpha:length(5,10)}/{title}")]
public Book Get(int id, string newAuthor, string title){
return new Book() { Title = "SQL Server 2012 id= " + id, Author = "Adrian & " + newAuthor };
}
...
However, please note that query parameters ?var1=1&var2=2 are not subject to evaluation to decide which API method will be used.
WebApi works based on reflection, so, this means that your curly braces {vars} must match the same name in your methods.
Therefore to match something like this api/Products/Product/test your template should look like this "api/{controller}/{action}/{id}" and your method needs to be declare like this:
[ActionName("Product")]
[HttpGet]
public object Product(string id){
return id;
}
Where the parameter string name was replaced by string id.
Related
Consider a situation where we can have several mappings with the same regular expression, which should be validated programmatically (for instance against database).
(this is not a valid piece of code, I am trying just to explain what I am trying to achieve. Note the regular expressions in the url path)
// Animal controller
#GetMapping(path = "/{animal-category [a-z-]+}/{animal-name [a-z-]+}")
public void show(#PathVariable String animalCategory, #PathVariable String animalName) {
// if animalCategory is not found in database, continue with next controller
}
// Plants controller
#GetMapping(path = "/{plant-category [a-z-]+}/{plant-name [a-z-]+}")
public void show(#PathVariable String plantCategory, #PathVariable String plantName) {
// if plantCateogry is not found in database, continue with next controller - as there is no more, it should return 404
}
You can achieve this problem with a general controller method like this:
// General controller method
#GetMapping(path = "/{category [a-z-]+}/{name [a-z-]+}")
public void show(#PathVariable String category, #PathVariable String name) {
// look in database for the category
if(isAnimalCatagory) {
return showAnimal(category, name);
}
else if(isPlantCategory) }
return showPlant(category, name);
}
return "redirect:/404";
}
public void showAnimal(String animalCategory, String animalName) {
// for animal categories
}
public void showPlant(String plantCategory, String plantName) {
// for plant categories
}
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();
}
Why can't Web API Core 2 tell these apart?
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values?name=dave
[HttpGet]
public string Get(string name)
{
return $"name is {name}";
}
Here's what happens -
Both http://localhost:65528/api/values and http://localhost:65528/api/values?name=dave cause the first Get() method to execute.
This exact code works fine in Web Api 2.
I know multiple ways of getting around this, but I don't know why it happens.
Can someone explain why this has changed?
I don't think you can even compile your code in ASP.NET Core Mvc 2.0 since you have 2 actions mapped to same route [HttGet] api/values:
AmbiguousActionException: Multiple actions matched.
Remember, ASP.NET Web API uses the HTTP verb as part of the request to figure which action to call. Although it uses conventional routing (you name your actions Get, Post, Put and Delete, etc) if you don't have route attribute specify, I would highly recommend to always use routing attribute to annotate your controllers and actions.
Api Design time
Now it's up to you to design the route, as a developer. Remember the route is supposed to be a Uri that can identify a resource / resources.
Use the name as identifier along with the route
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")] // constraint as a string
public IActionResult GetByName(string name)
{
...
}
}
Use the name as filter, against the resource collection
[Route("api/[controller]")]
public class CustomersController : Controller
{
// api/customers
// api/customers?name=dave
[HttpGet]
public IActionResult Get(string name)
{
...
}
}
To confuse you more
api/customers/dave will still execute GetById first!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
[HttpGet("{name}")]
public IActionResult GetByName(string name)
{
...
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
...
}
}
Both methods GetByName and GetById are potential candidates but MVC picks GetById method first because MVC compares the method/template name {name} and {id} through case-insensitive string comparison, and i comes before n.
That's when you want to put constraints.
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}")]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
You can also specify the Ordering too!
[Route("api/[controller]")]
public class CustomersController : Controller
{
[HttpGet]
public IActionResult Get()
{
...
}
// api/customers/portland
[HttpGet("{city:alpha}", Order = 2)]
public IActionResult GetByCity(string city)
{
...
}
// api/customers/dave
[HttpGet("{name:alpha}", Order = 1)]
public IActionResult GetByName(string name)
{
...
}
// api/customers/3
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
...
}
}
Without the Order, the method GetByCity will be in favor than GetByName because character c of {city} comes before the character n of {name}. But if you specify the order, MVC will pick the action based on the Order.
Sigh the post is too long....
Because in your case the best match in the route pipeline is the default httpget attribute (the one that get all). The query is a regular string so if you don't ask what you want from the query the best match is still the one that get all.
[HttpGet]
public string Get([FromQuery]string name)
{
return $"name is {name}";
}
The [FromQuery] is pointing to the key "name" in the query string to get the value.
Your should read Routing in asp.net core
I have a base request type..
class RequestBase
{
public string inputId;
public string derivedid;
}
and types that inherit ..
class RequestA : RequestBase
{
public string name;
}
and
class RequestB : RequestBase
{
public string color;
}
I have a webapi service, some actions take an input parameter of RequestA, some take RequestB
[HttpPost]
[MyFilter]
[ActionName("Process1")]
public HttpResponseMessage Process1(RequestA request)
{
//do something with request.derivedId
}
[HttpPost]
[MyFilter]
[ActionName("Process2")]
public HttpResponseMessage Process2(RequestB request)
{
//do something with request.derivedId
}
I have an actionfilter that takes the inputId from the request and generates a derivedId
public override void OnActionExecuting(HttpActionContext actionContext)
{
RequestBase request = (RequestBase)actionContext.ActionArguments["request"];
string inputId = request.inputId;
string derivedId = inputId + "123";
// ?? somehow inject derivedId back into the actionContext so that my controller methods can access?
}
As my comment states above, I'd like to populate the derivedId field and have it accessible to my controller methods.
Thanks in advance
There's a few solutions to this problem already described in this thread - one of them should suit you:
ASP.NET MVC Pass object from Custom Action Filter to Action
Consider a model class
public class MyModel
{
public string Id { get; set; }
/* some other properties */
}
And a controller
public class MyController
{
[HttpPut]
public ActionResult Update(string id, MyModel model)
{
/* process */
}
}
The routing is registered as follows:
protected override void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("MyController",
"api/my/{id}",
new { action = "Update", controller = "My"},
new { httpMethod = new HttpMethodConstraint(new[] { "PUT" }) });
}
When using a REST client and sending MyModel serialized as a JSON or XML request to this controller, a null "Id" property of "MyModel", overrides the "id" parameter of the action method, even if you post it to http://api.example.com/api/my/10.
How does one force ASP.NET MVC 3 to populate the "id" property from the URL (in this case "10") and ignore the "Id" property of the "MyModel"?
Note that I'm not using ASP.NET Web API.
Try using attribute [FromUri]. It's in "System.Web.Http". This attribute on action param id indicates it should be bonded using the url request.
using System.Web.Http;//at the top
public class MyController
{
[HttpPut]
public ActionResult Update([FromUri]string id, MyModel model)
{
/* process */
}
}
For MVC3 try to include web-api package(from nuget or manually) to use [FromUri] attribute. IF that is not possible then the only way I can think of getting it is from this.HttpContext.Request.QueryString["id"]
Instead of having id as a action method paramter declare it in action body. May have to change the url query api/my?id=1212. First try using api/my/{id} format.
var id = this.HttpContext.Request.QueryString["id"];