ASP.NET MVC-Routing über Methodenattribute [geschlossen]

80

Im StackOverflow-Podcast Nr. 54 erwähnt Jeff, dass sie ihre URL-Routen in der StackOverflow-Codebasis über ein Attribut über der Methode registrieren, die die Route verarbeitet. Klingt nach einem guten Konzept (mit dem Vorbehalt, den Phil Haack in Bezug auf die Routenprioritäten angesprochen hat).

Könnte jemand ein Beispiel zur Verfügung stellen, um dies zu ermöglichen?

Gibt es auch "Best Practices" für die Verwendung dieses Routing-Stils?

TorgoGuy
quelle

Antworten:

62

UPDATE : Dies wurde auf Codeplex gepostet. Der vollständige Quellcode sowie die vorkompilierte Assembly stehen zum Download bereit. Ich hatte noch keine Zeit, die Dokumentation auf der Website zu veröffentlichen, daher muss dieser SO-Beitrag vorerst ausreichen.

UPDATE : Ich habe einige neue Attribute hinzugefügt, um 1) Routenreihenfolge, 2) Routenparametereinschränkungen und 3) Routenparameter-Standardwerte zu behandeln. Der folgende Text spiegelt dieses Update wider.

Ich habe tatsächlich so etwas für meine MVC-Projekte gemacht (ich habe keine Ahnung, wie Jeff es mit Stackoverflow macht). Ich habe eine Reihe von benutzerdefinierten Attributen definiert: UrlRoute, UrlRouteParameterConstraint, UrlRouteParameterDefault. Sie können an MVC-Controller-Aktionsmethoden angehängt werden, damit Routen, Einschränkungen und Standardeinstellungen automatisch an sie gebunden werden.

Anwendungsbeispiel:

(Beachten Sie, dass dieses Beispiel etwas erfunden ist, aber die Funktion demonstriert.)

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

Definition von 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; }
}

Definition von 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; }
}

Definition von 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; }
}

Änderungen an Global.asax.cs:

Ersetzen Sie Aufrufe von MapRoute durch einen einzelnen Aufruf der Funktion RouteUtility.RegisterUrlRoutesFromAttributes:

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

        RouteUtility.RegisterUrlRoutesFromAttributes(routes);
    }

Definition von RouteUtility.RegisterUrlRoutesFromAttributes:

Die vollständige Quelle ist auf Codeplex verfügbar . Bitte besuchen Sie die Website, wenn Sie Feedback oder Fehlerberichte haben.

DSO
quelle
Ich denke, dies mit Attributen zu tun, verhindert, dass Routenstandards und Routenbeschränkungen verwendet werden ...
Nicolas Cadilhac
Bei diesem Ansatz habe ich nie Standardrouten benötigt, da Sie jede Route an eine bestimmte Methode binden. Sie haben Recht mit Einschränkungen. Ich habe versucht, Einschränkungen als Attributeigenschaft hinzufügen zu können, bin jedoch auf einen Haken gestoßen, da MVC-Einschränkungen mithilfe anonymer Objekte angegeben werden und Attributeigenschaften nur einfache Typen sein können. Es ist immer noch möglich, Einschränkungen als Attribut zu verwenden (mit mehr Codierung), aber ich habe mich noch nicht darum gekümmert, da ich bis zu diesem Punkt keine Einschränkungen in meiner MVC-Arbeit benötigt habe (ich neige dazu, Routenwerte zu validieren in der Steuerung).
DSO
3
Sehr schön! Unser RouteAttribute ist diesem sehr ähnlich und fügt nur ein bisschen zusätzliche Hilfsfunktionalität hinzu. Ich muss eine Antwort hinzufügen, in der die Unterschiede aufgeführt sind.
Jarrod Dixon
1
Das ist fantastisch. Ich liebe es.
BowserKingKoopa
1
Das ist toll! Ich benutze MvcContrib schon eine Weile und hatte keine Ahnung, dass dies dort drin war. Sie haben in Ihrem ursprünglichen Beitrag erwähnt, dass Sie keine Zeit hatten, dies zu dokumentieren. Ist das noch so? Zumindest eine Erwähnung in den MvcContrib-Dokumenten scheint sehr hilfreich zu sein, damit Entwickler zumindest wissen, dass es dort ist. Vielen Dank!
Todd Menier
44

Sie können auch versuchen AttributeRouting , die aus verfügbar ist Github oder über nuget .

Dies ist ein schamloser Plug, da ich der Projektautor bin. Aber verdammt, wenn ich nicht sehr glücklich damit bin. Du könntest es auch sein. Das Github-Repository- Wiki enthält zahlreiche Dokumentationen und Beispielcodes .

Mit dieser Bibliothek können Sie viel tun:

  • Dekorieren Sie Ihre Aktionen mit den Attributen GET, POST, PUT und DELETE.
  • Ordnen Sie einer einzelnen Aktion mehrere Routen zu und ordnen Sie sie mit einer Order-Eigenschaft.
  • Geben Sie Routenstandards und -einschränkungen mithilfe von Attributen an.
  • Geben Sie optionale Parameter mit einem einfachen? Token vor dem Parameternamen.
  • Geben Sie den Routennamen für die Unterstützung benannter Routen an.
  • Definieren Sie MVC-Bereiche auf einem Controller oder Basis-Controller.
  • Gruppieren oder verschachteln Sie Ihre Routen mithilfe von Routenpräfixen, die auf einen Controller oder Basis-Controller angewendet werden.
  • Unterstützt ältere URLs.
  • Legen Sie die Priorität von Routen zwischen den für eine Aktion definierten Routen innerhalb eines Controllers sowie zwischen Controllern und Basiscontrollern fest.
  • Generieren Sie ausgehende URLs in Kleinbuchstaben automatisch.
  • Definieren Sie Ihre eigenen benutzerdefinierten Routenkonventionen und wenden Sie sie auf einen Controller an, um die Routen für Aktionen innerhalb des Controllers ohne Boilerplate-Attribute zu generieren (denken Sie an den RESTful-Stil).
  • Debuggen Sie Ihre Routen mit einem mitgelieferten HttpHandler.

