Маршрутизация ASP.NET MVC через атрибуты метода

В StackOverflow Podcast # 54, Джефф упоминает, что они регистрируют свои URL-маршруты в базе данных StackOverflow через атрибут выше метода, который обрабатывает маршрут. Похоже на хорошую концепцию (с предостережением, которое Фил Хаак рассказал о приоритетах маршрутов).

Может ли кто-нибудь предоставить образец, чтобы это произошло?

Кроме того, какие-либо "лучшие практики" для использования этого стиля маршрутизации?

Ответ 1

UPDATE. Это было опубликовано на codeplex. Полный исходный код, а также предварительно скомпилированная сборка доступны для загрузки. Я еще не успел опубликовать документацию на сайте, так что это сообщение SO должно быть достаточно.

UPDATE. Я добавил несколько новых атрибутов для обработки 1) упорядочения маршрута, 2) ограничений параметров маршрута и 3) значений параметров маршрута маршрута. В приведенном ниже тексте указано это обновление.

Я действительно сделал что-то подобное для своих проектов MVC (я не знаю, как Jeff делает это с помощью stackoverflow). Я определил набор пользовательских атрибутов: UrlRoute, UrlRouteParameterConstraint, UrlRouteParameterDefault. Они могут быть привязаны к методам действий контроллера MVC, чтобы автоматически связывать маршруты, ограничения и значения по умолчанию.

Использование примера:

(Обратите внимание, что этот пример несколько надуман, но демонстрирует эту функцию)

public class UsersController : Controller
{
    // Simple path.
    // Note you can have multiple UrlRoute attributes affixed to same method.
    [UrlRoute(Path = "users")]
    public ActionResult Index()
    {
        return View();
    }

    // Path with parameter plus constraint on parameter.
    // You can have multiple constraints.
    [UrlRoute(Path = "users/{userId}")]
    [UrlRouteParameterConstraint(Name = "userId", Regex = @"\d+")]
    public ActionResult UserProfile(int userId)
    {
        // ...code omitted

        return View();
    }

    // Path with Order specified, to ensure it is added before the previous
    // route.  Without this, the "users/admin" URL may match the previous
    // route before this route is even evaluated.
    [UrlRoute(Path = "users/admin", Order = -10)]
    public ActionResult AdminProfile()
    {
        // ...code omitted

        return View();
    }

    // Path with multiple parameters and default value for the last
    // parameter if its not specified.
    [UrlRoute(Path = "users/{userId}/posts/{dateRange}")]
    [UrlRouteParameterConstraint(Name = "userId", Regex = @"\d+")]
    [UrlRouteParameterDefault(Name = "dateRange", Value = "all")]
    public ActionResult UserPostsByTag(int userId, string dateRange)
    {
        // ...code omitted

        return View();
    }

Определение атрибута UrlRouteAttribute:

/// <summary>
/// Assigns a URL route to an MVC Controller class method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class UrlRouteAttribute : Attribute
{
    /// <summary>
    /// Optional name of the route.  If not specified, the route name will
    /// be set to [controller name].[action name].
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Path of the URL route.  This is relative to the root of the web site.
    /// Do not append a "/" prefix.  Specify empty string for the root page.
    /// </summary>
    public string Path { get; set; }

    /// <summary>
    /// Optional order in which to add the route (default is 0).  Routes
    /// with lower order values will be added before those with higher.
    /// Routes that have the same order value will be added in undefined
    /// order with respect to each other.
    /// </summary>
    public int Order { get; set; }
}

Определение UrlRouteParameterConstraintAttribute:

/// <summary>
/// Assigns a constraint to a route parameter in a UrlRouteAttribute.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class UrlRouteParameterConstraintAttribute : Attribute
{
    /// <summary>
    /// Name of the route parameter on which to apply the constraint.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Regular expression constraint to test on the route parameter value
    /// in the URL.
    /// </summary>
    public string Regex { get; set; }
}

Определение UrlRouteParameterDefaultAttribute:

/// <summary>
/// Assigns a default value to a route parameter in a UrlRouteAttribute
/// if not specified in the URL.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class UrlRouteParameterDefaultAttribute : Attribute
{
    /// <summary>
    /// Name of the route parameter for which to supply the default value.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Default value to set on the route parameter if not specified in the URL.
    /// </summary>
    public object Value { get; set; }
}

Изменения в Global.asax.cs:

Заменить вызовы MapRoute одним вызовом функции RouteUtility.RegisterUrlRoutesFromAttributes:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        RouteUtility.RegisterUrlRoutesFromAttributes(routes);
    }

Определение RouteUtility.RegisterUrlRoutesFromAttributes:

Полный источник находится на codeplex. Перейдите на сайт, если у вас есть отзывы или отчеты об ошибках.

Ответ 2

Вы также можете попробовать AttributeRouting, который доступен из github или через nuget.

Это бесстыдный плагин, так как я автор проекта. Но, если я не очень доволен этим. Возможно, вы тоже. В репозитории github имеется много документации и пример кода wiki.

С помощью этой библиотеки вы можете сделать много:

  • Украсьте свои действия с помощью атрибутов GET, POST, PUT и DELETE.
  • Сопоставьте несколько маршрутов с одним действием, упорядочив их с помощью свойства Order.
  • Укажите значения по умолчанию и ограничения маршрута, используя атрибуты.
  • Укажите необязательные параметры с помощью простого? перед именем параметра.
  • Укажите имя маршрута для поддержки именованных маршрутов.
  • Определить области MVC на контроллере или базовом контроллере.
  • Группируйте или объединяйте маршруты вместе с использованием префиксов маршрута, применяемых к контроллеру или базовому контроллеру.
  • Поддержка устаревших URL-адресов.
  • Установите приоритет маршрутов между маршрутами, определенными для действия, внутри контроллера, а также между контроллерами и базовыми контроллерами.
  • Сгенерировать строчные исходящие URL в нижнем регистре.
  • Определите свои собственные пользовательские правила маршрута и примените их к контроллеру для генерации маршрутов действий в контроллере без атрибутов шаблонов (подумайте о стиле RESTful).
  • Отлаживайте свои маршруты, используя поставляемый HttpHandler.

Я уверен, что есть другие вещи, которые я забываю. Проверьте это. Безболезненно установить через nuget.

ПРИМЕЧАНИЕ. Начиная с 4/16/12, AttributeRouting также поддерживает новую инфраструктуру веб-API. На всякий случай вы ищете что-то, что может с этим справиться. Спасибо subkamran!

Ответ 3

1. Загрузите RiaLibrary.Web.dll и укажите его в своем веб-проекте ASP.NET MVC

2. Разверните методы контроллера с помощью атрибутов [Url]:

public SiteController : Controller
{
    [Url("")]
    public ActionResult Home()
    {
        return View();
    }

    [Url("about")]
    public ActionResult AboutUs()
    {
        return View();
    }

    [Url("store/{?category}")]
    public ActionResult Products(string category = null)
    {
        return View();
    }
}

BTW, '?' параметр "{? category}" означает, что он является необязательным. Вам не нужно будет указывать это явно в значениях по умолчанию маршрута, что равно этому:

routes.MapRoute("Store", "store/{category}",
new { controller = "Store", action = "Home", category = UrlParameter.Optional });

3. Обновить файл Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoutes(); // This does the trick
    }

    protected void Application_Start()
    {
        RegisterRoutes(RouteTable.Routes);
    }
}

Как установить значения по умолчанию и ограничения? Пример:

public SiteController : Controller
{
    [Url("admin/articles/edit/{id}", Constraints = @"id=\d+")]
    public ActionResult ArticlesEdit(int id)
    {
        return View();
    }

    [Url("articles/{category}/{date}_{title}", Constraints =
         "date=(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])")]
    public ActionResult Article(string category, DateTime date, string title)
    {
        return View();
    }
}

Как настроить порядок? Пример:

[Url("forums/{?category}", Order = 2)]
public ActionResult Threads(string category)
{
    return View();
}

[Url("forums/new", Order = 1)]
public ActionResult NewThread()
{
    return View();
}

Ответ 4

Это сообщение просто для расширения ответа DSO.

При преобразовании моих маршрутов в атрибуты мне нужно было обработать атрибут ActionName. Итак, в GetRouteParamsFromAttribute:

ActionNameAttribute anAttr = methodInfo.GetCustomAttributes(typeof(ActionNameAttribute), false)
    .Cast<ActionNameAttribute>()
    .SingleOrDefault();

// Add to list of routes.
routeParams.Add(new MapRouteParams()
{
    RouteName = routeAttrib.Name,
    Path = routeAttrib.Path,
    ControllerName = controllerName,
    ActionName = (anAttr != null ? anAttr.Name : methodInfo.Name),
    Order = routeAttrib.Order,
    Constraints = GetConstraints(methodInfo),
    Defaults = GetDefaults(methodInfo),
});

Также я нашел, что обозначение маршрута не подходит. Имя динамически создается с именем controllerName.RouteName. Но мои имена маршрутов являются константными строками в классе контроллера, и я использую эти константы для вызова Url.RouteUrl. Поэтому мне действительно нужно, чтобы имя маршрута в атрибуте являлось фактическим именем маршрута.

Еще одна вещь, которую я буду делать, - это преобразовать атрибуты по умолчанию и ограничения в AttributeTargets.Parameter, чтобы я мог привязать их к параметрам.

Ответ 5

Я объединил эти два подхода в версию Франкенштейна для тех, кто этого хочет. (Мне понравилась дополнительная нотация параметра, но также подумал, что они должны быть отдельными атрибутами по умолчанию/ограничениям, а не всем, смешанным в один).

http://github.com/djMax/AlienForce/tree/master/Utilities/Web/

Ответ 6

Мне нужно было настроить маршрутизацию ITCloud в asp.net mvc 2 с помощью AsyncController - для этого просто отредактируйте класс RouteUtility.cs в источнике и перекомпилируйте. Вы должны отключить "Завершено" от имени действия в строке 98

// Add to list of routes.
routeParams.Add(new MapRouteParams()
{
    RouteName = String.IsNullOrEmpty(routeAttrib.Name) ? null : routeAttrib.Name,
    Path = routeAttrib.Path,
    ControllerName = controllerName,
    ActionName = methodInfo.Name.Replace("Completed", ""),
    Order = routeAttrib.Order,
    Constraints = GetConstraints(methodInfo),
    Defaults = GetDefaults(methodInfo),
    ControllerNamespace = controllerClass.Namespace,
});

Затем, в AsyncController, украсьте XXXXCompleted ActionResult знакомыми атрибутами UrlRoute и UrlRouteParameterDefault:

[UrlRoute(Path = "ActionName/{title}")]
[UrlRouteParameterDefault(Name = "title", Value = "latest-post")]
public ActionResult ActionNameCompleted(string title)
{
    ...
}

Надеюсь, что это поможет кому-то с той же проблемой.