AttributeRouting
Define your routes directly on actions and controllers in:
Web API
Getting Started
ASP.NET MVC
Install the AttributeRouting nuget package:
PM> Install-Package AttributeRouting
The nuget package drops a file in your project at ~/App_Start/AttributeRouting.cs/vb
.
This file configures AttributeRouting to scan your project's assembly for route attributes when your app starts.
Hooray! At this point, you are ready to start using AttributeRouting! Jump to the basics →.
ASP.NET Web API
Install the AttributeRouting.WebApi nuget package:
PM> Install-Package AttributeRouting.WebApi
The nuget package drops a file in your project at ~/App_Start/AttributeRoutingHttp.cs/vb
.
This file configures AttributeRouting to scan your project's assembly for route attributes when your app starts.
Beware! Due to integration issues with the Web API WebHost framework, the following features will not work:
- performance enhancements when matching routes,
- custom route handlers,
- querystring parameter constraints,
- subdomain routing,
- localization applied to inbound/outbound urls, and
- lowercasing, appending prefixes, etc to generated routes.
These features all have to wait for vNext of the Web API.
Self-Hosted Web API
Install the AttributeRouting.WebApi.Hosted nuget package:
PM> Install-Package AttributeRouting.WebApi.Hosted
The install drops a file in your project at ~/AttributeRouting.cs/vb
.
This file contains a static method for registering routes in the HttpSelfHostConfiguration.
Here's a quick example of setting up a self-hosted application:
class Program { static void Main(string[] args) { var config = new HttpSelfHostConfiguration("http://localhost:8080"); config.Routes.MapHttpAttributeRoutes(cfg => { cfg.AddRoutesFromAssembly(Assembly.GetExecutingAssembly()); // Specify other configuration options here. }); using (var server = new HttpSelfHostServer(config)) { server.OpenAsync().Wait(); Console.WriteLine("Routes:"); config.Routes.Cast<HttpRoute>().LogTo(Console.Out); Console.WriteLine("Press Enter to quit."); Console.ReadLine(); } } }
Basics
Defining Routes
Routes are defined directly on actions using attributes.
There are five such attributes:
GET
, which generates a URL that responds to GET and HEAD requests;
POST
, which generates a URL that responds to POST requests;
PUT
, which generates a URL that responds to PUT requests;
DELETE
, which generates a URL that responds to DELETE requests; and
Route
, which generates a URL that responds to all or only specified methods.
using AttributeRouting.Web.Http; public class SampleController : Controller { [GET("Sample")] public ActionResult Index() { /* ... */ } [POST("Sample")] public ActionResult Create() { /* ... */ } [PUT("Sample/{id}")] public ActionResult Update(int id) { /* ... */ } [DELETE("Sample/{id}")] public string Destroy(int id) { /* ... */ } [Route("Sample/Any-Method-Will-Do")] public string Wildman() { /* ... */ } }
Attention VB.NET users!
Get
is a restricted keyword in the language, so to specify a GET request,
you must enter: [[GET]("Some/Url")]
.
Multiple Routes on an Action
You can have multiple GET routes on a single action.
If you do so, be sure to use the ActionPrecedence
property
to indicate the primary route so that you generate the correct outbound URLs.
[GET("", ActionPrecedence = 1)] [GET("Posts")] [GET("Posts/Index")] public ActionResult Index() { /* ... */ }
Route Defaults
Default values for URL parameters are specified inline. Separate the parameter from the default value with an equals sign:
[GET("Demographics/{state=MT}/{city=Missoula}")] public ActionResult Index(string state, string city) { /* ... */ }
Optional Route Parameters
Optional URL parameters are specified inline. Append a question mark after the parameter name:
[GET("Demographics/{state?}/{city?}")] public ActionResult Index(string state, string city) { /* ... */ }
Route Constraints
Route constraints are specified inline.
They take the form {parameterName:constraint(params)}
.
Following are all the built-in constraints:
// Type constraints [GET("Int/{x:int}")] [GET("Long/{x:long}")] [GET("Float/{x:float}")] [GET("Double/{x:double}")] [GET("Decimal/{x:decimal}")] [GET("Bool/{x:bool}")] [GET("Guid/{x:guid}")] [GET("DateTime/{x:datetime}")] // String constraints [GET("Alpha/{x:alpha}")] [GET("Length/{x:length(1)}")] [GET("LengthRange/{x:length(2, 10)}")] [GET("MinLength/{x:minlength(10)}")] [GET("MaxLength/{x:maxlength(10)}")] // Numeric constraints [GET("Min/{x:min(1)}")] [GET("Max/{x:max(10)}")] [GET("Range/{x:range(1, 10)}")] // Regex constraint [GET(@"Regex/{x:regex(^Howdy$)}")] // Querystring parameter constraints [GET("QuerystringParamExists?{x}")] [GET("QuerystringParamsExist?{x}&{y}")] [GET("QuerystringParamConstraints?{x:int}&{y:int}")]
Chaining and Combining with Defaults and Optionals
You can chain constraints, effectively and’ing them, and you can constrain while also specifying default values and optional params:
[GET("Chained/{state:alpha:length(2)}")] [GET("Defaults/{state:alpha:length(2)=MT}")] [GET("Optionals/{age:min(18)?}")]
Adding Custom Constraints
The constraint system is plug-and-play. You can easily add your own
IRouteConstraint
or IHttpRouteConstraint
when configuring AttributeRouting using the InlineRouteConstraints
property on the configuration object.
routes.MapAttributeRoutes(cfg => { // ... cfg.InlineRouteConstraints.Add("custom", typeof(CustomConstraint)); });
Then:
[GET("Path/{param:custom}")]
Simple Enum Constraints
There is a generic attribute EnumRouteConstraint<T>
, which allows you
to register your own enum constraints via the extensibility method described above.
routes.MapAttributeRoutes(cfg => { // ... cfg.InlineRouteConstraints.Add("color", typeof(EnumRouteConstraint<Color>)); });
Then:
[GET("Paintbrush/{which:color}")]
Constraining URL Parameters Globally
You can configure constraints to apply to parameters globally
across all the routes you define using the AddDefaultRouteConstraint
method on the configuration object.
This works by matching route parameters names against a specified pattern.
When a match is found, then the specified constraint is applied against the parameter.
routes.MapAttributeRoutes(cfg => { // ... cfg.AddDefaultRouteConstraint("id", new IntRouteConstraint()); });
Named Routes
To use named routes, specify a value for the RouteName
property of the route attributes.
[GET("Named/Route", RouteName = "Awesome")])
Auto-Generating Route Names
You can have AttributeRouting generate route names for you automatically. Just configure this feature when registering attribute routes:
routes.MapAttributeRoutes(config => { // ... config.AutoGenerateRouteNames = true; config.RouteNameBuilder = RouteNameBuilders.FirstInWins; });
The RouteNameBuilder
property is a
Func<RouteSpecification, string>
delegate.
You can define your own delegate to name your routes,
or use one of the two RouteNameBuilders provided by AttributeRouting:
-
RouteNameBuilders.FirstInWins
: This builder will generate routes in the form "Area_Controller_Action". In the case of routes that yield duplicate names, the duplicate route is not named and the builder will return null. -
RouteNameBuilders.Unique
: This builder ensures that every route has a unique name. Preferably, it generates routes names like "Area_Controller_Action". However, in the case of routes that yield duplicate names, it will append the HTTP method (if not a GET route) and/or a unique index to the route. So the most heinous possible form is "Area_Controller_Action_Method_Index".
FirstInWins
.
For Self-hosted Web API projects, the default builder is Unique
,
due to the fact that self-hosted Web API applications require every route to be uniquely named.
Route Prefixes
You can prefix all the routes in a controller using the RoutePrefix
attribute.
This is handy when you want to nest routes.
[RoutePrefix("Posts/{postId}")] public class CommentsController : ControllerBase { [GET("Comments")] public ActionResult Index(int postId) { /* ... */ } [GET("Comments/{id}")] public ActionResult Show(int postId, int id) { /* ... */ } }
This will generate the following routes:
- ~/Posts/{postId}/Comments
- ~/Posts/{postId}/Comments/{id}
Multiple Route Prefixes on a Controller
If you want to apply multiple route prefixes, go right ahead:
[RoutePrefix("Prefix/First", Precedence = 1)] [RoutePrefix("Prefix/Second")] public class MultiplePrefixController : Controller { [GET("Index")] public ActionResult Index() { /* ... */ } [GET("Details")] public ActionResult Details() { /* ... */ } }
This will generate the following routes:
- ~/Prefix/First/Index
- ~/Prefix/First/Details
- ~/Prefix/Second/Index
- ~/Prefix/Second/Details
Notice the Precedence
property on that first prefix.
It controls the order in which multiple prefixes are applied.
The value is used in the same way as the other precedence properties
(more here).
Ignoring Route Prefixes for Certain Routes
If you want to ignore the route prefix for a given route, just say so:
[RouteArea("Area")] [RoutePrefix("Prefix")] public class IgnorePrefixController : Controller { [GET("Index")] // => "Area/Prefix/Index" [GET("NoPrefix", IgnoreRotuePrefix = true)] // => "Area/NoPrefix" [GET("Absolute", IsAbsoluteUrl = true)] // => "Absolute" public ActionResult Index() { /* ... */ } }
ASP.NET MVC Areas
Areas can be mapped by applying the RouteAreaAttribute
on a controller.
All the routes defined in the controller will be mapped to the specified area.
These routes will also be prefixed with the area name.
[RouteArea("Admin")] public class PostsController : ControllerBase { /* ... */ }
If you are defining more than one controller for an area,
consider using a base controller decorated with the RouteAreaAttribute
and deriving all the controllers in the area from the base controller.
[RouteArea("Admin")] public abstract class AdminControllerBase : Controller { /* ... */ } public class PostsController : AdminControllerBase { /* ... */ } public class CommentsController : AdminControllerBase { /* ... */ } public class TagsController : AdminControllerBase { /* ... */ }
Overriding the Area URL Prefix
By default, areas defined with the RouteAreaAttribute
use the area name
as a prefix for all routes in that area. To override this behavior,
use the AreaUrl
property of the RouteAreaAttribute
:
[RouteArea("AreaName", AreaUrl = "MyCustomPrefix")]
Ignoring Area URLs for Certain Routes
If you want to ignore the area URL prefix for a given route, just say so:
[RouteArea("Area")] [RoutePrefix("Prefix")] public class IgnorePrefixController : Controller { [GET("Index")] // => "Area/Prefix/Index" [GET("NoAreaUrl", IgnoreAreaUrl = true)] // => "Prefix/NoAreaUrl" [GET("Absolute", IsAbsoluteUrl = true)] // => "Absolute" public ActionResult Index() { /* ... */ } }
Configuration
Important Note
Once you start to customize the configuration settings,
you must tell AttributeRouting the assemblies or controller types you wish to scan for route attributes.
Luckily this is simple. Just use one of the following methods of the configuration object:
AddRoutesFromAssembly
, AddRoutesFromController
, or AddRoutesFromControllersOfType
:
routes.MapAttributeRoutes(config => { config.AddRoutesFromAssembly(Assembly.GetExecutingAssembly()); config.AddRoutesFromController<MyController>(); config.AddRoutesFromControllersOfType<MyBaseController>(); });
Route Precedence
There are four ways to control route precedence:
-
among routes for an action
using the
ActionPrecedence
property of the route attributes; -
among routes in a controller
using the
ControllerPrecedence
property of the route attributes; -
among controllers in a site
using
AddRoutesFromController
method of the configuration object; -
among routes in a site
using the
SitePrecedence
property of the route attributes.
Controlling First and Last among Actions, Controllers, and Sites
As you read on, keep in mind that when using the
ActionPrecedence
, ControllerPrecedence
, and SitePrecedence
properties,
you can specify the order using positive and negative integers,
allowing you to control what routes are first and last:
- (0), 1, 2, 3, ... = first, second, third, ...
- -1, -2, -3, ... = last, second to last, third from last, ...
Precedence Among Routes for an Action
If you need to specify the precedence of routes defined against an action,
you can use the ActionPrecedence
property of the route attributes:
[GET("", ActionPrecedence = 1)] [GET("Posts")] [GET("Posts/Index")] public void Index() { /* ... */ }
Precedence Among Routes in a Controller
By default, the order of a route among the routes defined for a controller
is determined by the order of the action in the controller.
If you need to override this behavior, use the ControllerPrecedence
property
of the route attributes:
public class PrecedenceController : Controller { [GET("Route1", ControllerPrecedence = 1)] public ActionResult Route1() { /* ... */ } [GET("Route3")] public ActionResult Route3() { /* ... */ } [GET("Route2", ControllerPrecedence = 2)] public ActionResult Route2() { /* ... */ } }
Precedence Among Controllers in a Site
To control the precedence of routes on a per-controller basis,
use the AddRoutesFromController
method of the configuration object:
routes.MapAttributeRoutes(config => { config.AddRoutesFromController<PostsController>(); config.AddRoutesFromController<HomeController>(); });
You can also choose to add the routes from controllers to the beginning or end of the route table:
routes.MapAttributeRoutes(config => { config.AddRoutesFromController<PostsController>(); config.AddRoutesFromAssembly(Assembly.GetExecutingAssembly()); config.AddRoutesFromController<AccountController>(); });
In the preceding case, the routes from the PostController are registered first, then all the routes from the executing assembly are registered, and then the routes from the AccountController are registered.
There is another method, AddRoutesFromControllersOfType
,
which is useful if you want to register all the routes from an area, say,
before the other routes in your application.
This is convenient if you use a base class for an area.
routes.MapAttributeRoutes(config => { config.AddRoutesFromControllersOfType<AdminControllerBase>(); config.AddRoutesFromAssembly(Assembly.GetExecutingAssembly()); });
Precedence Among Routes in a Site
If you need to take an arbitrary route and put it at the top or bottom of your route table,
use the SitePrecedence
property of the route attributes:
[GET("I-Am-The-First-Route", SitePrecedence = 1)] public string IAmTheFirstRoute() { /* ... */ } [GET("I-Am-The-Last-Route", SitePrecedence = -1)] public string IAmTheLastRoute() { /* ... */ }
Lowercase URL Generation
If you want to generate lowercase outbound urls,
you can set the UseLowercaseRoutes
property on the configuration object:
routes.MapAttributeRoutes(config => { // ... config.UseLowercaseRoutes = true; });
If you would like to preserve the case of URL parameters
while lowercasing the rest of the url,
use the PreserveCaseForUrlParameters
property on the configuration object:
routes.MapAttributeRoutes(config => { // ... config.UseLowercaseRoutes = true; config.PreserveCaseForUrlParameters = true; });
Overriding the Global Configuration Settings
If you want to override the global config settings for a single route,
use the UseLowercaseRoute
and PreserveCaseForUrlParameters
properties of the route attributes:
[GET("Whatever", UseLowercaseRoute = true)] [GET("Whatever", UseLowercaseRoute = true, PreserveCaseForUrlParameters = true)]
Trailing Slash URL Generation
If you want to generate outbound urls that end with a trailing slash,
use the AppendTrailingSlash
property of the configuration object:
routes.MapAttributeRoutes(config => { // ... config.AppendTrailingSlash = true; });
Overriding the Global Configuration Setting
If you want to override the global config setting for a single route,
use the AppendTrailingSlash
property of the route attributes:
[GET("Whatever", AppendTrailingSlash = true)]
Advanced Features
Localizing URLs
You can localize your URLs using an AttribtueRouting translation provider.
A translation provider can be used to work against databases, resx files, etc.
To create your own, simply extend TranslationProviderBase
:
public abstract class TranslationProviderBase { /// <summary> /// List of culture names that have translations available via this provider. /// <summary> public abstract IEnumerable<string> CultureNames { get; } /// <summary> /// Gets the translation for the given route component by key and culture. /// <summary> /// <param name="key">The key of the route component to translate.<param> /// <param name="cultureName">The culture name for the translation.<param> public abstract string Translate(string key, string cultureName); }
Then, register your provider via the AddTranslationProvider
method of the configuration object.
routes.MapAttributeRoutes(config => { // ... config.AddTranslationProvider(new CustomTranslationProvider()); });
Using the Built-In FluentTranslationProvider
The FluentTranslationProvider
stores translation in-memory in a dictionary.
You can add translations in a strongly-typed manner:
var provider = new FluentTranslationProvider(); // You can add translations in a strongly-typed manner provider.AddTranslations() .ForController<TranslationController>() .AreaUrl(new Dictionary<string, string> { { "es", "es-Area" } }) .RoutePrefixUrl(new Dictionary<string, string> { { "es", "es-Prefix" } }) .RouteUrl(c => c.Index(), new Dictionary<string, string> { { "es", "es-Index" } });
You can also add translations by refencing the keys specified by the
TranslationKey
properties on the RouteArea
, RoutePrefix
,
and GET
, POST
, PUT
, DELETE
and Route
attributes.
var provider = new FluentTranslationProvider(); translations.AddTranslations() .ForKey("CustomAreaKey", new Dictionary<string, string> { { "es", "es-CustomArea" } }) .ForKey("CustomPrefixKey", new Dictionary<string, string> { { "es", "es-CustomPrefix" } }) .ForKey("CustomRouteKey", new Dictionary<string, string> { { "es", "es-CustomIndex" } });
If You Roll Your Own, Use Translation Keys
When using the FLuentTranslationProvider
, you don't have to worry about translation keys,
as they are managed internally and are based upon the names of your areas, controllers, and action methods.
However, if you use your own translation provider, you will want to apply translation keys
to the components of your routes. There is a TranslationKey
property available on
the RouteArea
, RoutePrefix
, and GET
, POST
,
PUT
, DELETE
and Route
attributes
[RouteArea("Area", TranslationKey = "CustomAreaKey")] [RoutePrefix("Prefix", TranslationKey = "CustomPrefixKey")] public class TranslationController : Controller { [GET("Index", TranslationKey = "CustomRouteKey")] public ActionResult CustomIndex() { /* ... */ } }
Translations and Inbound Requests
A route is added to the route table for each translation provided. So if you have 10 routes and translate the URLs for two cultures, you will have 30 routes in your route table.
By default, the inbound request handling doesn't care what about the current request culture,
so if you are browsing in Spanish, requesting the English or French URLs for an action will also work.
However, you can change this via the ConstrainTranslatedRoutesByCurrentUICulture
property of the configuration object:
routes.MapAttributeRoutes(config => { // ... config.AddTranslationProvider(provider); config.ConstrainTranslatedRoutesByCurrentUICulture = true; });
When you choose to constrain inbound requests this way, a route is considered when:
- no translations exist for the route;
- the route is translated for the current thread's current UI culture; or,
- the route is translated for the current thread's neutral culture when no translation exists for the specific culture (eg: you have translation for fr and the current UI culture is fr-FR).
If you want to use URL parameters for specifying the culture (/en/home, /pt/inicio, etc),
then use the CurrentUICultureResolver
property of the configuration object.
Given the current HTTP context and route data, this delegate returns the culture name.
By default, it returns the name of the current UI culture for the current thread.
routes.MapAttributeRoutes(config => { // ... config.ConstrainTranslatedRoutesByCurrentUICulture = true; config.CurrentUICultureResolver = (httpContext, routeData) => { return (string)routeData.Values["culture"] ?? Thread.CurrentThread.CurrentUICulture.Name; }; });
Translations and Outbound Routes
Translated URLs will be generated by the MVC framework via UrlHelper.Action()
and Html.ActionLink()
if a translation is available for the route.
In order to support this, you must set the thread's CurrentUICulture
property.
A simple solution involves detecting the user's preferences via the request context:
// In global.asax public MvcApplication() { BeginRequest += OnBeginRequest; } protected void OnBeginRequest(object sender, System.EventArgs e) { if (Request.UserLanguages != null && Request.UserLanguages.Any()) { var cultureInfo = new CultureInfo(Request.UserLanguages[0]); Thread.CurrentThread.CurrentUICulture = cultureInfo; } }
DRY: Route Conventions
You can define custom route conventions by applying an attribute derived from
RouteConventionAttributeBase
(source)
to your controllers. It's very simple:
public class MyRouteConventionAttribute : RouteConventionAttributeBase { public override IEnumerable<IRouteAttribute> GetRouteAttributes(MethodInfo actionMethod) { // TODO: Yield IRouteAttributes (GET/POST/PUT/DELETE/Route). } } [MyRouteConvention] public class MyController : Controller { /* ... */ }
Take Heed! When defining your own convention, it will help to know a bit about how the routes are constructed and added to the route table:
-
When the routes are being scanned, convention based routes for an action
are registered before explicitly defined routes.
So if you want your explicitly defined routes to come first,
use the
ActionPrecedence
property. -
If you decide to override the virtual
GetDefaultRoutePrefixes
orGetDefaultRouteArea
method, note that the attributes you generate will only be used if no explicit attributes are applied to your controller. Explicit attributes will act as overrides.
Two conventions are provided out-of-the-box. These provide example of what you can do.
-
The
RestfulRouteConventionAttribute
(source) will add RESTful routes for actions in ASP.NET MVC controllers. -
The
DefaultHttpRouteConventionAttribute
(source) will add routes typical of Web API controllers.
Restful Route Convention
Use the RestfulRouteConventionAttribute
like so:
[RestfulRouteConvention] public class PostsController : Controller { public ActionResult Index() { /* ... */ } public ActionResult New() { /* ... */ } public ActionResult Create() { /* ... */ } public ActionResult Show(int id) { /* ... */ } public ActionResult Edit(int id) { /* ... */ } public ActionResult Update(int id) { /* ... */ } public ActionResult Delete(int id) { /* ... */ } public ActionResult Destroy(int id) { /* ... */ } }
This will add the following routes to the route table:
Action | HTTP Method | URL |
---|---|---|
Index | GET | ~/Posts |
New | GET | ~/Posts/New |
Create | POST | ~/Posts |
Show | GET | ~/Posts/{id} |
Edit | GET | ~/Posts/{id}/Edit |
Update | PUT | ~/Posts/{id} |
Delete | GET | ~/Posts/{id}/Delete |
Destroy | DELETE | ~/Posts/{id} |
Default Http Route Convention
Use the DefaultHttpRouteConventionAttribute
like so:
[DefaultHttpRouteConvention] public class ProductsController : ApiController { public IEnumerable<string> GetAll() { /* ... */ } public string Get(int id) { /* ... */ } public string Post() { /* ... */ } public string Delete(int id) { /* ... */ } public string Put(int id) { /* ... */ } }
This will add the following routes to the route table:
Action | HTTP Method | URL |
---|---|---|
GetAll | GET | ~/Products |
Get | GET | ~/Products/{id} |
Post | POST | ~/Products |
Put | PUT | ~/Products/{id} |
Delete | DELETE | ~/Products/{id} |
Subdomain Routing
You can map your ASP.NET MVC areas to subdomains using the
Subdomain
property of the RouteAreaAttribute
.
Doing so ensures that the routes for the area are matched
only when requests are made to the specified subdomain.
Here's how:
[RouteArea("Users", Subdomain = "users")] public class SubdomainController : Controller { [GET("")] public ActionResult Index() { /* ... */ } }
When you do this, the area URL prefix ("Users" in this case) is not added to the route registered in the route table. So the index action will end up matching http://users.domain.com/, not http://users.domain.com/Users.
If you want to have an area map to a subdomain and
have a URL prefix for the area, use the AreaUrl
property like so:
[RouteArea("Admin", Subdomain = "internal", AreaUrl = "admin")] public class SubdomainWithAreaUrlController : Controller { [GET("")] public ActionResult Index() { /* ... */ } }
The route registered in the route table in this case will match http://internal.domain.com/admin.
Configuring Subdomain Parsing Logic and the Default Subdomain
By default, AttributeRouting will treat everything from the requested hostname
up to the domain name as the subdomain. This is based on an assumed format
like {localName}.domain.com
. AR also assumes that the default subdomain
for an application is www
, which is obviously going to be wrong in some cases.
To remain flexible, you can configure AR to use a custom delegate for parsing the subdomain from the hostname.
You can also configure the default subdomain name.
routes.MapAttributeRoutes(config => { // ... config.SubdomainParser = host => "return whatever string value you want"; config.DefaultSubdomain = "xyz"; });
Dynamically Configuring Subdomains
If your subdomains change depending on the hosted environment,
or if you need to configure the subdomains at runtime, use the
MapArea
method of the configuration object:
routes.MapAttributeRoutes(config => { // ... config.MapArea("AreaName").ToSubdomain("whatever"); });
More...
Debugging Routes
Use the LogRoutesHandler
to emits the routes in the RouteTable to a browser.
To use it, add the following line to your web.config:
<httpHandlers> <add path="routes.axd" verb="GET" type="AttributeRouting.Web.Logging.LogRoutesHandler, AttributeRouting"/> </httpHandlers>
Heads Up! If you installed via nuget, then this handler was registered automagically.
T4 Templates
There are controller templates available for both C# and Visual Basic for MVC 2, 3, and 4. They are available in the t4 folder off the project root.
If you're using MVC 4, you're in luck – the nuget package AttributeRouting.CodeTemplates.MVC4 will pull the templates into your project:
PM> Install-Package AttributeRouting.CodeTemplates.MVC4