Кажется, проблема связана с порядком, в котором будут регистрироваться маршруты области. Поскольку я не нашел какой-либо убедительной документации о том, как сопоставляются маршруты области, и не думаю, что безопасно полагаться на какой-либо конкретный порядок, я продолжил и создал новый класс для инкапсуляции маршрутизации, которую я мог бы вызывать из другого файл маршрутов. По сути, это «модулирует» область, так что ее можно использовать в качестве набора маршрутов, добавляемых к другому префиксу url.
Основная идея состоит в том, чтобы эмулировать способность Джанго включать другие urlconfs , чтобы я мог сделать что-то вроде следующего в FooAreaRegistration
:
public override void RegisterArea(AreaRegistrationContext context)
{
new BarAreaModule().RegisterRoutes(namePrefix:"bar", urlPrefix:"foo/", context.Routes);
context.MapRoute("foo_default", "foo/{controller}/{action}/{id}", new{controller="Default", action="Index", id=UrlParameter.Optional});
}
и все маршруты должны быть зарегистрированы упорядоченным образом, чтобы foo/bar
не путался с маршрутом "foo_default".
Ниже приведен полный источник класса AreaModule для всех, кому это интересно:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
namespace Gov.Wa.Hecb.UI.Portal.Areas
{
/// <summary>
/// Allows a group of functionality to be registered to any given url prefix. This is to be used to replace an Area in cases
/// where an Area is either a sub-module to another area, or if an area's functionality is to be parameterized and reused in
/// multiple urls.
/// </summary>
public abstract class AreaModule
{
public abstract string AreaName { get; }
/// <summary>
/// Registers all routes necessary for this Module to function with the given url prefix
/// </summary>
/// <param name="namePrefix"></param>
/// <param name="urlPrefix">a slash-appended string representing the url to match up to the module</param>
/// <param name="routes"></param>
public void RegisterRoutes(string namePrefix, string urlPrefix, RouteCollection routes)
{
if (string.IsNullOrEmpty(namePrefix))
throw new ArgumentException("namePrefix cannot be null or empty", "namePrefix");
if (string.IsNullOrEmpty(urlPrefix))
throw new ArgumentException("urlPrefix cannot be null or empty", "urlPrefix");
if (routes == null)
throw new ArgumentNullException("routes");
var context = new AreaModuleContext(AreaName, namePrefix, urlPrefix, routes);
var thisNamespace = GetType().Namespace;
if (thisNamespace != null)
context.Namespaces.Add(thisNamespace + ".*");
RegisterRoutes(context);
}
protected abstract void RegisterRoutes(AreaModuleContext context);
}
public class AreaModuleContext
{
#region Private
private readonly HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
#endregion
#region Constructors
public AreaModuleContext(string areaName, string namePrefix, string urlPrefix, RouteCollection routes, object state = null)
{
if (String.IsNullOrEmpty(areaName))
throw new ArgumentException("areaName cannot be null or empty", "areaName");
if (string.IsNullOrEmpty(namePrefix))
throw new ArgumentException("namePrefix cannot be null or empty", "namePrefix");
if (string.IsNullOrEmpty(urlPrefix))
throw new ArgumentException("urlPrefix cannot be null or empty", "urlPrefix");
if (routes == null)
throw new ArgumentNullException("routes");
AreaName = areaName;
NamePrefix = namePrefix;
UrlPrefix = urlPrefix;
Routes = routes;
State = state;
}
#endregion
#region Properties
public string AreaName { get; private set; }
public string NamePrefix { get; private set; }
public string UrlPrefix { get; private set; }
public ICollection<string> Namespaces
{
get { return _namespaces; }
}
public RouteCollection Routes { get; private set; }
public object State { get; private set; }
#endregion
#region Route Mapping
public Route MapRoute(string name, string url)
{
return MapRoute(name, url, (object) null /* defaults */);
}
public Route MapRoute(string name, string url, object defaults)
{
return MapRoute(name, url, defaults, (object) null /* constraints */);
}
public Route MapRoute(string name, string url, object defaults, object constraints)
{
return MapRoute(name, url, defaults, constraints, null /* namespaces */);
}
public Route MapRoute(string name, string url, string[] namespaces)
{
return MapRoute(name, url, null /* defaults */, namespaces);
}
public Route MapRoute(string name, string url, object defaults, string[] namespaces)
{
return MapRoute(name, url, defaults, null /* constraints */, namespaces);
}
public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces)
{
if (namespaces == null && Namespaces != null)
namespaces = Namespaces.ToArray();
var route = Routes.MapRoute(NamePrefix + name, UrlPrefix + url, defaults, constraints, namespaces);
route.DataTokens["area"] = AreaName;
// disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
// controllers belonging to other areas
var useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;
return route;
}
#endregion
}
}