Ich bin sicher, es gibt noch andere Dinge, die ich vergesse. Hör zu. Die Installation über Nuget ist problemlos.

HINWEIS: Ab dem 16.04.12 unterstützt AttributeRouting auch die neue Web-API-Infrastruktur. Nur für den Fall, dass Sie nach etwas suchen, das damit umgehen kann. Danke Subkamran !

Stelle
quelle
10
Dieses Projekt scheint ausgereifter zu sein (bessere Dokumentation, mehr Funktionen, vollständige Testsuite) als die anderen genannten Optionen
David Laing
3
Ich stimme zu, dies scheint alles zu tun, was Sie sich nur wünschen können, und dies mit einer guten Beispieldokumentation.
Mike Chamberlain
3
Vielen Dank. Ich verwende diese Lösung gerne und sie hat alle meine Routing-Konflikte, Mehrdeutigkeiten und Verwirrungen gelöst.
Valamas
3
Hey Stelle, du solltest die obigen Aufzählungspunkte auf deine Github-Seite schreiben, da ich diesen SO-Beitrag gefunden habe, als ich auf der
Suche
2
Gibt es einen Vorteil, wenn Sie Ihre Routen an einem Ort deklarieren, nur um Devils Advocate zu spielen? Als ob wir etwas verlieren oder in irgendeiner Weise auf diese Methode umsteigen?
GONeale
9

1. Laden Sie RiaLibrary.Web.dll herunter und verweisen Sie in Ihrem ASP.NET MVC- Websiteprojekt darauf

2. Dekorieren Sie Controller-Methoden mit den [Url] -Attributen:

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

Übrigens '?' Der Parameter '{? category}' bedeutet, dass er optional ist. Sie müssen dies nicht explizit in den Routenstandards angeben, was dem entspricht:

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

3. Aktualisieren Sie die Datei 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);
    }
}

Wie werden Standardeinstellungen und Einschränkungen festgelegt? Beispiel:

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

Wie stelle ich die Bestellung ein? Beispiel:

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

[Url("forums/new", Order = 1)]
public ActionResult NewThread()
{
    return View();
}
Konstantin Tarkus
quelle
1
Sehr schön! Besonders gut gefällt mir die {?param}Nomenklatur für optionale Parameter.
Jarrod Dixon
3

Dieser Beitrag dient nur dazu, die Antwort von DSO zu erweitern.

Beim Konvertieren meiner Routen in Attribute musste ich das ActionName-Attribut verarbeiten. Also in 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),
});

Auch die Benennung der Route fand ich nicht geeignet. Der Name wird dynamisch mit controllerName.RouteName erstellt. Aber meine Routennamen sind const-Zeichenfolgen in der Controller-Klasse, und ich verwende diese const, um auch Url.RouteUrl aufzurufen. Deshalb brauche ich wirklich den Routennamen im Attribut, um der tatsächliche Name der Route zu sein.

Eine andere Sache, die ich tun werde, ist, die Standard- und Einschränkungsattribute in AttributeTargets.Parameter zu konvertieren, damit ich sie an Parameter halten kann.

Nicolas Cadilhac
quelle
Ja, ich habe das Verhalten bei der Routenbenennung irgendwie verändert. Es ist wahrscheinlich am besten, das zu tun, was Sie getan haben. Verwenden Sie einfach das, was im Attribut unverändert ist, oder machen Sie es null. Gute Idee, die Standardeinstellungen / Einschränkungen für die Parameter selbst festzulegen. Ich werde dies wahrscheinlich irgendwann auf Codeplex veröffentlichen, um Änderungen besser verwalten zu können.
DSO
0

Ich habe diese beiden Ansätze zu einer Frankensteinschen Version für jeden kombiniert, der es will. (Ich mochte die optionale Parameternotation, dachte aber auch, dass sie separate Attribute von Standard / Einschränkungen sein sollten und nicht alle in einem gemischt).

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

Max Metral
quelle
0

Ich musste das ITCloud-Routing in asp.net mvc 2 mithilfe eines AsyncControllers zum Laufen bringen. Bearbeiten Sie dazu einfach die RouteUtility.cs-Klasse in der Quelle und kompilieren Sie sie neu. Sie müssen das "Abgeschlossen" aus dem Aktionsnamen in Zeile 98 entfernen

// 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,
});

Dekorieren Sie dann im AsyncController das XXXXCompleted ActionResult mit den bekannten UrlRouteund UrlRouteParameterDefaultAttributen:

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

Hoffe das hilft jemandem mit dem gleichen Problem.

TimDog
quelle
Zu Ihrer Information, Konvention besteht darin, die MVC-bezogenen Attribute für die ActionNameAsync-Methode und nicht für die ActionNameCompleted-Methode zu haben.
Erv Walter
Danke - habe das nicht bemerkt.
TimDog