wtorek, 17 sierpnia 2010

ASP.NET MVC 2 cz. 2 Routing w MVC

Routing to jeden z najważniejszych mechanizmów wykorzystywanych w ASP.NET MVC. Pozwala na tworzenie łatwych do zapamiętania adresów URL. Jak więc konfigurować ten system aby użytkownik mógł łatwo trafić w żądane miejsce?

Najpierw jeszcze coś o cyklu życia aplikacji MVC. Całość żądania od klienta do serwera i z powrotem przebiega w następujący sposób:
- zapytanie dociera do serwera IIS
- mechanizm routingu sprawdza adres pod kątem wprowadzonych wzorców
- po znalezieniu odpowiedniego wzorca zapytanie przekierowane jest do odpowiedniego kontrolera
- kontroler wywołuje akcję
- akcja zwraca obiekt typu ActionResult (np. ViewResult z zawartością renderowanej strony HTML)

Tak w skrócie wygląda mechanizm od kuchni. Teraz przejdźmy do sedna tego posta czyli Routingu.
Routing ustawiamy w pliku Global.asax.cs
Zapis:
routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );
możemy przedstawić w następującej formie:
            Route myRoute = new Route("{controller}/{action}/{id}", new MvcRouteHandler())
            {
                Defaults = new RouteValueDictionary(new
                {
                    controller = "Home",
                    action = "Index",
                    id = UrlParameter.Optional
                })
            };
            routes.Add(myRoute);

Każdy taki obiekt opisuje w jaki sposób ma być obsłużone żądanie HTTP. Są to więc jakby matryce dla różnych wersji zapytania HTTP. Dla przykładu rozważmy kilka przykładowych adresów Url i ich rozwinięć:
/ - {controller = "Home", action = "Index" }
/Produkt - {controller = "Produkt", action = "Index" }
/Produkt/Bombonierki - {controller = "Produkt", action = "Bombonierki" }
/Produkt/Bombonierki/76 - {controller = "Produkt", action = "Bombonierki", id="76" }

Konfiguracja routingu opiera się o trzy główne filary:
- RoutingBase - pozwala na tworzenie własnych mechanizmów routingu
- Route - standardowa klasa wykorzystywana w routingu
- RouteCollection - całokształt konfiguracji routingu

RouteTable.Routes - specjalna, statyczna instancja klasy ROuteCollection, którą wypełniamy podczas startu naszej aplikacji. Należy pamiętać, że podczas przeszukiwania tej listy jest ona rozpatrywana od góry do dołu i pierwsze dopasowanie jest wybierane. Dla przykładu:
Chcemy aby po wpisaniu adresu /Katalog wywołał się kontroler Produkt i jego metoda ListaProduktow. Zrealizujemy to w dwóch formach (krótszej i dłuższej):

            //1. Sposób
            routes.MapRoute(
                "Default", // Route name
                "Katalog", // URL with parameters
                new { controller = "Produkt", action = "ListaProduktow", id = UrlParameter.Optional } // Parameter defaults
            );

            //2. Sposób
            Route myRoute = new Route("Katalog", new MvcRouteHandler())
            {
                Defaults = new RouteValueDictionary(new
                {
                    controller = "Produkt",
                    action = "ListaProduktow",
                })
            };
            routes.Add(myRoute);

Który z tych sposobów wybierzemy zależy tylko od nas.

Bardzo często zachodzi potrzeba przesłania parametru (np. id przedmiotu który chcemy kupić). Aby np. przekazać id=15 modyfikujemy nasz routing do postaci:
            routes.MapRoute(
                "Default", // Route name
                "Katalog/{id}", // URL with parameters
                new { controller = "Produkt", action = "ListaProduktow"}
            );

Nasz adres w przeglądarce będzie mieć postać:
/Katalog/15
a definicja akcji która obsłuży takie zapytanie:
            routes.MapRoute(
                "Default", // Route name
                "Katalog/{id}", // URL with parameters
                new { controller = "Produkt", action = "ListaProduktow", id = UrlParameter.Optional } // Parameter defaults
            );

Sama metoda kontrolera, która obsłuży ten routing będzie mieć postać:
        public ActionResult ListaProduktow(int id)
        {
           //do somethig
        }


