ASP.NET MVC Routing Via Attributes [zamknięte]

80

W StackOverflow Podcast # 54 Jeff wspomina, że ​​rejestrują swoje trasy URL w bazie kodu StackOverflow za pomocą atrybutu powyżej metody, która obsługuje trasę. Brzmi jak dobry pomysł (z zastrzeżeniem, które przedstawił Phil Haack odnośnie priorytetów tras).

Czy ktoś mógłby podać jakąś próbkę, aby to się stało?

Czy są jakieś „sprawdzone metody” korzystania z tego stylu routingu?

TorgoGuy
źródło

Odpowiedzi:

62

AKTUALIZACJA : To zostało opublikowane na codeplex . Kompletny kod źródłowy, a także wstępnie skompilowany zestaw są dostępne do pobrania. Nie miałem jeszcze czasu na umieszczenie dokumentacji na stronie, więc ten wpis SO na razie wystarczy.

AKTUALIZACJA : Dodałem kilka nowych atrybutów do obsługi 1) kolejności tras, 2) ograniczeń parametrów trasy i 3) wartości domyślnych parametrów trasy. Poniższy tekst odzwierciedla tę aktualizację.

Właściwie zrobiłem coś takiego dla moich projektów MVC (nie mam pojęcia, jak Jeff to robi przy użyciu stackoverflow). Zdefiniowałem zestaw atrybutów niestandardowych: UrlRoute, UrlRouteParameterConstraint, UrlRouteParameterDefault. Mogą być dołączane do metod akcji kontrolera MVC, aby automatycznie wiązać z nimi trasy, ograniczenia i wartości domyślne.

Przykładowe użycie:

(Zwróć uwagę, że ten przykład jest nieco wymyślony, ale demonstruje tę funkcję)

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

Definicja 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; }
}

Definicja 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; }
}

Definicja 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; }
}

Zmiany w Global.asax.cs:

Zamień wywołania MapRoute za pomocą pojedynczego wywołania funkcji RouteUtility.RegisterUrlRoutesFromAttributes:

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

        RouteUtility.RegisterUrlRoutesFromAttributes(routes);
    }

Definicja RouteUtility.RegisterUrlRoutesFromAttributes:

Pełne źródło jest dostępne w Codeplex . Przejdź do tej witryny, jeśli masz jakieś uwagi lub raporty o błędach.

DSO
źródło
Myślę, że zrobienie tego z atrybutami uniemożliwia użycie domyślnych tras i ograniczeń trasy ...
Nicolas Cadilhac
Przy takim podejściu nigdy nie potrzebowałem tras domyślnych, ponieważ wiążesz każdą trasę z określoną metodą. Masz rację co do ograniczeń. Rozglądałem się za możliwością dodawania ograniczeń jako właściwości atrybutu, ale napotkałem problem, ponieważ ograniczenia MVC są określane za pomocą anonimowych obiektów, a właściwości atrybutów mogą być tylko typami prostymi. Myślę, że nadal jest możliwe tworzenie ograniczeń jako atrybutu (z większym kodowaniem), ale jeszcze się tym nie przejmowałem, ponieważ tak naprawdę nie potrzebowałem ograniczeń w mojej pracy MVC do tego momentu (mam tendencję do sprawdzania wartości tras w kontrolerze).
DSO
3
Bardzo dobrze! Nasz RouteAttribute jest bardzo podobny do tego, dodając tylko trochę dodatkowej funkcjonalności pomocnika. Muszę dodać odpowiedź z wyszczególnieniem różnic.
Jarrod Dixon
1
To jest fantastyczne. Uwielbiam to.
BowserKingKoopa
1
To jest świetne! Używam MvcContrib przez jakiś czas i nie miałem pojęcia, że ​​tam jest. Wspomniałeś w swoim oryginalnym poście, że nie masz czasu, aby to udokumentować. Jest to ciągle aktualne? Wygląda na to, że przynajmniej wzmianka o tym w dokumentacji MvcContrib byłaby bardzo pomocna, aby programiści przynajmniej wiedzieli, że tam jest. Dzięki!
Todd Menier
44

Możesz także wypróbować AttributeRouting , który jest dostępny z github lub za pośrednictwem nuget .

To bezwstydna wtyczka, bo jestem autorem projektu. Ale dang, jeśli nie jestem zadowolony z jego używania. Ty też możesz być. W wiki repozytorium github znajduje się mnóstwo dokumentacji i przykładowego kodu .

