OK, I know the easiest way to use Attribute Routing in MVC 6 is:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
And here is the controller code using the new Tokens (without Areas):
[Route("[controller]/[action]")]
public class HomeController : Controller
{
}
And here is the controller code using the new Tokens (with Areas):
[Area("MyArea")]
[Route("[controller]/[action]")]
public class HomeController : Controller
{
}
Questions:
Is this how MS wants you to code your controllers using Areas and Tokens?
Or is there a cleaner way?
Could they have somehow created an [area] Token?
Lastly, I know I can play this game, but isn't the 1st convention-based approach - app.UseMvc() - the simplest?
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
});
}
Microsoft gives you two options, each with own pros and cons. You should decide which one is better based on your context/needs.
Convention based routing
Pros:
It is simpler, instead of defining everything on an per-action level you just decide once and for all how your urls will looks like .
Perfect when your urls exactly match controller/action names.
If you want to change and url you need change the name of class/method.
Perfect for projects with clean and predictable url structure.
Perfect for quickly prototyping a new project.
Slightly easier for developers -> by knowing an url you know in which controller/action lies the functionality
Cons:
You loose little bit of control
Attribute-based routing
Pros:
Gives you total control over how the url looks like, for example for SEO purposes.
If you want to change an url you do not need to change the name of class/method.
Perfect when your urls do not match controller and action names or you want to hand-craft them (i.e customer wants that).
Perfect for maintaining backward compatibility, when you have an legacy project and want to have compatible url structure.
Cons:
Requires little bit more work, as you need to define the routes in your code. Please note that adding an attribute to a class/method is a matter of seconds.
How to decide which one to use:
If you have/expect to have very few routes.MapRoute() calls -> use convention routing as its simpler
If you have/expect to have lots of routes.MapRoute() calls -> use attribute routing
Related
I have an ASP.NET Web API I wrote and have published. Now that its out there we are looking at doing some improvements, and these improvements involve changes to certain calls which means we need to version to keep existing clients working.
I have used attribute routing so far in my app. Methods are invoked by: Controller/Action via RoutePrefix and Route attributes.
When I do need to create a V2 of my classes, I only want to recreate the classes that have actually changed, and redirect other routes back to v1 classes because they haven't changed. (Otherwise I just end up with a lot of boilerplate code, or duplicate code).
What I want to do is have the following routes work for my v1 version of classes:
Controller/Action
For V2 I want any new classes to go to V2, and any classes that haven't changed I want to return the HttpControllerDescriptor from V1 class. The route would look like v2/Controller/Action but would be redirected to Controller/Action.
I've implemented a IHttpControllerSelector and return the appropriate HttpControllerDescriptors but its not making the call into the method. I believe its because the routing information doesn't match the action. (When I put in an IHttpActionSelector and trace the exception it says "multiple actions were found that match the request).
So, I'm guess I'm wondering: Is this even possible? Is this the best way to achieve what I'm trying to do?
Here is what I implemented for versioning support in asp.net web api. Important to note I did not use attribute routing but explicit routes in WebApiConfig.cs so if you want to follow this pattern you would need to switch back to explicit routes. Also I do not prefer version information in the actual route, I use a custom (ie. "version") parameter in Accept header. I also set the version per mime type as in the below example. If version number is not set by the client or if the requested version does not exist this will fall back to default controller.
Create a class and inherit from DefaultHttpControllerSelector so you can fallback to base class behavior when you wanted to.
Override SelectController method as such:
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IDictionary controllers = GetControllerMapping();
IHttpRouteData routeData = request.GetRouteData();
string controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor controllerDescriptor;
if (string.IsNullOrWhiteSpace(controllerName))
{
return base.SelectController(request);
}
if (!controllers.TryGetValue(controllerName, out controllerDescriptor))
{
return null;
}
string version = GetVersionFromAcceptHeader(request);
if (string.Equals(version, "1"))
{
return controllerDescriptor;
}
string newName = string.Concat(controllerName, "V", version);
HttpControllerDescriptor versionedControllerDescriptor;
if (controllers.TryGetValue(newName, out versionedControllerDescriptor))
{
return versionedControllerDescriptor;
}
return controllerDescriptor;
}
Register this controller selector in your webapiconfig Register method:
config.Services.Replace(typeof(IHttpControllerSelector), new YourControllerSelector(config));
I'm very confused about the design of my RESTful services!
If I was doing this using vanilla MVC3/4 then I would simply have action methods marked [HTTPGet] etc. and I could have multiple Get's per controller. I this way I would organise controllers by their "meta group".
I've looked at the Web API MVC4 template and it gives me the automatic translation from an Http GET to the Getxxx() method - but this implies a single Get per controller and organising controllers by object, rather than function...which seems to make some sense.
I see many posts on adding named routes - but this seems to break the natural model of Get, Post, Put, Delete. If I do that - then aren't I (in essence) just going back to vanilla MVC4?
Is there any impact on having lots of controllers?
Am I thinking
about this correctly?
Shortly,
Is there any impact on having lots of controllers?
No
Am I thinking about this correctly?
Generally yes.
Default WebAPI/MVc template uses routing that relays on prefixes and naming GetXXX, PostXX.
RouteTable.Routes.MapRoute(
"WithActionApi",
"api/{controller}/{action}/{id}"
);
But you can create your own custom routing with action names instead. Then you uses in URL name of your action method and as you've wrote Attributes to set HTTP Verbs like [HttpGet]
RouteTable.Routes.MapRoute(
"DefaultApi",
"api/{controller}/{id}",
new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);
[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
return new string[] { "byCategory1", "byCategory2" };
}
I'm upgrading a custom solution where I can dynamically register and unregister Web Api controllers to use the new attribute routing mechanism. However, it seems to recent update to RTM break my solution.
My solution exposes a couple of Web Api controllers for administration purposes. These are registered using the new HttpConfigurationExtensions.MapHttpAttributeRoutes method call.
The solution also allows Web Api controllers to be hosted in third-party assemblies and registered dynamically. At this stage, calling HttpConfigurationExtensions.MapHttAttributeRoutes a second time once the third-party controller is loaded would raise an exception. Therefore, my solution uses reflection to inspect the RoutePrefix and Route attributes and register corresponding routes on the HttpConfiguration object.
Unfortunately, calling the Web Api results in the following error:
"No HTTP resource was found that matches the request URI".
Here is a simple controller that I want to use:
[RoutePrefix("api/ze")]
public sealed class ZeController : ApiController
{
[HttpGet]
[Route("one")]
public string GetOne()
{
return "One";
}
[HttpGet]
[Route("two")]
public string GetTwo()
{
return "Two";
}
[HttpPost]
[Route("one")]
public string SetOne(string value)
{
return String.Empty;
}
}
Here is the first solution I tried:
configuration.Routes.MapHttpRoute("ZeApi", "api/ze/{action}");
Here is the second solution I tried:
var type = typeof(ZeController);
var routeMembers = type.GetMethods().Where(m => m.IsPublic);
foreach (MethodInfo method in routeMembers)
{
var routeAttribute = method.GetCustomAttributes(false).OfType<RouteAttribute>().FirstOrDefault();
if (routeAttribute != null)
{
string controllerName = type.Name.Substring(0, type.Name.LastIndexOf("Controller"));
string routeTemplate = string.Join("/", "api/Ze", routeAttribute.Template);
configuration.Routes.MapHttpRoute(method.Name, routeTemplate);
}
}
I also have tried a third solution, whereby I create custom classes that implement IHttpRoute and trying to register them with the configuration to no avail.
Is it possible to use legacy-style route mapping based upon the information contained in the new routing attributes ?
Update
I have installed my controller in a Web Application in order to troubleshoot the routing selection process with the Web Api Route Debugger. Here is the result of the screenshot:
As you can see, the correct action seems to be selected, but I still get a 404 error.
Update2
After further analysis, and per Kiran Challa's comment below, it seems that the design of Web Api prevents mixing attribute routing and conventional routing, and that what I want to do is not possible using this approach.
I have created a custom attribute [RouteEx] that serves the same purpose of the Web Api [Route] attribute, and now my code works perfectly.
I guess, since this is not possible using the conventional attribute routing, none of the answers on this question could legitimately be consisered valid. So I'm not nominating an answer just yet.
You shouldn't be required to use reflection and inspect the attribute-routing based attributes yourself. Attribute routing uses existing Web API features to get list of controllers to scan through.
Question: Before the switch to attribute routing, how were you loading these assemblies having the
controllers?
If you were doing this by IAssembliesResolver service, then this solution should work even with attribute routing and you should not be needing to do anything extra.
Regarding your Update: are you calling MapHttpAttributeRoutes?
I'm trying to build a REST service in a Sitecore root. My application start looks like this:
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = System.Web.Http.RouteParameter.Optional });
}
And my URL looks like this:
http://{mydomain}/api/books
I have the correct controller and all that.
But Sitecore keeps redirecting me to the 404 page. I've added the path to the IgnoreUrlPrefixes node in the web.config, but to no avail. If I had to guess, I'd think that Sitecore's handler is redirecting before my code gets the chance to execute, but I really don't know.
Does anybody have any idea what might be wrong?
Your assessment is correct. You need a processor in the httpRequestBegin pipeline to abort Sitecore's processing. See the SystemWebRoutingResolver in this answer:
Sitecore and ASP.net MVC
It's also described in this article:
http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2010/10/Sitecore-MVC-Crash-Course.aspx
But I'll include the code here as well. :)
public class SystemWebRoutingResolver : Sitecore.Pipelines.HttpRequest.HttpRequestProcessor
{
public override void Process(Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
{
RouteData routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(args.Context));
if (routeData != null)
{
args.AbortPipeline();
}
}
}
Then in your httpRequestBegin configuration:
<processor type="My.SystemWebRoutingResolver, My.Classes" />
You might want to have a look at Sitecore Web Api
It's pretty much the same you are building.
Another option, which I've used to good effect, is to use the content tree, the "star" item, and a sublayout/layout combination dedicated to this purpose:
[siteroot]/API/*/*/*/*/*/*/*/*/*
The above path allows you to have anywhere between 1 and 9 segments - if you need more than that, you probably need to rethink your process, IMO. This also retains all of the Sitecore context. Sitecore, when unable to find an item in a folder, attempts to look for the catch-all star item and if present, it renders that item instead of returning a 404.
There are a few ways to go about doing the restful methods and the sublayout (or sublayouts if you want to segregate them by depth to simplify parsing).
You can choose to follow the general "standard" and use GET, PUT, and POST calls to interact with these items, but then you can't use Sitecore Caching without custom backend caching code). Alternately, you can split your API into three different trees:
[siteroot]/API/GET/*/*/*/*/*/*/*/*/*
[siteroot]/API/PUT/*/*/*/*/*/*/*/*/*
[siteroot]/API/POST/*/*/*/*/*/*/*/*/*
This allows caching the GET requests (since GET requests should only retrieve data, not update it). Be sure to use the proper caching scheme, essentially this should cache based on every permutation of the data, user, etc., if you intend to use this in any of those contexts.
If you are going to create multiple sublayouts, I recommend creating a base class that handles general methods for GET, PUT, and POST, and then use those classes as the base for your sublayouts.
In your sublayouts, you simply get the Request object, get the path (and query if you're using queries), split it, and perform your switch case logic just as you would with standard routing. For PUT, use Response.ReadBinary(). For POST use the Request.Form object to get all of the form elements and iterate through them to process the information provided (it may be easiest to put all of your form data into a single JSON object, encapsulated as a string (so .NET sees it as a string and therefore one single property) and then you only have one element in the post to deserialize depending on the POST path the user specified.
Complicated? Yes. Works? Yes. Recommended? Well... if you're in a shared environment (multiple sites) and you don't want this processing happening for EVERY site in the pipeline processor, then this solution works. If you have access to using MVC with Sitecore or have no issues altering the pipeline processor, then that is likely more efficient.
One benefit to the content based method is that the context lifecycle is exactly the same as a standard Sitecore page (logins, etc.), so you've got all the same controls as any other item would provide at that point in the lifecycle. The negative to this is that you have to deal with the entire page lifecycle load before it gets to your code... the pipeline processor can skip a lot of Sitecore's process and just get the data you need directly, making it faster.
you need to have a Pipeline initializer for Routing:
It will be like :
public class Initializer
{
public void Process(PipelineArgs args)
{
RouteCollection route = RouteTable.Routes;
route.MapHttpRoute("DefaultApi", "api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
}
}
On config file you will have :
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<initialize>
<processor type="_YourNameSpace.Initializer,_YourAssembly" />
</initialize>
</pipelines>
</sitecore>
</configuration>
Happy coding
ASP.NET MVC 2 controllers and actions use UpperCamelCase.
For some reasons many big sites, including SO, use lowercase (with underscore) for controllers and actions in the urls. Examples:
https://stackoverflow.com/questions
https://stackoverflow.com/users/377920/randomguy
http://www.reddit.com/ad_inq/
http://www.wired.com/special_multimedia/mobile/
etc.
I would like to know how this is accomplished.
The default router seems to be case-insensitive, ie. stackoverflow.com/questions/ask will be directed to Questions-controller's Ask() method without a problem.
However, say we want to direct questions/add_to_favorites to Questions-controller's AddToFavorites() action.
How is this accomplished?
Is it now required to use Html.ActionLink("add_to_favorites")
instead of Html.ActionLink("AddToFavorites") to make the links in the HTML point as questions/add_to_favorites instead of Questions/AddToFavorites?
Edit:
Similar posts
How can I have lowercase routes in ASP.NET MVC?
ASP.NET MVC: Get lowercase links (instead of Camel Case)
One way to support underscores is to use the ActionName attribute:
[ActionName("add_to_favorites")]
public ActionResult AddToFavorites() {
// ...
}
However, this doesn't work for controllers. Perhaps if we could somehow remove all the underscores from the request before it gets to the routing mechanism, then it would work.
You can add custom routes manually. This is not an universal solution and must be added for every controller and action separately.
routes.MapRoute(
"Web2.0 RoR style lowercase URLs with underscores",
"questions-foo/add_to_favorites",
new { controller = "Questions", action = "AddToFavorites" }
);
The cool thing is that the URL generating Html-helper methods don't need to be modified. The routing table is used to route incoming requests and to generate URLs. So,
Html.ActionLink("Add to favorites", "Questions", "AddToFavorites"); maps to /questions-foo/add_to_favorites.
Note that the original /Question/AddToFavorites still works as does /qUeStIoN/aDdtOfAvOrItEs as well as /qUeStIoNs-FOO/ADD_TO_FAVORITES because the default routing mechanism is case-insensitive.