Skip to content

Instantly share code, notes, and snippets.

@loosechainsaw
Created January 27, 2012 08:17
Show Gist options
  • Save loosechainsaw/1687757 to your computer and use it in GitHub Desktop.
Save loosechainsaw/1687757 to your computer and use it in GitHub Desktop.
Route Generator for MVC
RouteBuilder.CreateRoutesFrom()
.CurrentAssembly()
.WithActionNamesWithUnderscoresAsSeperatedRoutes()
.IgnoreAspNetMvcDefaultRoute("Home","Index")
.AssignRoutes(routes);
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class ExplicitRouteAttribute : Attribute
{
public string Url { get; set; }
public RouteValueDictionary Constraints { get; set; }
public RouteValueDictionary Defaults { get; set; }
public ExplicitRouteAttribute(string url) : this(url,null)
{
}
public ExplicitRouteAttribute(string url, RouteValueDictionary defaults) : this(url,defaults,null)
{
}
public ExplicitRouteAttribute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints)
{
Url = url;
Defaults = defaults;
Constraints = constraints;
}
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class IgnoreActionAttribute : Attribute
{
}
public class RouteGenerationSemanticModel
{
private readonly IList<Assembly> _assembliesToScan;
public RouteGenerationSemanticModel()
{
Area = String.Empty;
Controller = String.Empty;
Action = String.Empty;
_assembliesToScan = new List<Assembly>();
}
public string Area { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public IRouteGenerationConvention RouteCreationConvention { get; set; }
public IEnumerable<Assembly> AssembliesToScan { get { return _assembliesToScan; } }
public bool IgnoreAspNetMvcDefaultRoute { get; set; }
public void ScanAssembly(Assembly assembly)
{
_assembliesToScan.Add(assembly);
}
}
public class RouteGenerationOptions
{
public string AreaName { get; set; }
public bool IgnoreMvcDefaultRoute { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
}
public interface ICanAssignRoutes
{
ICanAssignRoutes IgnoreAspNetMvcDefaultRoute(string controller, string action);
void AssignRoutes(RouteCollection routes);
}
public interface ICanBuildRoutesBuilder
{
ICanBuildRoutesBuilder CurrentAssembly();
ICanBuildRoutesBuilder AreaName(string area);
ICanBuildRoutesBuilder ScanAssembly(string assemblyName);
ICanBuildRoutesBuilder ScanAssembly(Assembly assembly);
ICanAssignRoutes WithActionNamesAsRoutes();
ICanAssignRoutes WithActionNamesWithUnderscoresAsSeperatedRoutes();
ICanAssignRoutes UsingAspNetMvcDefaults();
ICanAssignRoutes UsingRouteGenerationConvention(IRouteGenerationConvention convention);
}
public interface IRouteGenerationConvention
{
IEnumerable<RouteBase> GetRoutes(IEnumerable<Type> candidates, RouteGenerationOptions options);
}
public class ActionNameRouteConvention : IRouteGenerationConvention
{
public IEnumerable<RouteBase> GetRoutes(IEnumerable<Type> candidates, RouteGenerationOptions options)
{
foreach (var type in candidates.Where(x => x.Name.EndsWith("Controller")))
{
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
var attrs = method.GetCustomAttributes(false);
if(attrs != null && attrs.Any(x => x.GetType() == typeof(IgnoreActionAttribute)))
continue;
if (IsAspNetDefaultRoute(options, method, type))
continue;
var url = String.Format("{0}/", method.Name);
var optional = new List<string>();
url = ProcessParameters(optional, url, method);
var defaults = new RouteValueDictionary { { "controller", type.Name.Replace("Controller", String.Empty) }, { "action", method.Name } };
foreach (var opt in optional)
{
defaults.Add(opt, UrlParameter.Optional);
}
yield return new Route(url, new MvcRouteHandler())
{
Defaults = defaults
};
}
}
yield break;
}
private static string ProcessParameters(ICollection<string> optional, string url, MethodInfo method)
{
foreach (var parameter in method.GetParameters())
{
if (parameter.IsOptional)
optional.Add(parameter.Name);
url += "{" + parameter.Name + "}";
}
return url;
}
private static bool IsAspNetDefaultRoute(RouteGenerationOptions options, MethodInfo method, Type type)
{
return options.IgnoreMvcDefaultRoute && type.Name == String.Format("{0}Controller", options.Controller) && method.Name == options.Action;
}
}
public class ActionNameWithUnderscoresRouteConvention : IRouteGenerationConvention
{
public IEnumerable<RouteBase> GetRoutes(IEnumerable<Type> candidates, RouteGenerationOptions options)
{
foreach (var type in candidates.Where(x => x.Name.EndsWith("Controller")))
{
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
var attrs = method.GetCustomAttributes(false);
if (attrs != null && attrs.Any(x => x.GetType() == typeof(IgnoreActionAttribute)))
continue;
if (IsAspNetDefaultRoute(options, method, type))
continue;
var url = String.Empty;
var optional = new List<string>();
if (attrs != null && attrs.Any(x => x.GetType() == typeof(ExplicitRouteAttribute)))
continue;
url = ProcessUrlSegments(url, method);
url = ProcessParameters(optional, url, method);
var defaults = new RouteValueDictionary { { "controller", type.Name.Replace("Controller", String.Empty) }, { "action", method.Name } };
foreach (var opt in optional)
{
defaults.Add(opt, UrlParameter.Optional);
}
yield return new Route(url, new MvcRouteHandler())
{
Defaults = defaults
};
}
}
yield break;
}
private static string ProcessUrlSegments(string url, MethodInfo method)
{
if (method.Name.Contains("_"))
{
var names = method.Name.Split(new[] { '_' });
url = names.Aggregate(url, (current, name) => current + (name + "/"));
}
else
{
url = String.Format("{0}/", method.Name);
}
return url;
}
private static string ProcessParameters(ICollection<string> optional, string url, MethodInfo method)
{
foreach (var parameter in method.GetParameters())
{
if (parameter.IsOptional)
optional.Add(parameter.Name);
url += "{" + parameter.Name + "}";
}
return url;
}
private static bool IsAspNetDefaultRoute(RouteGenerationOptions options, MethodInfo method, Type type)
{
return options.IgnoreMvcDefaultRoute && type.Name == String.Format("{0}Controller", options.Controller) && method.Name == options.Action;
}
}
public class AspNetMvcRouteConvention : IRouteGenerationConvention
{
public IEnumerable<RouteBase> GetRoutes(IEnumerable<Type> candidates, RouteGenerationOptions options)
{
if (options.IgnoreMvcDefaultRoute)
yield break;
yield return new Route("{controller}/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = UrlParameter.Optional })
};
}
}
public class RouteBuilder : ICanBuildRoutesBuilder, ICanAssignRoutes
{
private readonly RouteGenerationSemanticModel model;
private RouteBuilder()
{
model = new RouteGenerationSemanticModel();
}
public static ICanBuildRoutesBuilder CreateRoutesFrom()
{
return new RouteBuilder();
}
public ICanBuildRoutesBuilder CurrentAssembly()
{
model.ScanAssembly(Assembly.GetCallingAssembly());
return this;
}
public ICanBuildRoutesBuilder AreaName(string area)
{
model.Area = area;
return this;
}
public ICanBuildRoutesBuilder ScanAssembly(string assemblyName)
{
model.ScanAssembly(Assembly.Load(assemblyName));
return this;
}
public ICanBuildRoutesBuilder ScanAssembly(Assembly assembly)
{
model.ScanAssembly(assembly);
return this;
}
public ICanAssignRoutes WithActionNamesAsRoutes()
{
if (model.AssembliesToScan == null || !model.AssembliesToScan.Any())
throw new InvalidOperationException("You have not specified any assemblies to scan");
model.RouteCreationConvention = new ActionNameRouteConvention();
return this;
}
public ICanAssignRoutes WithActionNamesWithUnderscoresAsSeperatedRoutes()
{
if (model.AssembliesToScan == null || !model.AssembliesToScan.Any())
throw new InvalidOperationException("You have not specified any assemblies to scan");
model.RouteCreationConvention = new ActionNameWithUnderscoresRouteConvention();
return this;
}
public ICanAssignRoutes UsingAspNetMvcDefaults()
{
if (model.AssembliesToScan == null || !model.AssembliesToScan.Any())
throw new InvalidOperationException("You have not specified any assemblies to scan");
model.RouteCreationConvention = new AspNetMvcRouteConvention();
return this;
}
public ICanAssignRoutes UsingRouteGenerationConvention(IRouteGenerationConvention convention)
{
if (model.AssembliesToScan == null || !model.AssembliesToScan.Any())
throw new InvalidOperationException("You have not specified any assemblies to scan");
model.RouteCreationConvention = convention;
return this;
}
public ICanAssignRoutes IgnoreAspNetMvcDefaultRoute(string controller, string action)
{
model.IgnoreAspNetMvcDefaultRoute = true;
model.Controller = controller;
model.Action = action;
return this;
}
public void AssignRoutes(RouteCollection routes)
{
if (model.RouteCreationConvention == null)
throw new InvalidOperationException("No route creation convention assigned");
model.AssembliesToScan.ToList().ForEach(x => model.RouteCreationConvention.GetRoutes(x.GetTypes(),
new RouteGenerationOptions
{
AreaName = model.Area,
IgnoreMvcDefaultRoute = model.IgnoreAspNetMvcDefaultRoute,
Controller = model.Controller,
Action = model.Action
})
.ToList().ForEach(routes.Add));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment