WEB API : Single parameter get methods on different controllers - asp.net-mvc

How do I form routes to use the same method on different controllers in ASP.Net Web API?
Here's What I've tried :
var config = new HttpSelfHostConfiguration(strUrl);
config.Routes.MapHttpRoute(
"Scripts", "{controller}/{Name}",
new { controller = "Scripts",strScriptId=""});
config.Routes.MapHttpRoute(
"Images", "{controller}/{strParam}",
new { controller = "Images", strImageId= "" });
Thanks.
Edit: Same method in the sense..for example, a method accepting just one parameter on with a different name on two controllers.
Example :
ScriptsController has GetScripts(string strScriptId) method
ImagesController has GetImages(string strImageId) method.
I need to access it like
1. http://localhost/GetScripts/ScriptId123
2. http://localhost/GetImages/ImageId223
I'm unable to figure out the routes for this. I have tried the above routes (edited them to make it clearer.)
I have many more such controllers with different getsomething methods accepting just one parameter.

You could just have one single route like below:
config.Routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new {id = RouteParameter.Optional});
and you could have your actions like below:
GetImages([FromUri(Name="id")] string strImageId);
GetScripts([FromUri(Name="id")] string strScriptId);

Please refer to this tutorial for routing options.
For Id parameter it's the easiest. Routing should be:
config.Routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and the controller method (it doesn't matter on which controller!):
public MyData Get(int id)
{
...
}
If you really want string for Id, you can just use it as follows:
public MyData Get(string id)
{
...
}
Do you really need a different name for the parameter?
If you do, you can simply try the following routing (I am not sure, though):
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}"
);
Also please note that the routing routes you wrote are conceptually wrong for WebApis:
Instead of
.../GetScripts/ScriptId123
.../GetImages/ImageId223
Consider the following (For Get requests):
.../Scripts/123
.../Images/123

The default route:
routes.MapHttpRoute(
name: "default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Should work for you. Make sure your action methods matches the signature (use id instead of strImageId and strScriptId)
EDIT:
#Josh the parameters in the route SHOULD match the parameters in your actions.
As I said before, there is NO NEED to create alternate routes but if you are required to preserve those names on the ids, you can create a route to accommodate them:
routes.MapHttpRoute(
name: "scriptsRoute",
routeTemplate: "{controller}/{strScriptId}",
defaults: new { controller="Scripts" }
);
routes.MapHttpRoute(
name: "imagesRoute",
routeTemplate: "{controller}/{strImageId}",
defaults: new { controller="Images" }
);
Please NOTE in the routes above the ids are not optional, you can add the constrain if needed.
Also what #liel is recommending in terms of naming your controllers/actions is good advise.

AS We all know REST is resource based and this identify the resource with the URL, so that not more than one method with same parameter will be allowed in the REST service but there is work around in MVC 5 Web Api method level routing.
Here is the example you can do that:
[HttpGet]
[Route("api/search/FindByName/{name}")]
FindByName(string name)
{
}
[HttpGet]
[Route("api/search/FindById/{name}")]
FindById(int searchId)
Note:"search" is the controller name.
Please let know if need more clarification.

Related

Creating Api controller in asp .net mvc

I have created controller called ApiConroller in MVC project. When I tried to run its Index() action method it returns the below error message:
This XML file does not appear to have any style information associated
with it. The document tree is shown below. No HTTP
resource was found that matches the request URI
'http://localhost:60000/api/index'.
Why it is returning xml file? How to make it return a view?
/api/ is part of the default mapping for the Web Api routing, as shown here:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
If you're not using Web Api, make sure you remove those route maps by removing the WebApiConfig file from your project and/or removing WebApiConfig.Register(GlobalConfiguration.Configuration) from the Global.asax file.
If you are using Web Api somewhere, you'll need to change the routing noted above.

MVC Controller not being reached in Web API Project

I have a Web Api project which needs to return an MVC styled View. I have made my MVC Controller as such
public class MVCController: Controller
{
[HttpGet]
[Route("api/mvc/test")]
public ActionResult test()
{
return View();
}
}
However, When i try to access this controller from the web, i cant seem to reach the controller. I get the following error:
{"Message":"No HTTP resource was found that matches the request URI 'http://localhost/foo/api/mvc/test'.","MessageDetail":"No type was found that matches the controller named 'mvc'."}
After doing searches on google, people seem to be telling me to change routing properties in the webapiconfig to
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
But i still seem to be having no luck. IF HOWEVER i change my controller to an webapi controller as such
public class MVCController: ApiController
{
[HttpGet]
[Route("api/mvc/test")]
public IHttpActionResult test()
{
return Ok();
}
}
I can reach the controller.. If someone could give me some insight to as what is going on, it would be much appreciated.
UPDATE
After reading the response below, I have updated my controller to look like this:
public class MVCController: Controller
{
[HttpGet]
public ActionResult test()
{
return View();
}
}
However, localhost/MVCController/test still seems to give me a 404 error and the Controller is not being hit. Sorry for my newbieness by the way.
I bet you have a file called WebApiConfig.cs which has this code.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
This is where the routing pattern is defined for the web api controllers. So any request coming with api/something will be considered as a request for web api endpoint because you must have invoked the WebApiConfig.Register call before registering the routes for mvc controllers. The Order of registering routes really matters.
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register); // first one
RouteConfig.RegisterRoutes(RouteTable.Routes); //second one
}
Since the order of registration matters, when a request comes with api/something, it will be matched against the route registration for web apis and framework will try to look for matching web api controller.s
If you swap the order of calling the route registration method, your request will work. but that might affect other parts when you try to access web api controllers.
BTW, Are you sure you want to have api/ in the route pattern for your MVC controller ? Unless you really want some different url patten than the normal convention(*controllername/action*), just remove the Route attribute it self. With the default route definition, it will work for yourSite/mvc/test request.

