I'm trying to create a method that takes two strings and gets called if I provide the params or not. It's proving to be difficult for me.
Here is my controller with the test method:
public class TestController : ApiController
{
[HttpGet]
public string Get(string one, string two )
{
return "Testing";
}
}
I would like the method to be available at the folloring urls:
url/test/
url/test/
url/test/?one=1&two=2
url/test/?one=1
url/test/?two=2
url/test/get
url/test/get?one=1&two=2
url/test/get?one=1
url/test/get?two=2
I'm also planning to add POST support, but I think this will prove to much for WebAPI and I'll have to have a dedicated model class.
My routes are:
.Routes.MapHttpRoute("Basic", "{controller}/{action}",
defaults: new { action = "Get" });
I understand that I could set up routes to specify the 'one' and 'two' parameters, but my routes definitions sit in a central place and I'm not ready to pollute my global routes to cater for a special case in a controller (and then do it for hundred other methods).
It seems to me like a pretty basic scenario - what is the pattern to follow here?
You need to set default values on the parameters to make them optional:
[HttpGet]
public string Get(string one = null, string two = null )
{
return "Testing";
}
Related
I'm using this codes to Attribute Routing .
my controller :
[RouteArea("Administrator")]
[Route("{action}")]
public partial class HomeController : Controller
{
[HttpGet]
[Route("~/Home/Template/{id}")]
public virtual ActionResult Template(string template)
{
switch (template.ToLower())// error :Object reference not set to an instance of an object..
{
case "main":
return PartialView(Url.Content(MVC.Administrator.Home.Views.Main));
default:
throw new Exception("template not known");
}
}
}
RouteConfig :
internal static class RouteConfig
{
internal static void RegisterRoutes(AreaRegistrationContext context)
{
context.MapRoute(
"Administrator_default",
"Administrator/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { string.Format("{0}.Controllers", typeof(RouteConfig).Namespace) });
}
}
url : http://localhost:22738/home/template/main
how to use Attribute Routing ?
Is there a way to solve the problem? I have no idea
It would behoove you spend some time reading the documenation for attribute routing. See: http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx
First, you haven't enabled attribute routing. You need the following in RouteConfig.cs:
routes.MapMvcAttributeRoutes();
Second, you can't apply [Route] to a controller class. You can use [RoutePrefix] if that's what you're looking for. However, bear in mind, that if you include a param in your route prefix, all your action must accept that param. Although, since you have a param of action here, it's entirely possible that you just don't understand how this works. You don't pass the action name with attribute routing. The action that is hit is determined by the one that has the matching route.
Third, when you define a route on an action, you need only specify the portion of the route not covered by RouteArea or RoutePrefix. Using the tilde (~) says that you want to ignore all set prefixes and define the whole route for the action, if that's what you actually want here, that's fine, but just keep in mind that you don't have to follow the /Controller/Action/{id} convention employed by the default route in RouteConfig.cs. The whole point of attribute routing is to define custom routes easily. If you're going to rely on the default route, you might as well just use that and forget about attribute routing.
Finally, in your route, you're accepting the param, id, but your action doesn't take it. Instead it has its own param of template. The params need to match or either the route will not match or the action won't be able to work.
To summarize, the following is likely what you're looking for:
[RouteArea("Administrator")]
[RoutePrefix("home")]
public partial class HomeController : Controller
{
[Route("template/{template}")]
public virtual ActionResult Template(string template)
{
switch (template.ToLower())// error :Object reference not set to an instance of an object..
{
case "main":
return PartialView(Url.Content(MVC.Administrator.Home.Views.Main));
default:
throw new Exception("template not known");
}
}
}
I am using MVC 5.1 with AutoFac.
I dont understand why the below route from each controller conflict with this URL: https://localhost:44300/Home/login
I thought it would map to the first method. I get this error though:
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
AllThings.WebUI.Controllers.AccountController
AllThings.WebUI.Controllers.PostController
public class AccountController : Controller
{
//
// GET: /Account/Login
[Route("~/{site}/Login")]
[Route("~/Account/Login")]
[Route("~/{country:maxlength(2)}/{site}/Login")]
[Route("~/{country:maxlength(2)}/Account/Login")]
[AllowAnonymous]
public ActionResult Login(string returnUrl, string country, string site)
{
return View();
}
}
public class PostController : Controller
{
[Route("~/{site}/{CategoryUrl?}")]
[Route("~/{country:maxlength(2)}/{site}/{CategoryUrl?}", Name = "ResultList")]
[AllowAnonymous]
public ActionResult List(string country, string site, SearchCriteriaViewModel searchCriteriaViewModel)
{
return View("List", searchCriteriaViewModel);
}
}
The main problem is that you have 3 possible routes that can match /Home/Login.
[Route("~/{site}/Login")]
[Route("~/Account/Login")]
[Route("~/{site}/{CategoryUrl?}")]
Liberal use of placeholders, especially that is all you have in a URL template definition is not a good thing. You should use literals in the URL or if you use placeholders, there should be constraints on them so they don't conflict.
Note that the following routs conflict as well:
[Route("~/{country:maxlength(2)}/{site}/Login")]
[Route("~/{country:maxlength(2)}/Account/Login")]
[Route("~/{country:maxlength(2)}/{site}/{CategoryUrl?}", Name = "ResultList")]
Any one of them could match UK/Account/Login.
Additionally, using a tilde (~) is to override a route prefix (see the MSDN documentation). If your controller doesn't define one, you should just start with the first segment or placeholder.
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.
I am using a third party service that does an async callback to a URL I provide to them.
So I tell them to use http://www.mysite.com/Status/Incoming.
This must obviously map to an Incoming() method on my StatusController.
However, what I don't have control over is the format of the parameters they call my URL with.
E.g. They will do a callback such as: http://www.mysite.com/Status/Incoming?param1=val1¶m2=val2¶m3=val3
I want to map this to the parameters of my action method: Incoming(string param1, string param2, int param3)
How do I do this?
I have found a lot of stuff about custom routing, but nothing about legacy QueryString parameters.
There is no such thing as legacy query string parameters. There are query string parameters and they are part of the HTTP specification. And assuming that the http://www.mysite.com/Status/Incoming?param1=val1¶m2=val2¶m3=val3 url is called you don't need any route to make it map to the following action (the default route will do just fine):
public ActionResult Incoming(string param1, string param2, string param3)
{
...
}
The default model will take care of binding those values.
Why not use a catch all?
routes.MapRoute(
"Incoming",
"Status/Incoming/{*path}", // URL with parameters
new { controller = "Status", action = "Incoming"}
);
then in your controller,
public ActionResult Incoming(string path){
// the next line would probably be better off in a model binder, but this works:
var dictionary = path
.Substring(path.IndexOf("?")+1)
.Split("&")
.Select(x =>
{
var kvArray = x.Split("=");
return new KeyValuePair<string, string>(kvArray[0], kvArray[1]);
})
.ToDictionary(x=>x.Key,x=>x.Value);
return Incoming(dictionary);
}
public ActionResult Incoming(Dictionary<string,string> dictionary){
//do stuff
}
All that being said, I think using the Request.QueryString is probably a better approach. As long as you are using MVC, it is accessible from your controller. However, if you can guarantee that the correct parameters will be passed then Darin's approach is going to be the best choice.
When I've had to deal with this before now, I just use the "legacy" call of Request.QueryString. It still works, even if it isn't very graceful.
This is ASP.NET MVC v1 (not using the v2 yet)
I have a route entry like this:
routes.MapRoute(
"Srp",
"soeg-{searchQuery}/{listingType}",
new { controller = "Srp", action = "Search", listingType = string.Empty },
new { listingType = "privat|forhandler|"}
);
and an action to match it:
public ActionResult Search(QueryParameters queryParameters)
It works perfectly - the mvc framework knows to map the searchQuery and listingType onto the two properties of the QueryParameters object with the same names.
My problem is unit testing. I'm using Mvccontrib project and LOVING the ShouldMapTo method:
[Test]
public void RegisterSrpRoutes_SoegWithKeywordAndValidListingType_ShouldMapCorrectly()
{
var queryParameters = new QueryParameters {SearchQuery = "hest", ListingType = "privat"};
"~/soeg-hest/privat".ShouldMapTo<SrpController>(controller => controller.Search(queryParameters));
}
It doesn't work though! I used to have specific parameters on my action like this:
public ActionResult Search(string searchQuery, string listingType)
which worked (obviously the unittest would try and map to Search with two parameters (strings) instead of this one object.
Does anyone have an idea of how to solve the problem, short of going back to writing all properties as parameters. The mvc automapping of properties rocks, but i'm hoping there is some way i can have mvccontribs testhelper work with that as well.
It's been a while since I looked at this code, but I believe that it does a .Equals call on the parameter you send. For primitive types this is easy, but for you parameter object, try implementing the Equals override and have it test the equality of each of the properties.