ASP.NET Web API routes not mapping to controller actions - c#

I have an ASP.NET Web API application where I want to respond to the following routes:
1. http://localhost:1234/values
2. http://localhost:1234/values/123
3. http://localhost:1234/values/large
4. http://localhost:1234/values/small
Note: These routes and examples are just that, examples. But they map to what I want achieve.
Should return all values, say a list of numbers.
Should return the value of the resouce with id 123.
Should return a list of what the app considers large values. Whatever that may be.
Should return a list of what the app consider small values.
As with a billion ASP.NET Web Api routing examples, number (1) and (2) are straightforward. But when I try and resolve (3) and (4), then (1) and (2) are no longer working.
This is the routes I have at the moment:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// trying to map to "values/large" or "values/small"
routes.MapHttpRoute(
name: "ActionsApi",
routeTemplate: "{controller}/{action}",
defaults: null,
constraints: new { action = #"^[a-zA-Z]+$" }
);
// trying to map to "values/123"
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" }
);
// trying to map to "values"
routes.MapHttpRoute(
name: "ControllerApi",
routeTemplate: "{controller}"
);
With the above routes, (3) and (4) work.
(2) returns:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
No action was found on the controller 'Values' that matches the name '123'.
</string>
And (1) returns:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
Multiple actions were found that match the request:
System.Collections.Generic.IEnumerable`1[System.String] Get() on type MvcApplication3.Controllers.ValuesController
System.Collections.Generic.IEnumerable`1[System.String] Large() on type MvcApplication3.Controllers.ValuesController
System.Collections.Generic.IEnumerable`1[System.String] Small() on type MvcApplication3.Controllers.ValuesController
</string>
I am at a loss as to how to setup the routes to support the 4 API examples I listed above.
EDIT: Thanks to David, he pointed out that \w also matched digits, hence a problem. I changed it to [a-zA-Z]+ to match large and small.
Now all work except for (1).
EDIT 2 As suggested by #andrei I made the id parameter optional, to try and get (1) working, but resulted in the following error for that route:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
The optional default I added here:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new { id = #"^\d+$" }
);

Have you considered leaving the routing as default, and dealing with the "values" in your controller?
e.g.
Change your Get to make id of type string, then check for "special" values inside your controller.
public class ValuesController : ApiController
{
// GET api/values/5
public string Get(string id)
{
if (id == "large")
{
return "large value";
}
if (id == "small")
{
return "small value";
}
return "value " + id;
}
}

Related

Force questionmark to show in querystring where only ID param is passed

I've implemented code to encrypt my query string parameter names and values. The code i have implemented will only encrypt query string that contain ?. (This is to prevent encryption of unneeded URL's, such as the .css files).
A way to combat this would be to always show the ? in query strings when only the ID parameter is passed.
For example I would like: http://domain/controller/Action/17
To show as: http://domain/controller/Action/?id=17
I understand that I probably need to edit my routes, I've tried adding the ? symbol to the route which throws the error : The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.
My routes are defined as:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Login", id = UrlParameter.Optional } // Parameter defaults
);
How can I get my query strings to show like the example given above?
Don't define your parameters in routes.
ASP.NET automaticaly will add the question mark.
You can then call http://domain/controller/Action?id=17 and it will route to
public ActionResult Action(int id) { }
Update: If you want to kill domain/controller/action/id format completely, you need to define the route as:
routes.MapRoute(
name: "Parameterless", //or any name
url: "YourController",
defaults: new { controller = "YourController", action = "YourAction" }
);
Now you can use domain/controller/action?id={id} and domain/controller/action/id will 404.
If you are getting a Server Application Error, you need to provide more details, since it might be related to something else.

web api route with optional parameter

I am currently playing around with some things...According to this link, I need to construct a route that is open to the following format
webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier?passesUpdatedSince=tag
so I defined the route like so
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "{version}/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{passesUpdatedSince}",
defaults: new { controller = "SerialNumbers", action = "GET", passesUpdatedSince = RouteParameter.Optional }
);
However, the following route fails for the url
http://localhost/v1/devices/24358235235loji200/registrations/pass.com.mypass?passesUpdatedSince=12a512
How can I configure the route so that the above url can reach my controller?
My controller looks like
[HttpGet]
public HttpResponseMessage Get(string passesUpdatedSince ="")
{
//do stuff
}
UPDATE
Thanks to the comments, I've made the following changes.
the route
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}",
defaults: new { controller = "SerialNumbers", action = "GET" }
);
My controller is as follows
public HttpResponseMessage Get(string deviceLibraryIdentifier,
string passTypeIdentifier,
string passesUpdatedSince = "")
{
//do stuff
}
According to the Apple docs, is it right to assume the following the webservice calls could look like
http://localhost:31472/v1/devices/23lk5235232oijlk/registrations/pass.com.mypass
http://localhost:31472/v1/devices/23lk5235232oijlk/registrations/pass.com.mypass?passesUpdatedSince=159025
as these are returning 404.
These, however, do work.
http://localhost:31472/v1/devices/23lk5235232oijlk/registrations/pass.com.mypass/?passesUpdatedSince=1415l
http://localhost:31472/v1/devices/23lk5235232oijlk/registrations/pass.com.mypass/
So would there be a way to get it to work without the presence of the / near the end of the url?
It does look like the device is unable to recognize the route. I get the following message
Get serial #s task (for device 2523ff2fswtsfdh6544, pass type pass.com.mypass, last updated (null); with web service url https://weburl) encountered error: Unexpected response code 404
Because part of the URI had periods in it (pass.com.mypass), this always returned a 404
I had to add the
<modules runAllManagedModulesForAllRequests="true" />
in my web.config. And after that, everything worked as expected
For the route, try:
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "{version}/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}",
defaults: new { controller = "SerialNumbers", action = "GET" }
);
Note that you should actually have a hard-coded value where {version} is, according to the link you gave us (https://developer.apple.com/library/ios/documentation/PassKit/Reference/PassKit_WebService/WebService.html#//apple_ref/doc/uid/TP40011988-CH0-SW4).
A hard-coded version would look like this:
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}",
defaults: new { controller = "SerialNumbers", action = "GET" }
);
Your controller action also needs to be able to accept all parameters of the route:
[HttpGet]
public HttpResponseMessage Get(string deviceLibraryIdentifier,
string passTypeIdentifier,
string passesUpdatedSince ="")
{
//do stuff
}

Controller is taken from Url although no controller is requested in URL - c# .Net MVC 4

I'm currently working at a .NET 4.5 MVC 4 Web Application.
I have got the following Routes:
routes.MapRoute(
name: "Default",
url: "api/",
defaults: new { controller = "Response", action = "ReturnAllStations" }
);
routes.MapRoute(
name: "ID",
url: "api/{id}",
defaults: new { controller = "Response", action = "ReturnStuffA", id = UrlParameter.Optional }
);
Now when I enter the URL http://localhost:55302/api/ it all works fine. But when I enter an URL like this: http://localhost:55302/api/SampleId1234 I get the following error "No type was found that matches the controller named 'Sample1234'."
Why does it try to get a controller named Sample1234 and not the defautlt one and use sample1234 as parameter?
Your default route should come last. Route config will look for the configuration from top to bottom and when it finds a match immediately returns invokes that action.
In your case always invoke the first configuration because it matches the api/ configuration.

WebAPI accepts broken JSON send via Fiddler

For some reason I can send these two JSON requests to my WebAPI and it'll accept and create an User. One with double quotes and one without.
{"Username":"Bob", "FirstName":"Foo", "LastName":"Bar", "Password":"123", "Headline":"Tuna"}
{Username:"Bob", FirstName:"Foo", LastName:"Bar", Password:"123", Headline:"Tuna"}
This is the method which creates an User.
// localhost:12345/api/controller/create
[ActionName("create")]
public HttpResponseMessage PostUser(User user)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, user);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = user.UserId }));
return response;
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
However I'm able to make a successful POST request without the /create at the end of URL. I'm assuming that the reason for the success POST request is because whether or not the action name is present it will search for a Post in the method name.
So my question is, what's the purpose of the action name then? How can I make it so it's a must in the URL? Also why is it accepting both JSON requests and how can I make it accept one or the other.
EDIT:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Routing by Action-name
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{username}",
defaults: new { username = RouteParameter.Optional }
);
Thank you for your time
/twice
How can I make it so it's a must in the URL?
By changing your route definition in ~/App_Start/WebApiConfig.cs and making the action name explicitly appear in your route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Also why is it accepting both JSON requests and how can I make it accept one or the other.
Because the Web API uses JSON.NET as JSON serializer which accepts both of them.
If you look in App_Start for the route that's registered for your API calls you'll see:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
You'll notice that no action is specified. If you want to map to action names, you can change it to:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
But then you'll always be required to set an action (unless it's a GET which is the default). You can no longer POST directly toe controller, you'll always need to add an action.
Per this link the default behavior is to look for a method that starts with the HTTP method used:
To find the action, Web API looks at the HTTP method, and then looks
for an action whose name begins with that HTTP method name. For
example, with a GET request, Web API looks for an action that starts
with "Get...", such as "GetContact" or "GetAllContacts". This
convention applies only to GET, POST, PUT, and DELETE methods. You can
enable other HTTP methods by using attributes on your controller.
We’ll see an example of that later.
Another option is to create different controllers for your different GET calls, which is what I've done in the past.

How do I get ASP.NET Web API working with versioning and the Help Page extension?

I've implemented a versioning framework into my WebAPI application, and would very much like to get it working with the new Help Page extension from Microsoft.
Microsoft.AspNet.WebApi.HelpPage
SDammann.WebApi.Versioning
Quite simply, I don't know how to get them both working together. I have 2 projects:
AdventureWorks.Api (The main host/root application)
AdventureWorks.Api.v1 (A class library containing the first version of the API)
The versioning works as expected.
I've tried installing the HelpPage package on the root application, and when I browse to the help page, it appears none of the controllers are being found. Internally I believe it uses:
Configuration.Services.GetApiExplorer().ApiDescriptions
This returns no results, so I get an error.
Can anyone assist me in getting both of these packages working together?
Edit:
In the beginning, I wasn't sure this was a routing problem, but recent comments seem to suggest otherwise. Here is my RouteConfig.cs
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "SldExportAliasApi",
routeTemplate: "api/v{version}/sld-export/{id}",
defaults: new { id = RouteParameter.Optional, controller = "Export" }
);
routes.MapHttpRoute(
name: "LinkRoute",
routeTemplate: "api/v{version}/link/{system}/{deployment}/{view}",
defaults: new { controller = "Link" }
);
routes.MapHttpRoute(
name: "DefaultSubParameterApi",
routeTemplate: "api/v{version}/{controller}/{id}/{param}",
defaults: new { id = RouteParameter.Optional, param = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v{version}/{controller}/{action}/{id}",
defaults: new { action = "Index", id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
You need to get a documentation XML file from your project AdventureWorks.Api.v1 project and place it in the bin folder of the AdventureWorks.Api project:
Then add these lines to your Application_Start method:
// enable API versioning
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new RouteVersionControllerSelector(GlobalConfiguration.Configuration));
GlobalConfiguration.Configuration.Services.Replace(typeof(IApiExplorer), new VersionedApiExplorer(GlobalConfiguration.Configuration));
GlobalConfiguration.Configuration.Services.Replace(typeof(IDocumentationProvider),
new XmlCommentDocumentationProvider(System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) +
"\\Adventure.Api.v1.XML"));
Then you can view your API with the documentation.
Sometimes the version number does not get to be picked up correctly, and replaced by ???
To fix this add:
if (api.ActionDescriptor.ControllerDescriptor.ControllerType.Namespace != null)
{
var versionName = api.ActionDescriptor.ControllerDescriptor.ControllerType.Namespace.Replace(".Controllers", "").Split('.').Last();
api.RelativePath = api.RelativePath.Replace("v???", versionName);
}
to your ApiGroup.cshtml exactly at this place:
#foreach (var api in Model)
{
if (api.ActionDescriptor.ControllerDescriptor.ControllerType.Namespace != null)
{
var versionName = api.ActionDescriptor.ControllerDescriptor.ControllerType.Namespace.Replace(".Controllers", "").Split('.').Last();
api.RelativePath = api.RelativePath.Replace("v???", versionName);
}
<tr>
<td class="api-name">#api.HttpMethod.Method #api.RelativePath</td>
<td class="api-documentation">
#if (api.Documentation != null)
{
<p>#api.Documentation</p>
}
else
{
<p>No documentation available.</p>
}
</td>
</tr>
}
This should do the trick!
I couldn't figure out how to comment on a post :( I think this should probably be a comment under the marked answer for this question but SDamman is updated and all I needed to do was this
// enable API versioning
GlobalConfiguration.Configuration.Services.Replace(typeof(System.Web.Http.Dispatcher.IHttpControllerSelector),
new SDammann.WebApi.Versioning.RouteVersionedControllerSelector(GlobalConfiguration.Configuration));
GlobalConfiguration.Configuration.Services.Replace(typeof(IApiExplorer), new SDammann.WebApi.Versioning.VersionedApiExplorer(GlobalConfiguration.Configuration));
There is a type called VersionedApiExplorer and it works great. Hope this helps the solution is much easier now.
EDIT:
I realized after trying to get help working again myself that my answer wasn't obvious at all.
The ONLY thing you need to do to get help pages working is replace the global configs IApiExplorer, that's it. Just do it right after you change the handler per sdammans instructions.
I agree with #mortware, the default routing for web api would mean your url should look something like "site/api/controllerName/" if you're using default Get()/Post() methods. If you're using specificly named methods then the route looks something like "site/api/controllerName/methodName".
I've also run into difficulty with the parameter names. Eg, if in your route specified in /App_Start/WebApiConfig.cs you have;
// Controller with ID
// To handle routes like `/api/VTRouting/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
// Controllers with Actions
// To handle routes like `/api/VTRouting/route`
config.Routes.MapHttpRoute(
name: "ControllerAndAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
then the method parameter for your http verb must have a parameter called "id", eg;
// url: site/api/controller/<int>
public HttpResponseMessage Get(int id) { return null; /*dummy*/ }
// url: site/api/controller/<int>
public HttpResponseMessage Post(int id) { return null; /*dummy*/ }
// url: site/api/controller/SomeAction/<int>
public HttpResponseMessage SomeAction(int id) { return null; /*dummy*/ }
If you have something like;
public HttpResponseMessage Get(int myID) { return null; /*dummy*/ }
It will not work as the "myID" parameter doesn't match that {id} specified in the route. As #OakNinja pointed out, we'll need your routing in the WebApiConfig.cs to help you pinpoint the exact cause

Categories