Parametry domyślne
Korzystając z wyżej skonstruowanej trasy routingu, musimy jawnie określić id czyli wpisać w adresie przeglądarki Katalog/konkretne_id. Dla naszych potrzeb i naszych użytkowników, warto wprowadzić także możliwość wprowadzenia samego adresu Katalog który pobiera listę wszystkich produktów a nie wybranego. Aby to osiągnąć należy skorzystać z parametrów domyślnych które wprowadzamy w definicji routingu:
            routes.MapRoute(
                "Default", // Route name
                "Katalog/{id}", // URL with parameters
                new { controller = "Produkt", action = "ListaProduktow", id = UrlParameter.Optional } // Parameter defaults
            );

następnie korzystamy z parametrów domyślnych metod (nowość w .NET 4.0):
        public ActionResult ListaProduktow(int id = 0)
        {
            ViewData["id"] = id;
            return View();
        }



Ograniczenia parametrów
Można ograniczać parametry ze względu na rodzaj żądania (POST, GET) jak i typ danych przesyłanych w parametrze (np. tylko liczby). Dla przykładu wprowadzimy ograniczenie na parametr id. Będzie ono zobowiązywało id do bycia liczbami 1 do 3 cyfrowymi:
            routes.MapRoute(
                "Default", // Route name
                "Katalog/{id}", // URL with parameters
                new { controller = "Produkt", action = "ListaProduktow", id = UrlParameter.Optional },  // Parameter defaults
                new { id = @"\d{1,3}" }
            );

Można także wprowadzić ograniczenie na metodę POST, GET itd.:
            routes.MapRoute(
                "Default", // Route name
                "Katalog/{id}", // URL with parameters
                new { controller = "Produkt", action = "ListaProduktow", id = UrlParameter.Optional },  // Parameter defaults
                new { id = @"\d{1,3}", httpMethod = new HttpMethodConstraint("Post") }
            );


Nieograniczona ilość parametrów
Jeżeli potrzebujemy mieć możliwość dopasowania routingu do takiego adresu:
Kategoria/Podkategoria/Szczegolowo/Cukierki
to aby nie pisać tasiemca w naszym routingu można skorzystać z instukcji:
            routes.MapRoute(
                "Default", // Route name
                "Katalog/{*CukierkiPath}", // URL with parameters
                new { controller = "Produkt", action = "ListaProduktow"}
            );


Ignorowanie wybranych dróg
Jeżeli jest możliwość dodania nowej trasy to także istnieje możliwość wykluczenia pewnych tras z routingu. Służy do tego metoda IgnoreRoute:
           routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

W tym przypadku usuwa możliwość odwoływania się do plików axd.



Tworzenie własnej implementacji routingu
Implementacja nie jest trudna. Zaczynamy od zaimplementowania abstrakcyjnej klasy RouteBase.
Znajdują się w niej dwie metody:
GetRouteData(HttpContextBase httpContext) - w tej metodzie sprawdzamy czy przychodzący adres Url pasuje do tego, który zdefiniowaliśmy. Jeżeli tak jest zwracamy klasę implementującą interfejs IRouteHandler
GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) - służy do tworzenia adresów opartych o kontroler, akcję, parametry (w naszym przypadku zwrócimy null)

    public class MyRouting : RouteBase
    {
        private string[] urls = new string[] {
            "~Kontroler/adres", "~/Kontroler/adres2"
        };

        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            string requestUrl = httpContext.Request.AppRelativeCurrentExecutionFilePath;
            if (urls.Contains(requestUrl, StringComparer.InvariantCultureIgnoreCase))
            {
                RouteData rd = new RouteData(this, new MvcRouteHandler());
                rd.Values.Add("controller", "MyController");
                rd.Values.Add("action", "MyAction");
                rd.Values.Add("url", requestUrl);
                return rd;
            }
            return null;
        }

        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            return null;
        }
    }

Następnie tak stworzoną trasę rejestrujemy w pliku Global.asax.cs. Nie wolno zapomnieć że lista w celu dopasowania przeszukiwana jest od góry do dołu więc warto naszą trasę dodać jako pierwszą:
routes.Add(new MyRouting());

Po wpisaniu w przeglądarce adresu /Kontroler/adres lub /Kontroler/adres2 zostaniemy przekierowani do kontrolera MyController i akcji MyAction

1 komentarz: