Web Api routing limitations? - asp.net

Say I have this 2 actions in my Api Controller :
[HttpGet]
public Product ProductById(int id)
{
...
return item;
}
[HttpGet]
public string ProductByString(string st)
{
return "String Result";
}
I also have these 2 routes :
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new
{
id = RouteParameter.Optional
});
config.Routes.MapHttpRoute("DefaultApi2", "api/{controller}/{st}");
Running http://mysite:9000/api/products/1 will work.
It will catch the first route because there's a match.
if I wanted the STRING action version I must do :
http://mysite:9000/api/products/?st=blabla this will be using ALSO the first route. ( query string is not negotiation to match a route).
If I swap the routes , only the DefaultApi2 route is hit. ( I do understand why it is happening).
So my conclusion is that I will never be able to do both:
http://mysite:9000/api/products/1
And
http://mysite:9000/api/products/Guitar
In other words - the second route will never be a match.
Question
Besides
mentioning the {action} name in the route (not recommended ,web Api conventions)
using route attribute ( I dont use webApi 2)
What is the right way of solving this issue ? I can see how it can be complicated ( I 'll have to remember : only the int ID has the x/y/id thing , for others I must use querystring etc etc.... I can get lost easily - in a big application)

I guess it can be fixed with three routes:
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
contraint: new { id = #"\d+" }
);
// only matches numeric id's
config.Routes.MapHttpRoute("DefaultApi2", "api/{controller}/{st}");
// only matches having a parameter, no matter what data type
config.Routes.MapHttpRoute("DefaultApi3",
"api/{controller}/{id}",
new
{
id = RouteParameter.Optional
});
// matches the empty id

Related

How to know which route is currently mapped

I have a route as
routes.MapRoute(
"User", // Route name
"Person/{action}", // URL with parameters
new { controller = "User" } // Parameter defaults
);
that means if I put url like
http://localhost/myApp/Person/Detail
then it should invoke Detail action of User controller, right?
Ok, I have done it and routing also works good, means it invoke action properly.
Now if I want to get controller name then I will use
ControllerContext.RouteData.Values["controller"];
and that will give me User, but I want it to be Person (i.e. as in URL). How can I get that?
The Request.Url property of Controller will return a Uri object containing details of the current url, including the segments.
string[] segments = Request.Url.Segments;
// returns ["/", "Person/", "Detail"]
string value = segments[1].Remove(segments[1].Length - 1);;
// returns "Person"
you can get controller name by following code
HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
If you're in a view, then you can do:
ViewContext.RouteData.Values["Controller"]
and for custom url you can define
[Route("myApp/Person/{action?}")]
public ActionResult View(string id)
{
if (!String.IsNullOrEmpty(id))
{
return View("ViewStudent", GetStudent(id));
}
return View("AllStudents", GetStudents());
}

Definition of routes with ASP.net and C #

I have a problem with the definition of routes with ASP.net and C #, using the GET verb.
I get this URL,
http://123.45.67.89:39051/dev/point/save?name=125.25
I get this URL, where point changes according to where I want to search the data. The dev and save parameters are constant. The number of points is large, would need to make this could take any value with characters and numbers.
Modify the file WebApiConfig, trying it can take any value and did not work.
For example:
point12
point23
point24
MzaB342
Pozo123
MzaE258
WebApiConfig
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "dev/{points}/save",
defaults: new { points= RouteParameter.Optional }
constraints: new { points= #"^[a-z]+$" }
);
How can I do this?
It is correct that I have to put "^ [a-zA-Z0-9] + $" to take qur numbers, but if problem persists tel sent a query a requirement
http://123.45.67.89:39051/dev/values/save?name=125.25
Works fine.
If I want to consult:
http://123.45.67.89:39051/dev/point12/save?name=125.25
I get a 404 error, because there is no route point12, these are name within a database, and there are a large amount, could not generate a route for each of them, have to redirect them to one and then to decode this route and assign the value.
Or what I get after http://123.45.67.89:39051/dev/xxyyzz121/save take it correctly, I separate the information from the URL, which allows me to control the request for each point, using data binding or Parse Query.
I can not change the way I get the URL, that comes from another system.
Try to test the solution to put the path and when I enter the class, have an error in ActionResult, you need to load System.Web.Mvc, as I am using WebAPI without MVC, can not find it, if it gives me errors added in WebAPI other classes.
As could be done to define default routes.
Web.Api.Config Code:
namespace WebApp_dev
{
public static class WebApiConfig
{
public static void Register (HttpConfiguration config)
{
// Configuration API and Web services
// Web API routes
config.MapHttpAttributeRoutes ();
config.Routes.MapHttpRoute (
name: "DefaultApi"
routeTemplate "dev / {controller} / {id}",
defaults: new {controller = "values", id = RouteParameter.Optional,},
constraints: new {controller = # "^ [a-zA-Z0-9] + $"}
);
}
}
}
ValuesController.cs Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace WebApp_dev.Controllers
{
public class ValuesController: ApiController
{
// GET api / values
public IEnumerable <string> Get ()
{
return new string [] {"value1", "value2"};
}
// GET api / values ​​/ 5
public string Get (int id)
{
return "value";
}
// POST api / values
public void Post ([FromBody] string value)
{
}
// PUT api / values ​​/ 5
public void Put (int id, [FromBody] string value)
{
}
// DELETE api / values ​​/ 5
public void Delete (int id)
{
}
}
}
No, the name of the route changes, they are values​​, the URL that can reach between muchs are:
/dev/nombre1/save?name1=123.56
or
/dev/point123/save?name12=12
or
/dev/pozo12/save?value1=13
or
/dev/mbz134/save?costo2=13
or
/dev/patag235/save?name8=13
What changes is name1, point123, pozo12, mbz134 etc. The amount is large, are stored in a database, and according to that comes, it is the search is done and the response sent.
The beginning of the URL /dev and end /save? It stays constant , what is after the save? (name1, name12, value1, costo2, name8) are the parameters that change and references also: name1, point123, pozo12, mbz134 etc, which also change.
Function properly, very good response.
One more question I need to separate a variable point name for which I access the data, for example:
http://123.45.67.89:8090/dev/nombre1/save?name1=12.56&di2=1&an1=5
stored in a variable (nombre_pto =), the value entered: name1
http://123.45.67.89:8090/dev/patag235/save?name1=3.56&name2=2.36&val4=5
stored in a variable (nombre_pto =), the value entered: patag235
http://123.45.67.89:8090/dev/mza341/save?name1=123.56&pos2=23.36
stored in a variable (nombre_pto =), the value entered: mza341
This will need to search the database and filter the information according to the parameters.
From already thank you very much
I could solve.
string url_completa = Request.RequestUri.AbsolutePath;
// Busco el nombre del punto de medicion-----------
int _indexPto = url_completa.IndexOf("/dev/");
url_completa=url_completa.Remove(0, (_indexPto + 5))
The regex in your constraint could be "^[a-zA-Z0-9]+$"
UPDATED according to changes that were done in question description.
To handle such urls you should:
Register wildcard route in WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "dev/{point}/save",
defaults: new { controller = "Values", action = "Save" },
constraints: new { point = #"^[a-zA-Z0-9]+$" }
);
Write your controller to process this route:
public class ValuesController : ApiController
{
[HttpGet]
public string Save(string point)
{
// in point variable you will get part between /dev and /save
// for /dev/point/save?name=125.25 url it will be "point"
// get parameters that follow by ? mark in url
var queryParams = GetQueryParams(Request);
// loop through all of them
foreach (var pair in queryParams)
{
string paramName = pair.Key; // for /dev/point/save?name=125.25 will be "name"
string paramvalue = queryParams[pair.Key]; // for /dev/point/save?name=125.25 will be 125.25
}
return "some value";
}
private Dictionary<string, string> GetQueryParams(HttpRequestMessage request)
{
return request.GetQueryNameValuePairs()
.ToDictionary(kv => kv.Key, kv => kv.Value,
StringComparer.OrdinalIgnoreCase);
}
}
Other urls are also fit this controller and this action.
Hope that finally it is what you were asking for.

ASP.NET Web API Controller Function Executes when entering not mapped action route

I have a controller function which accepts a strongly typed model as parameter.
When i enter ANY url mapping to the controller but not on a specific action on the post request ,
then the controller executes this function instead of returning a 404 code.
When i Change the function's parameter to a primitive type variable then the problem does not occur. (i have tried using other strongly typed models as parameters and again the problem occurs)
Here's the function.
public class PhoneApiController : ApiController
{
[HttpPost]
public HttpResponseMessage RegisterApp(RegisterAppInfo appInfo)
{
var resp = Request.CreateResponse(HttpStatusCode.OK, new
{
Success = true,
AppId = 1000,
IdAlias = "QAUBC9",
appInfo = appInfo
});
return resp;
}
}
So when i enter for example
localhost:51464/Api/PhoneApi/Sajsdkasjdklajsasd
the function executes normally.!
I am using the default Route config
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I don't know if this is a bug or i am doing something wrong.
The URI /Api/PhoneApi/Sajsdkasjdklajsasd does match your route template api/{controller}/{id} with {controller} matching PhoneApi and {id} matching Sajsdkasjdklajsasd. I assume you are making a POST to this URI. So, Web API is mapping your request to the action method RegisterApp with [HttpPost] in the controller class PhoneApiController.
As far as the junk stuff in the URI, it gets mapped to {id}. But your parameter is RegisterAppInfo, which is a complex type and that gets bound from request body and not the URI. That's why it works when you have the complex type. The simple types are bound from URI, query string.
If you have the action method as public HttpResponseMessage RegisterApp(string id, Abc appInfo), you will see that this id parameter gets populated with "Sajsdkasjdklajsasd".
For MVC 4.5 this is the only thing that works
There is currently a bug about this.
Below is a work around in order to get the following route types work
api/{controller}/ //Get All
api/{controller}/{Id} //Get for id
api/{controller}/{Id}/{Action}/ //Get all for action for controller with Id
you need to do the following.
Change your routing over to. (Note the default action..)
config.Routes.MapHttpRoute(
name : "DefaultAPi",
routeTemplate : "api/{controller}/{id}/{action}",
defaults: new
{
id = RouteParameter.Optional,
action = "DefaultAction"
}
);
In your controller change the base methods over to
[ActionName("DefaultAction")]
public string Get()
{
}
[ActionName("DefaultAction")]
public string Get(int id)
{
}
[ActionName("SpaceTypes")]
public string GetSpaceTypes(int id)
{
}
Now everything should work as expected..
Thanks to Kip Streithorst full this, for a full explanation
The way routing works in Web API is:
First it matches the URI against route template. At this stage, it's not looking at your controller actions
Then it looks for a matching controller
Then it looks for a method where (a) the action matches (POST in this case) and (b) every simple parameter type is matched with a value from the URI.
If there is a complex parameter type, it tries to read that from the request body.
By default, Web API tries to bind "simple" parameter types (like int) from the URI, and tries to read complex types from the request body.
See here for details: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection

Web API Nested Resource in URL

I am creating my first ASP.NET web API. I am trying to follow the standard REST URLs. My API would return the search result records. My URL should be –
../api/categories/{categoryId}/subcategories/{subCategoryId}/records?SearchCriteria
I am planning to use oData for searching and Basic / Digest Authentication over IIS. My problem is in the nested resources. Before I return the search results, I need to check whether the user has access to this category and sub category.
Now I created my Visual Studio 2012 – MVC4 / Web API project to start with. In the App_Start folder, there are 2 files that I believe are URL and order of resource related.
1.RouteConfig.cs
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
2.WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
With this model, it works fine if my URL is ../api/records?SearchCriteria but it is not my URL design mentioned above. I understand that I have to do little more reading but so far not able to find the correct article. Need your advice on how to achieve my URL and what changes are needed in these 2 files. Alternatively, are there some other configuration that I am missing here? Thanks in advance.
Asp.net Web API 2 provides Attribute routing out of the box. You can define Route on individual action method or at global level.
E.g:
[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }
You can also set a common prefix for an entire controller by using the [RoutePrefix] attribute:
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET api/books
[Route("")]
public IEnumerable<Book> Get() { ... }
// GET api/books/5
[Route("{id:int}")]
public Book Get(int id) { ... }
}
You can visit this link for more information on Attribute routing in Web API 2.
Assuming you have a controller named categories, Your WebApiConfig.cs could have a route like this to match your desired url (I would personally leave the /records portion off):
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{categoryId}/subcategories/{subCategoryId}",
defaults: new { controller = "categories", categoryId = somedefaultcategory,
subCategoryId = RouteParameter.Optional }
);
and a method could look like this:
// search a single subcategory
public IQueryable<SearchRecord> Get(int categoryId, int subCategoryId = 0, string SearchCriteria = "")
{
// test subCategoryId for non-default value to return records for a single
// subcategory; otherwise, return records for all subcategories
if (subCategoryId != default(int))
{
}
}
But, what if you want to also return just the categories and not subcategories? You'd need an additional route after the first one that is more generic:
config.Routes.MapHttpRoute(
name: "Categories",
routeTemplate: "api/{controller}/{categoryId}",
defaults: new { controller = "categories", categoryId = RouteParameter.Optional }
);
with two methods like:
// search a single category
public IQueryable<SearchRecord> Get(int categoryId, string SearchCriteria = "")
{
}
// search all categories
public IQueryable<SearchRecord> Get(string SearchCriteria = "")
{
}

Duplicate routes need to fallback to next matching route when no Action is found

I have a situation where I need to override methods in an old HttpHandler API in an ASP.NET WebForms application. I'm using WebAPI as the base for the new API and it's been working great except I need the routing to fallback when an action is not found in the new API. This is not true currently, instead I get a "404: Not Found" whenever the WebAPI controller does not have a matching Action.
My routing for the new API is as follows:
var apiroute = routes.MapHttpRoute(
"API Default",
"api/{language}-{country}/{controller}/{action}/{id}",
new { id = RouteParameter.Optional }
);
The old Httphandler APIs register their routes as follows (order is after WebAPI route):
var route = new Route("api/{language}-{country}/Person/{command}", this);
RouteTable.Routes.Add(route);
I want /api/en-US/Person/List to match the first route if theres a PersonController with a List action but to fallback to the old api if there is not.
Question: Is it possible to add a filter to the first route to only match if the Action is indeed available on the controller? How else could I accomplish this?
Update
You may also want to investigate attribute based routing this may be a cleaner solution only the actions you annotate with the routing attributes will be matched to the route and that may help you achieve what you want.
Or
Although there may be better ways than this, it is possible if you implement a custom IRouteConstraint e.g. this article.
A highly rough and ready approach you could improve upon is below:
public class IfActionExistsConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var actionName = values["Action"] as string;
if (actionName == null)
{
return false;
}
var controllerName = values["Controller"] as string;
var controllerTypeResolver = GlobalConfiguration.Configuration.Services.GetHttpControllerTypeResolver();
var controllerTypes = controllerTypeResolver.GetControllerTypes(GlobalConfiguration.Configuration.Services.GetAssembliesResolver());
var controllerType = controllerTypes.SingleOrDefault(ct => ct.Name == string.Format("{0}Controller", controllerName));
if(controllerType == null)
{
return false;
}
return controllerType.GetMethod(actionName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) != null;
}
}
Registration:
var apiroute = routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{language}-{country}/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { IfActionExistsConstraint = new IfActionExistsConstraint() }
);

Resources