I am new to WebAPI.
I have a MVC project and webApi project both reside under the **same solution**. Also, BLL Class library and DAL class library reside under the same solution.
Earlier, my MVC project will talk to the BLL now I am trying to create a WebAPi project which stands in between MVC and BLL.
Here is what I have come up with so far :
I'm using the HTTPClient to post to a WebApi project. My post method on my controller accepts a single parameter (a model).
StatsCriteria criteria = new StatsCriteria();
......
var client = new HttpClient();
var response = client.PostAsJsonAsync("http://localhost:52765/api/reports", criteria).Result;
.......
Here's the signature for my controller in Webapi
[HttpPost]
public CMAReportVM Reports([FromBody] StatsCriteria criteria)
{
var cmaReport = Service3.GetCMAReport(criteria.Mlsnums);
//Create Map to enable mapping business object to View Model
Mapper.CreateMap<CMAReport, CMAReportVM>();
// Maps model to VM model class
var cmaVM = Mapper.Map<CMAReport, CMAReportVM>(cmaReport);
reutn cmaVM;
}
// and here's my routing:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
I am getting the following
405 : Method not allowed.
As the WebAPI and MVC project both reside under same sloution I am not sure where/how to host my webapi.
Related
I've created both an ASP.NET MVC project which includes the Web API and I created an empty project where only Web API was selected and I'm trying to understand why they are behaving differently.
Both of them from what I can tell have the routing definition defined as follows:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
But I'd like to understand why the functions that are defined in the controller are behaving differently.
In my ASP.NET MVC Solution, I can create multiple functions under the one controller:
public IEnumerable<Product> GetAllProducts()
{
return products;
}
public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
I don't have to add any attributes, well at least for gets and I can call individual function as:
http://localhost/api/test/GetProduct/1
http://localhost/api/test/GetProducts
In the Web API only solution, I had to define it as:
public IHttpActionResult HelloWorld()
{
return Ok("Hello World");
}
but the above will not work unless I add the [HttpGet] attribute to my HelloWorld function.
Also I don't seem to be able to define multiple functions within the one controller as it is done the in ASP.NET MVC Solution. Defining the same function as above just does not work or even if I define the functions below
[HttpGet]
public IHttpActionResult HelloWorld()
{
return Ok("Hello World");
}
[HttpGet]
public IHttpActionResult GetTime()
{
return Ok(DateTime.Now.ToString());
}
I get the following error:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>
Multiple actions were found that match the request: HelloWorld on type
MyProject.Controllers.TestController GetTime on type
MyProject.Controllers.TestController
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace>
at
System.Web.Http.Controllers.ApiControllerActionSelector.
ActionSelectorCacheItem.SelectAction(HttpControllerContext
controllerContext) at System.Web.Http.ApiController.
ExecuteAsync(HttpControllerContext controllerContext, CancellationToken
cancellationToken) at System.Web.Http.Dispatcher.
HttpControllerDispatcher.<SendAsync>d__15.MoveNext()
</StackTrace>
When I define a single function, it works but contrarily to a Web API defined in my ASP.NET MVC solution, when I call the below
http://localhost/api/test/HelloWorld
It doesn't seem to care what the function name is called as it will work if I call any of the following:
http://localhost/api/test
http://localhost/api/test/GoodBye
and will only fail if I provide an invalid controller name i.e.
http://localhost/api/mytest
Can you explain the difference between the 2 and ideally what do I have to do to get a stand-alone Web API to behave the same way as it does when defined in Asp.net MVC
Thanks.
UPDATE-1
Below are 2 good examples where the behavior is clearly different but I can't see why as both are called "Web API with ASP.NET MVC".
Creating Simple Service with ASP.NET MVC Web API to be accessed by Windows Store and Windows Phone Application
Getting Started with ASP.NET Web API 2 (C#)
Edit:
I'm sry at first I misunderstood your enquiry and therefore gave you the wrong answer to your question.
In Web Api you can't define two functions that serve the exact same path. But you can overload them like you would with Functions.
public IEnumerable<Product> GetAllProducts() {}
public IHttpActionResult GetProduct(int id) {}
These two function differ in so far that they serve different paths:
localhost/api/products/
localhost/api/products/{id}/
The following functions on the other hand both serve localhost/api/test/
[HttpGet]
public IHttpActionResult HelloWorld()
{
return Ok("Hello World");
}
[HttpGet]
public IHttpActionResult GetTime()
{
return Ok(DateTime.Now.ToString());
}
Your second concern has been the naming and mostly why you can't define HelloWorld() without [HttpGet]. That is because Web Api automatically maps functions that have a keyword such as Get or Delete as part of their name to the corresponding Http Function.
Adding the Http-Attribute is only needed if the function name does not conform to these naming conventions or shall serve multiple Http-Methods. It's described here
I hope your questions are now answered.
Orig:
The Default Routing and subsequent decision on what Function is to be executed is different between MVC and Web API
Default Web Api:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Default Mvc:
config.Routes.MapHttpRoute(
name: "DefaultMvc",
routeTemplate: "/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
For Web Api the Routing is dependant on the controller name and further which Http-Method has been selected as well as the controller actually having a function the has been tagged with this method. Hence the and further Tags
Mvc on the other hand routes via the controller name and the action name to the desired functionality.
Web Api 2 adds further possibilies in letting you define AttributeRoutes. You may want to look further on this topic here and here
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.
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.
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.
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.