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


80

У підкасті StackOverflow # 54 Джефф згадує, що вони реєструють свої URL-маршрути в кодовій базі StackOverflow через атрибут над методом, який обробляє маршрут. Звучить як гарна концепція (із застереженням, яке Філ Хак висловив щодо пріоритетів маршруту).

Хтось може надати зразок, щоб це сталося?

Також є якісь "найкращі практики" щодо використання цього стилю маршрутизації?

Відповіді:


62

ОНОВЛЕННЯ : Це було розміщено на codeplex . Повний вихідний код, а також попередньо скомпільована збірка доступні для завантаження. Я ще не встиг розмістити документацію на сайті, тож цієї публікації SO наразі буде достатньо.

ОНОВЛЕННЯ : Я додав кілька нових атрибутів для обробки 1) упорядкування маршруту, 2) обмеження параметрів маршруту та 3) значень параметрів маршруту за замовчуванням. Текст нижче відображає це оновлення.

Я насправді зробив щось подібне для своїх проектів MVC (я не уявляю, як Джефф робить це за допомогою 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 . Будь ласка, перейдіть на сайт, якщо у вас є відгуки або повідомлення про помилки.


Думаю, якщо це зробити з атрибутами, це заважає використовувати стандартні маршрути та обмеження маршруту ...
Ніколас Каділхак,

З таким підходом мені ніколи не потрібні маршрути за замовчуванням, оскільки ви прив'язуєте кожен маршрут до певного методу. Ви маєте рацію щодо обмежень. Я розглядав можливість додавати обмеження як властивість атрибута, але натрапив на загрозу, оскільки обмеження MVC вказуються за допомогою анонімних об’єктів, а властивості атрибутів можуть бути лише простими типами. Це все ще можливо, я думаю робити обмеження як атрибут (з більшим кодуванням), але я ще не заморочувався з цим, оскільки мені не потрібні обмеження в моїй роботі MVC до цього моменту (я схильний перевіряти значення маршруту в контролері).
DSO

3
Дуже мило! Наш RouteAttribute дуже схожий на цей, лише додавши трохи додаткової допоміжної функціональності. Мені доведеться додати відповідь із деталізацією відмінностей.
Джаррод Діксон

1
Це фантастика. Я це люблю.
BowserKingKoopa

1
Це чудово! Я використовував MvcContrib деякий час і не мав уявлення, що це там. Ви згадували у своєму оригінальному дописі, що не встигли його задокументувати. Це все ще так? Здається, принаймні згадка про це в документах MvcContrib була б дуже корисною, щоб розробники принаймні знали, що вона є. Дякую!
Тодд Меньє

44

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

Це безсоромна пробка, оскільки я автор проекту. Але бог, якщо я не дуже задоволений цим користуватися. Можливо, ти теж. У вікі- репозиторії github є безліч документації та зразкового коду .

За допомогою цієї бібліотеки ви можете зробити багато:

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

Я впевнений, що є щось інше, про що я забуваю. Перевір. Безболісно встановлювати через nuget.

ПРИМІТКА. Починаючи з 16.04.12, AttributeRouting також підтримує нову інфраструктуру веб-API. Про всяк випадок, якщо ви шукаєте щось, що з цим впорається. Дякую субкамран !


10
Цей проект здається більш зрілим (краща документація, більше можливостей, повний набір тестів), ніж інші згадані варіанти
Девід Лейнг,

3
Я згоден, це, здається, робить все, що ви могли б захотіти, і з хорошою прикладною документацією.
Mike Chamberlain

3
Дуже дякую. Я із задоволенням використовую це рішення, і воно вирішило всі мої конфлікти маршрутизації, двозначність та плутанину.
Валамас

3
Гей, місце, ти повинен написати вищезазначені пункти на своїй сторінці в github, оскільки я знайшов цей SO-допис, коли
ходив

2
Просто щоб пограти в адвокатів дияволів, чи є користь оголосити свої маршрути в одному місці? Як ми щось втрачаємо або обмежуємось у будь-якому випадку, переходячи на цей метод?
GONeale

9

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();
    }
}

До речі, "?" параметр "{? 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();
}

1
Дуже мило! Мені особливо подобається {?param}номенклатура необов’язкових параметрів.
Джаррод Діксон

3

Цей допис призначений лише для розширення відповіді 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. Але мої імена маршрутів - це рядки const у класі контролера, і я використовую ці const, щоб також викликати Url.RouteUrl. Ось чому мені справді потрібна назва маршруту в атрибуті, щоб бути фактичною назвою маршруту.

Інша річ, яку я буду робити, - це перетворити атрибути за замовчуванням і обмеження на AttributeTargets.Parameter, щоб я міг прикріпити їх до параметрів.


Так, я якось коливався щодо поведінки імен маршрутів. Ймовірно, найкраще робити те, що ви робили, просто використовуйте whats в атрибуті як є або робіть його нульовим. Приємна ідея встановити типові / обмеження на самі параметри. Можливо, я колись опублікую це на codeplex, щоб краще керувати змінами.
DSO

0

Я об’єднав ці два підходи у франкенштейнівську версію для всіх, хто цього хоче. (Мені сподобався необов’язковий запис параметра, але я також подумав, що вони повинні бути окремими атрибутами від типових / обмежень, а не змішаними в одному).

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


0

Мені потрібно було змусити маршрутизацію 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)
{
    ...
}

Сподіваюся, це допоможе комусь із тим самим питанням.


FYI, домовленість полягає в тому, щоб мати пов'язані з MVC атрибути методу ActionNameAsync, а не метод ActionNameCompleted.
Erv Walter

Дякую - не зрозумів цього.
TimDog
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.