No type was found that matches the controller named 'User'

I'm trying to navigate to a page which its URL is in the following format:
localhost:xxxxx/User/{id}/VerifyEmail?secretKey=xxxxxxxxxxxxxxx
I've added a new route in the RouteConfig.cs file and so my RouteConfig.cs looks like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "VerifyEmail",
url: "User/{id}/VerifyEmail",
defaults: new { controller = "User", action = "VerifyEmail" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
}
Unfortunately, when trying to navigate to that URL I get this page:
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost:52684/User/f2acc4d0-2e03-4d72-99b6-9b9b85bd661a/VerifyEmail?secretKey=e9bf3924-681c-4afc-a8b0-3fd58eba93fe'.
</Message>
<MessageDetail>
No type was found that matches the controller named 'User'.
</MessageDetail>
</Error>
and here is my UserController:
public class UserController : Controller
{
// GET /User/{id}/VerifyEmail
[HttpGet]
public ActionResult VerifyEmail(string id, string secretKey)
{
try
{
User user = UsersBL.Instance.Verify(id, secretKey);
//logger.Debug(String.Format("User %s just signed-in in by email.",
user.DebugDescription()));
}
catch (Exception e)
{
throw new Exception("Failed", e);
}
return View();
}
}
Please tell me what am I doing wrong?
In my case, the controller was defined as:
public class DocumentAPI : ApiController
{
}
Changing it to the following worked!
public class DocumentAPIController : ApiController
{
}
The class name has to end with Controller!
Edit: As #Corey Alix has suggested, please make sure that the controller has a public access modifier; non-public controllers are ignored by the route handler!
In my case after spending almost 30 minutes trying to fix the problem, I found what was causing it:
My route defined in WebApiConfig.cs was like this:
config.Routes.MapHttpRoute(
name: "ControllersApi",
routeTemplate: "{controller}/{action}"
);
and it should be like this:
config.Routes.MapHttpRoute(
name: "ControllersApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
as you see it was interfering with the standard route defined in RouteConfig.cs.
In my case I was using Web API and I did not have the public defined for my controller class.
Things to check for Web API:
Controller Class is declares as public
Controller Class implements ApiController : ApiController
Controller Class name needs to end in Controller
Check that your url has the /api/ prefix. eg. 'host:port/api/{controller}/{actionMethod}'
Another solution could be to set the controllers class permission to public.
set this:
class DocumentAPIController : ApiController
{
}
to:
public class DocumentAPIController : ApiController
{
}
In my case I wanted to create a Web API controller, but, because of inattention, my controller was inherited from Controller instead of ApiController.
In my case, the routing was defined as:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{*catchall}",
defaults: new { controller = "WarehouseController" }
while Controller needs to be dropped in the config:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{*catchall}",
defaults: new { controller = "Warehouse" }
In my case I was seeing this because I had two controllers with the same name:
One for handling Customer orders called CustomersController and the other for getting events also called CustomersController
I had missed the duplication, I renamed the events one to CustomerEventsController and it worked perfectly
In my case it was a case of over-aggressive caching by the WebHostHttpControllerTypeResolver.
Fix:
Delete all files (or in my case just any files named "MS-ApiControllerTypeCache.xml") under this path:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root
Restart the app pool
credit: https://sitecore.stackexchange.com/questions/9897/webapi-controllers-not-being-found-in-sitecore-8-2
Faced the same problem. Checked all the answers here but my problem was in namespacing.
Routing attributes exists in System.Web.Mvc and in System.Web.Http. My usings included Mvc namespace and it was the reason. For webapi u need to use System.Net.Http.
Experienced this similar issue. We are dealing with multiple APIs and we were hitting the wrong port number and getting this error. Took us forever to realize. Make sure the port of the api you are hitting is the correct port.
I have also faced the same problem. I searched a lot and found that the class level permission is needed. by default, the class permission level is internal so I thought that it won't affect the program execution. But it got affected actually, you should give your class permission as public so that, you won't face any problem.
And one more. if it is webapi project, your webapirouteconfig file will overwrite the routeconfig.cs file settings. So update the webapi routeconfig file as well to work properly.
In my solution, I have a project called "P420" and into other project I had a P420Controller.
When .NET cut controller name to find route, conflict with other project, used as a library into.
Hope it helps.
In my case I was calling the APi like
http://locahost:56159/api/loginDataController/GetLoginData
while it should be like
http://locahost:56159/api/loginData/GetLoginData
removed Controller from URL and it started working ...
Peace!
In my solution, when I added the my new Controller to the project, the wizard asked me if I want to set the location of the controller into the App_Code folder.
The wizard warned me, if I do not locate it into the the App_Code folder, the controller type won't be found.
But I didn't read the whole warning, because I wanted to locate the file to elsewhere..
so that's why it didn't work for me.
After I added a new controller and let it to be in the App_Code by default, everything worked.
And one more answer to this for good measure...
In my case another project had been accidentally added as a reference by someone which brought in all of that project's controllers and causing route conflicts. Removing it and moving the code that was needed from it to a better place where it could be referenced without bringing in all of the controllers was the solution.
Go to the location
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root and find withe the file name "MS-ApiControllerTypeCache.xml"
and
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root
Delete all files named MS-ApiControllerTypeCache.xml from the root folder.
For my case, the issue ought to be addressed this way

Controller action "Index" not invoked with default setting

I have a ASP.NET web application project, I implemented a WebApi controller and now proceeding with a MVC controller.
I've added a new MVC controller (eg. "Test") and selected to add it with "empty read and write actions".
The controller is created with various CRUD methods, however the Index action is behaving unexpectedly.
By default the Index action does not take any arguments:
public ActionResult Index()
{
return View();
}
Now if I try to invoke this method using url "/Test/Index", the method is not being called.
But if I type url "/Test/Index/1" the Index action is run.
It looks to me that its related to routing so I checked the RouteConfig class which looks like:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Id is set to optional and my understanding is I should not be required to provide a parameter value in Index method?
I created another project and checked this behavior and it works fine.
So wondering what in this project making MVC controller behaving differently?
Is mixing WebApi controllers with MVC controllers having some side effect (making it a special case)?
Let me know if you need any information to answer this question.
Update:
I did modify the WebApiConfig for making API controller work as per my requirement, it looks like this:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{action}",
defaults: new { controller = "iclock", action = "cdata" }
);
}
I could not understand why it affect the MVC controller route? I got it now with answers provided.
Update 2:
The problem in this case is I can not (or can I?) prefix api in web api route as a third party device is sending GET and POST request using a fixed url, like following:
GET /iclock/cdata?SN=xxxxxx&options=all&pushver=2.0.2&language=XX
GET /iclock/cdata?SN=xxxxxx&type=time
POST /iclock/cdata?SN=123456&table=ATTLOG&Stamp=2011-04-05T17:40:20
I achieved above request handling functionality making changes into the WebApiConfig route. Now need a front end UI for which I am developing a MVC controller with CRUD operation.
What should be ideal approach in this case?
It could be when you navigate to Test/Index it doesn't know if it should use the web route where there is an optional "id" or the api route where there is no "id". If possible I would prefix your Web API routes, such as "api/{controller}/{action}" as described in the MVC Web API template.
For your second example, applications that access the API would simple prefix the URLs with "api/". If they are using the URLs you specified and cannot be changed, then I think you may be stuck because when the request comes in there is still the problem of not knowing which route to use.
If the URLs can be changed, as mentioned above, simple prefix with "api/".
EDIT: Updated my answer based on the extra information/code-snippet provided.
EDIT: Updated again
Did you look in WebApiConfig instead of RouteConfig in App_Start
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
You have a conflict in your routing table. Your Web API routing table is now the same as your standard MVC routing table. Your Web API routing table (the one that is initialized with MapHttpRoute should be declared as: routeTemplate: "api/{controller}/{id}". Also, you shouldn't have modified the Web API route. It is not cast in stone that it must be api/{controller}/{id}, but there is a certain convention that you should follow. Try reseting your routes to be like the following and then try again:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
So, first reset to normal routing configuration and test. After that make changes slowly and follow the result.

Nested resources in ASP.net MVC 4 WebApi

Is there a better way in the new ASP.net MVC 4 WebApi to handle nested resources than setting up a special route for each one? (similar to here: ASP.Net MVC support for Nested Resources? - this was posted in 2009).
For example I want to handle:
/customers/1/products/10/
I have seen some examples of ApiController actions named other than Get(), Post() etc, for example here I see an example of an action called GetOrder(). I can't find any documentation on this though. Is this a way to achieve this?
Sorry, I have updated this one multiple times as I am myself finding a solution.
Seems there is many ways to tackle this one, but the most efficient I have found so far is:
Add this under default route:
routes.MapHttpRoute(
name: "OneLevelNested",
routeTemplate: "api/{controller}/{customerId}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
This route will then match any controller action and the matching segment name in the URL. For example:
/api/customers/1/orders will match:
public IEnumerable<Order> Orders(int customerId)
/api/customers/1/orders/123 will match:
public Order Orders(int customerId, int id)
/api/customers/1/products will match:
public IEnumerable<Product> Products(int customerId)
/api/customers/1/products/123 will match:
public Product Products(int customerId, int id)
The method name must match the {action} segment specified in the route.
Important Note:
From comments
Since the RC you'll need to tell each action which kind of verbs that are acceptable, ie [HttpGet], etc.
EDIT: Although this answer still applies for Web API 1, for Web API 2 I strongly advise using Daniel Halan's answer as it is the state of the art for mapping subresources (among other niceties).
Some people don't like to use {action} in Web API because they believe that in doing so they will be breaking the REST "ideology"... I contend that. {action} is merely a construct that helps in routing. It is internal to your implementation and has nothing to do with the HTTP verb used to access a resource.
If you put HTTP verb constraints on the actions and name them accordingly you're not breaking any RESTful guidelines and will end up with simpler, more concise controllers instead of tons of individual controllers for each sub-resource. Remember: the action is just a routing mechanism, and it is internal to your implementation. If you struggle against the framework, then something is amiss either with the framework or your implementation. Just map the route with an HTTPMETHOD constraint and you're good to go:
routes.MapHttpRoute(
name: "OneLevelNested",
routeTemplate: "api/customers/{customerId}/orders/{orderId}",
constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "GET" }) },
defaults: new { controller = "Customers", action = "GetOrders", orderId = RouteParameter.Optional, }
);
You can handle these in the CustomersController like this:
public class CustomersController
{
// ...
public IEnumerable<Order> GetOrders(long customerId)
{
// returns all orders for customerId!
}
public Order GetOrders(long customerId, long orderId)
{
// return the single order identified by orderId for the customerId supplied
}
// ...
}
You can also route a Create action on the same "resource" (orders):
routes.MapHttpRoute(
name: "OneLevelNested",
routeTemplate: "api/customers/{customerId}/orders",
constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "POST" }) },
defaults: new { controller = "Customers", action = "CreateOrder", }
);
And handle it accordingly in the Customer controller:
public class CustomersController
{
// ...
public Order CreateOrder(long customerId)
{
// create and return the order just created (with the new order id)
}
// ...
}
Yes, you still have to create a lot of routes just because Web API still can't route to different methods depending on the path... But I think it is cleaner to declaratively define the routes than to come up with a custom dispatching mechanisms based on enums or other tricks.
For the consumer of your API it will look perfectly RESTful:
GET http://your.api/customers/1/orders (maps to GetOrders(long) returning all orders for customer 1)
GET http://your.api/customers/1/orders/22 (maps to GetOrders(long, long) returning the order 22 for customer 1
POST http://your.api/customers/1/orders (maps to CreateOrder(long) which will create an order and return it to the caller (with the new ID just created)
But don't take my word as an absolute truth. I'm still experimenting with it and I think MS failed to address properly subresource access.
I urge you to try out http://www.servicestack.net/ for a less painful experience writing REST apis... But don't get me wrong, I adore Web API and use it for most of my professional projects, mainly because it is easier to find programmers out there that already "know" it... For my personal projects I prefer ServiceStack.
Since Web API 2 you can use Route Attributes to define custom routing per Method, allowing for hierarchical routing
public class CustomersController : ApiController
{
[Route("api/customers/{id:guid}/products")]
public IEnumerable<Product> GetCustomerProducts(Guid id) {
return new Product[0];
}
}
You also need to initialize Attribute Mapping in WebApiConfig.Register(),
config.MapHttpAttributeRoutes();
I don't like using the concept of "actions" in the route of an ASP.NET Web API. The action in REST is supposed to be the HTTP Verb. I implemented my solution in a somewhat generic and somewhat elegant way by simply using the concept of a parent controller.
https://stackoverflow.com/a/15341810/326110
Below is that answer reproduced in full because I'm not sure what to do when one post answers two SO questions :(
I wanted to handle this in a more general way, instead of wiring up a ChildController directly with controller = "Child", as Abhijit Kadam did. I have several child controllers and didn't want to have to map a specific route for each one, with controller = "ChildX" and controller = "ChildY" over and over.
My WebApiConfig looks like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ChildApi",
routeTemplate: "api/{parentController}/{parentId}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
My parent controllers are very standard, and match the default route above. A sample child controller looks like this:
public class CommentController : ApiController
{
// GET api/product/5/comment
public string Get(ParentController parentController, string parentId)
{
return "This is the comment controller with parent of "
+ parentId + ", which is a " + parentController.ToString();
}
// GET api/product/5/comment/122
public string Get(ParentController parentController, string parentId,
string id)
{
return "You are looking for comment " + id + " under parent "
+ parentId + ", which is a "
+ parentController.ToString();
}
}
public enum ParentController
{
Product
}
Some drawbacks of my implementation
As you can see, I used an enum, so I'm still having to manage parent controllers in two separate places. It could have just as easily been a string parameter, but I wanted to prevent api/crazy-non-existent-parent/5/comment/122 from working.
There's probably a way to use reflection or something to do this on the fly without managing it separetly, but this works for me for now.
It doesn't support children of children.
There's probably a better solution that's even more general, but like I said, this works for me.

Resources