Dzięki tej bibliotece możesz wiele zrobić:

  • Udekoruj swoje akcje atrybutami GET, POST, PUT i DELETE.
  • Mapuj wiele tras do jednej akcji, porządkując je za pomocą właściwości Order.
  • Określ wartości domyślne i ograniczenia trasy za pomocą atrybutów.
  • Określ opcjonalne parametry za pomocą prostego? token przed nazwą parametru.
  • Określ nazwę trasy do obsługi nazwanych tras.
  • Zdefiniuj obszary MVC na kontrolerze lub kontrolerze podstawowym.
  • Grupuj lub zagnieżdżaj swoje trasy razem, używając prefiksów tras zastosowanych do kontrolera lub kontrolera podstawowego.
  • Obsługuj starsze adresy URL.
  • Ustaw priorytet tras wśród tras zdefiniowanych dla akcji, w kontrolerze oraz między kontrolerami i kontrolerami podstawowymi.
  • Automatycznie generuj wychodzące adresy URL małymi literami.
  • Zdefiniuj własne konwencje tras niestandardowych i zastosuj je na kontrolerze, aby wygenerować trasy dla akcji w kontrolerze bez atrybutów standardowych (pomyśl w stylu RESTful).
  • Debuguj swoje trasy przy użyciu podanego HttpHandler.

Jestem pewien, że zapominam o innych rzeczach. Sprawdź to. Instalacja przez nuget jest bezbolesna.

UWAGA: Od 16.04.12 AttributeRouting obsługuje również nową infrastrukturę interfejsu API sieci Web. Na wypadek, gdybyś szukał czegoś, co sobie z tym poradzi. Dzięki subkamran !

miejsce
źródło
10
Ten projekt wydaje się bardziej dojrzały (lepsza dokumentacja, więcej funkcji, kompletny zestaw testów) niż inne wspomniane opcje
David Laing
3
Zgadzam się, wydaje się, że robi wszystko, czego możesz chcieć, z dobrą przykładową dokumentacją.
Mike Chamberlain,
3
Dziękuję Ci bardzo. Z radością korzystam z tego rozwiązania, które rozwiązało wszystkie moje konflikty dotyczące routingu, niejasności i zamieszanie.
Valamas,
3
Hej miejscu, powinieneś napisać powyższe punktory na swojej stronie github, ponieważ znalazłem ten wpis SO, ponieważ szukałem więcej szczegółów :)
GONeale
2
Żeby zagrać adwokata diabłów, czy jest jakaś korzyść z deklarowania wszystkich tras w jednym miejscu? Czy coś tracimy, czy też jesteśmy w jakikolwiek sposób ograniczeni przejściem na tę metodę?
GONeale
9

1. Pobierz plik RiaLibrary.Web.dll i odwołaj się do niego w projekcie witryny ASP.NET MVC

2. Zdekoruj metody kontrolera za pomocą atrybutów [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, „?” Zaloguj się Parametr „{? category}” oznacza, że ​​jest opcjonalny. Nie musisz określać tego jawnie w domyślnych ustawieniach trasy, co jest równe temu:

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

3. Zaktualizuj plik 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);
    }
}

Jak ustawić wartości domyślne i ograniczenia? Przykład:

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

Jak ustawić zamówienie? Przykład:

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

[Url("forums/new", Order = 1)]
public ActionResult NewThread()
{
    return View();
}
Konstantin Tarkus
źródło
1
Bardzo dobrze! Szczególnie podoba mi się {?param}nazewnictwo parametrów opcjonalnych.
Jarrod Dixon
3

Ten post ma na celu jedynie rozszerzenie odpowiedzi DSO.

Konwertując moje trasy na atrybuty, musiałem obsłużyć atrybut ActionName. Więc w 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),
});

Również nazwa trasy nie była odpowiednia. Nazwa jest budowana dynamicznie za pomocą controllerName.RouteName. Ale moje nazwy tras są ciągami znaków stałych w klasie kontrolera i używam ich również do wywoływania Url.RouteUrl. Dlatego naprawdę potrzebuję, aby nazwa trasy w atrybucie była rzeczywistą nazwą trasy.

Inną rzeczą, którą zrobię, jest przekonwertowanie atrybutów default i constraint na AttributeTargets.Parameter, aby móc je przypisać do parametrów.

Nicolas Cadilhac
źródło
Tak, oscylowałem przy zachowaniu nazywania tras. Prawdopodobnie najlepiej jest zrobić to, co zrobiłeś, po prostu użyj tego, co jest w atrybucie tak, jak jest, lub ustaw go na zero. Niezły pomysł, umieszczenie domyślnych / ograniczeń na samych parametrach. Prawdopodobnie opublikuję to w Codeplex w pewnym momencie, aby lepiej zarządzać zmianami.
DSO
0

Musiałem uzyskać routing ITCloud działający w asp.net mvc 2 przy użyciu AsyncController - aby to zrobić, po prostu edytuj klasę RouteUtility.cs w kodzie źródłowym i ponownie skompiluj. Musisz usunąć „Completed” z nazwy akcji w linii 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,
});

Następnie w AsyncController udekoruj XXXXCompleted ActionResult znanymi UrlRoutei UrlRouteParameterDefaultatrybutami:

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

Mam nadzieję, że pomoże to komuś z tym samym problemem.

TimDog
źródło
FYI, konwencja ma mieć atrybuty powiązane z MVC w metodzie ActionNameAsync, a nie w metodzie ActionNameCompleted.
Erv Walter
Dzięki - nie zdawałem sobie z tego sprawy.
TimDog,