Uwierzytelnianie i routing

Transkrypt

Uwierzytelnianie i routing
ASP.NET
MVC
6
Uwierzytelnianie i routing
1
Zaawansowane programowanie internetowe
Instrukcja nr 6
1. Cel zajęć
Celem zajęć jest zapoznanie się z metodami uwierzytelniania użytkownika aplikacji oraz
z mechanizmami routingu w ASP.NET MVC.
2. Odrobina teorii na temat routingu
Routing jest mechanizmem, który pozwala na zamianę przyjaznych dla użytkownika adresów
internetowych (np. http://localhost:3456/home/details/3) na coś co może zrozumieć serwer
i aplikacja internetowa. Mechanizm ten jest częścią składową platformy .NET 4.0 dlatego
dostępny jest dla wszystkich aplikacji ASP.NET:
• w przypadku klasycznego ASP.NET adresy przyjazne użytkownikowi zamieniane są na
adres strony internetowej
• w przypadku ASP.NET MVC adresy przyjazne użytkownikowi zamieniane są na
strukturę określającą jaki kontroler i która jego akcja powinna być wywołana.
Reguły routingu są definiowane w pliku Global.asax. Znajduje się tam metoda RegisterRoutes,
która domyślnie ma postać:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index",
id = UrlParameter.Optional } // Parameter defaults
);
}
W metodzie tej wywoływane są kolejno dwie metody: IgnoreRoute i MapRoute. Pierwsza
z nich służy do zdefiniowania tras, które mają być ignorowane (tzn. użytkownik nie będzie
mógł się dostać do zasobów, na które wskazują). Druga z nich służy do definiowania nowej
trasy (i generalnie ta metoda będzie nas najbardziej interesowała).
Metoda MapRoute przyjmuje następujące parametry:
1. nazwa trasy (np.: „Default”),
2. wzorzec URL (np.: „{controller}/{action}/{id}”), który definiuje jak trasa ma wyglądać,
3. domyślne parametry trasy,
4. ograniczenia jakie mogą być narzucone na trasę.
Wzorzec URL służy do zdefiniowania wyglądu trasy, czyli adresu strony, który musi podać
użytkownik np. w pasku adresu przeglądarki. Wzorzec trasy zbudowany jest z segmentów,
które rozdzielane są znakami slash. Każdy segment może składać się ze stałej znakowej lub
zmiennej (przy czym zmienne muszą być ujęte w nawiasy klamrowe). Przykładowe wzorce
przedstawione są poniżej:
2
Zaawansowane programowanie internetowe
Instrukcja nr 6
/Produkty/{kategoria_produktow}
/Faktury/{Rok}/{Miesiac}/{Dzien}
/Artykuly/{Rok}-{Miesiac}-{Dzien}
Następnym parametrem są wartości domyślne dla zmiennych trasy. W domyślnej metodzie
wyglądają one następująco:
new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
W tym przypadku jeżeli użytkownik nie poda wartości dla zmiennej „controller” lub „action”,
które występują we wzorcu podstawione zostaną wartości domyślne, które zdefiniowane są
w tym parametrze. Na szczególną uwagę zasługuje zmienna id do której przypisana jest
domyślna wartość UrlParameter.Optional, która oznacza, że zmienna ta może, ale nie musi
wystąpić w trasie. Jeżeli wystąpi to wartość ta zostanie podstawiona do odpowiedniej
zmiennej, jeżeli nie to żadna domyślna wartość nie będzie podstawiona).
UWAGA!!! W przypadku ASP.NET MVC we wzorcu URL powinny wystąpić zmienne controller
i action, które określają nazwę kontrolera i nazwę akcji, która ma być wywołana, jeżeli nie
wystąpią muszą być do nich przypisane wartości domyślne, inaczej ASP.NET MVC nie będzie
mógł albo określić kontrolera, albo akcji do której powinien przekazać sterowanie.
Ostatnim parametrem, który może przyjmować metoda MapRoute są ograniczenia, które
mogą być nałożone na trasę. Ograniczenia mogą być nałożone albo na zmienne, które
występują we wzorcu URL, albo na metodę przesłania danych. Definiowane są albo jako
wyrażenie regularne, albo jako obiekt, które implementuje interfejs IRouteConstraint.
Przykładowe trasy z ograniczeniami:
routes.MapRoute(
"ListaOsob",
"Osoby",
new { controller = "Home", action = "Lista",
id = UrlParameter.Optional },
new { httpMethod = new HttpMethodConstraint("Get") }
);
routes.MapRoute(
"EdycjaOsoby",
"Edytuj/{id}",
new { controller = "Home", action = "Edycja",
id = UrlParameter.Optional },
new { id = @"\d+" }
);
Pierwsza z tych tras będzie zastosowana tylko w przypadku, gdy żądanie zostanie przesłane
metodą GET, z kolei druga, gdy parametr id będzie liczbą całkowitą.
3
Zaawansowane programowanie internetowe
Instrukcja nr 6
Jeżeli powyższe trasy zostały by dołączone do metody RegisterRoutes, wyglądała by ona
następująco:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"ListaOsob",
"Osoby",
new { controller = "Home", action = "Lista",
id = UrlParameter.Optional },
new { httpMethod = new HttpMethodConstraint("Get") }
);
routes.MapRoute(
"EdycjaOsoby",
"Edytuj/{id}",
new { controller = "Home", action = "Edycja",
id = UrlParameter.Optional },
new { id = @"\d+" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index",
id = UrlParameter.Optional } // Parameter defaults
);
}
Obecnie metoda ta zawiera definicję trzech tras więc rodzi się pytanie w jakiej kolejności będą
one dopasowywane do żądania. Otóż trasy będą dopasowywane w kolejności wystąpienia
w metodzie RegisterRoutes. Wybrana zostanie pierwsza pasująca trasa, co oznacza, że należy
umieszczać je w kolejności od najbardziej szczegółowych do najbardziej ogólnych.
3. Zadanie I
Proszę przebudować aplikację, która była utworzona na poprzednich zajęciach, aby
umożliwiała uwierzytelnianie użytkownika i dostęp do edycji danych tylko dla określonych
użytkowników.
Przyjmijmy następujące założenia:
1. Kontroler Home jest ogólnodostępny (również dla użytkowników niezalogowanych)
2. Wyświetlanie danych o przechowywanych w bazie kontaktach jest możliwe tylko dla
zalogowanych użytkowników
4
Zaawansowane programowanie internetowe
Instrukcja nr 6
3. Dodawanie i edycja danych jest możliwe tylko dla użytkowników z grupy „Edytorzy”
i „Administratorzy”
4. Usuwanie danych jest możliwe tylko dla użytkowników z grupy „Administratorzy”
W tym celu należy wykonać odpowiednie kroki:
1. Otwórzmy projekt z poprzednich zajęć.
2. Proszę otworzyć stronę ASP.NET Configuration.
3. Następnie dodajmy trzech użytkowników o dowolnych loginach oraz dwie grupy
„Edytorzy” i „Administratorzy”. Pierwszego użytkownika nie przypisujmy do żadnej z
grup, drugiego przypiszmy do grupy Edytorzy, a trzeciego do grupy Administratorzy.
4. Przejdźmy do spełnienia określonych wyżej założeń:
• Ponieważ kontroler Home i jego akcji obecnie są ogólnodostępne
wymaganie 1. jest od razu spełnione.
• W celu spełnienia wymagania drugiego tzn. aby kontroler Osoba był
dostępny tylko dla użytkowników zalogowanych oznaczmy ten kontroler
atrybutem Authorize:
[Authorize]
public class OsobaController : Controller
{
…
•
W celu spełnienia wymagań trzeciego i czwartego, konieczne jest oznaczenie
metod Create oraz Edit atrybutem Authorize, który będzie określał
dodatkowo, które grupy użytkowników mają prawo wywołać określoną akcję
np.:
[Authorize(Roles="Edytorzy,Administratorzy")]
public ActionResult Create()
{
return View();
}
4. Wykorzystanie mechanizmu ASP.NET Routing
Przyjmijmy następujące założenia:
1. Aplikacja powinna umożliwiać dostęp do swoich tak jak dotychczas tzn:
użytkownik powinien mieć dostęp do poszczególnych akcji systemu gdy
w przeglądarce poda adres wg schematu:
{domena}/{kontroler}/{akcja}/{id}
2. Dodatkowo, wymagane jest aby użytkownik miał dostęp do odpowiedniej
funkcjonalności systemu jeżeli poda, którąś z poniżej wymienionych tras:
5
Zaawansowane programowanie internetowe
Instrukcja nr 6
a) {nazwa_serwera}/Kontakty
Wyświetlenie wszystkich kontaków (to samo co:
{nazwa_serwera}/Osoba/ )
b) {nazwa_serwera}/NowyKontakt
Wyświetlenie formularza dodawania nowej
{nazwa_serwera}/Osoba/Create )
osoby
(to
samo
co:
c) {nazwa_serwera}/Kontakty/{Nazwisko}/{akcja}
Jeżeli w bazie występuje tylko jedna osoba o danym nazwisku aplikacja
powinna wyświetlić okno odpowiednie dla wybranej akcji. W przeciwnym
wypadku powinna się wyświetlić lista osób o tym nazwisku w celu
uszczegółowienia wyboru.
d) {nazwa_serwera}/Kontakty/{Imie}_{Nazwisko}/{akcja}
Jeżeli w bazie występuje tylko jedna osoba o danym imieniu i nazwisku
aplikacja powinna wyświetlić okno odpowiednie dla wybranej akcji.
W przeciwnym wypadku powinna się wyświetlić lista osób o tym nazwisku
w celu uszczegółowienia wyboru.
I. Spełnienie wymagań a) i b)
1.
2.
Proszę otworzyć okno Solution Explorer i dwukrotnie kliknąć na pliku global.asax
Proszę odnaleźć metodę RegisterRoutes i zmodyfikować ją tak aby uzyskać
następujący kod:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"NowyKontakt"
, "NowyKontakt"
, new { controller="Osoba", action = "create"}
);
routes.MapRoute(
"Kontakty"
, "Kontakty"
, new { controller = "Osoba", action = "index"}
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
6
Zaawansowane programowanie internetowe
Instrukcja nr 6
Powyższy kod dodaje dwie nowe trasy, dla których przypisano „na sztywno”
odpowiednie kontrolery i ich akcje.
3.
Proszę przetestować nowe trasy
II. Spełnienie wymagania c)
W przypadku poprzednich tras nakład pracy był bardzo niewielki, gdyż
wykorzystywaliśmy istniejące już akcje kontrolera Osoba. Wymaganie c) określa, osoby
w bazie mają być wyszukiwane po nazwisku. Dodatkowo, jeżeli w bazie danych
zapisana jest tylko jedna osoba o danym nazwisku to od aplikacja od razu ma przejść
do wyświetlania formularza odpowiedniego dla wybranej akcji, natomiast, gdy takich
osób będzie więcej najpierw ma być wyświetlona ich lista, z której użytkownik
wybierze odpowiedni wpis.
Np. wydając polecenie: {nazwa_serwera}/Kontakty/Nowak/edit, to:
a) jeżeli w bazie danych znajdują się informacje tylko o jednej osobie o nazwisku
Nowak to od razu ma być wyświetlony formularz edycji danych tej osoby.
b) jeżeli w bazie danych znajdują się informacje o więcej niż jednej osobie
o nazwisku Nowak to najpierw powinna się wyświetlić lista tych osób,
z której dopiero użytkownik wybierze odpowiedni wpis do edycji.
W celu realizacji tego zadania wymagane będą następujące kroki:
1.
Dodanie nowej metody do interfejsu IOsobaUslugi, która będzie zwracać listę osób
o podanym nazwisku. Metoda ta może mieć następującą deklarację:
List<OsobaModel> ZwrocOsobyWgNazwiska(string nazwisko);
2.
Dodanie nowej metody do klasy OsobaUslugi w celu spełnienia wymagań
zmodyfikowanego interfejsu.
3.
Dodanie do kontrolera dodatkowych akcji: DetailsByName, EditByName,
DeleteByName, które będą realizowały wymaganie c) dla odpowiednich akcji.
Z punktu widzenia języka C# możliwe jest stworzenie tych metod o takiej samej
nazwie jak już istniejące tj. Details, Edit, Delete, różniących się listą parametrów.
Mechanizm ten nie może być jednak wykorzystany w tym przypadku, ponieważ
domyślny sposób wybierania akcji do wykonania w ASP.NET MVC nie jest
w stanie rozróżnić takich metod.
Przykładowo akcja DetailsByName może mieć następujący kod:
public ActionResult DetailsByName(string nazwisko)
{
List<OsobaModel> dane =
_osoby.ZwrocOsobyWgNazwiska(nazwisko);
if (dane.Count == 1)
7
Zaawansowane programowanie internetowe
Instrukcja nr 6
return Details(dane[0].ID);
else
return View("Index", dane);
}
Pobranie danych z bazy
Sprawdzenie liczby zwróconych obiektów
Jeżeli liczba ta jest równa jeden to wywołujemy metodę Details podając jako
parametr identyfikator wybranej osoby.
W przeciwnym przypadku tworzymy widok “Index” i przekazujemy do niego
listę odczytanych obiektów.
4.
Obecnie, gdy mamy już wszystkie wymagane metody należy wskazać ASP.NET
MVC kiedy ma je wywołać. W tym celu dodajmy nową trasę do metody
RegisterRoutes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"WgNazwiska"
, "Kontakty/{nazwisko}/{action}"
, new { controller = "Osoba", action = "DetailsByName" }
);
routes.MapRoute(
"NowyKontakt"
, "NowyKontakt"
, new { controller = "Osoba", action = "create" }
);
…
}
5.
6.
7.
Proszę uruchomić aplikację.
Proszę przetestować powyższą akcję wpisując w przeglądarce np.:
http://localhost:50265/Kontakty/Nowak/DetailsByName/
oczywiście port oraz nazwisko osoby należy dostosować do swojej aplikacji.
Jeżeli w bazie danych jest więcej niż jedna osoba o podanym nazwisku wszystko
powinno działać poprawnie. Natomiast, gdy w bazie jest tylko jedna osoba
o podanym nazwisko to powinniśmy otrzymać błąd: „The view 'detailsbyname' or
its master was not found.”
Jak łatwo zauważyć pomimo wywołania metody Details ASP.NET MVC dalej
próbuje znaleźć widok dla oryginalnej akcji (czyli: DetailsByName). Aby wskazać
konkretny widok, który nas interesuje należy lekko zmodyfikować metodę Details:
public ActionResult Details(int id)
{
OsobaModel osoba = _osoby.ZwrocOsobyWgID(id);
8
Zaawansowane programowanie internetowe
Instrukcja nr 6
List<TelefonModel> numeryTelefonow =
_telefony.PobierzTelefonyDlaOsoby(id);
return View("Details",
new SzczegolyOsoby {
Osoba = osoba,
NumeryTelefonow = numeryTelefonow }
);
}
Obecnie metoda ta jawnie wskazuje, który widok ma być wczytany, co rozwiązało
nasz problem.
8. Podobnie do metody DetailsByName należy stworzyć również metody EditByName
i DeleteByName.
9.
Powyższe rozwiązanie tworzy jednak kilka nowych problemów:
a.
b.
c.
Użytkownik musi pamiętać o podaniu nazwy akcji z końcówką ByName,
Domyślnie metoda BeginForm wykorzystywana w widokach Edit.aspx,
Create.aspx, Delete.aspx, generuje formularz, który wysyłany jest do tej samej
akcji, która go wygenerowała. Co oznacza, że Framework po zatwierdzeniu
formularza będzie szukał metod EditByName, DeleteByName (ale w naszym
projekcie metody te generują formularz, więc się zapętlamy).
W projektach wykonywanych na poprzednich zajęciach formularz edycji
danych wywoływany był za pomocą polecenia np:
http://localhost:50265/Osoba/Edit/5
czyli identyfikator rekordu, który chcieliśmy zmienić był zapisany w adresie.
Obecnie tego identyfikatora nie ma więc aplikacja nie będzie wiedziała, który
rekord chcemy zmodyfikować.
Najprostsze jest rozwiązanie ostatniego problemu, gdyż wystarczy trochę
zmodyfikować widok Edit.aspx dołączając do niego kod, który będzie generował
ukryte pole formularza dla właściwości ID:
…
<fieldset>
<legend>Fields</legend>
<%:Html.HiddenFor(model=>model.ID) %>
<div class="editor-label">
<%: Html.LabelFor(model => model.Imie) %>
</div>
…
W celu rozwiązania problemów a) i b) należy stworzyć klasę, która będzie
modyfikowała nazwę akcji przed jej wywołaniem (uwaga w pliku zawierającym tę
klasę należy dołączyć przestrzeń nazw System.Web.Routing):
9
Zaawansowane programowanie internetowe
Instrukcja nr 6
public class ChangeActionNameHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(
RequestContext requestContext) {
var routeData = requestContext.RouteData;
var request = requestContext.HttpContext.Request;
if (request.HttpMethod == "GET”)
routeData.Values[“action”] += "ByName"
return new MvcHandler(requestContext);
}
}
Zadaniem tej klasy jest zmodyfikowanie nazwy akcji poprzez dodanie do niej
przyrostka „ByName”, co pozwala na podawanie użytkownikowi nazw akcji tj.
„Edit”, „Details”, „Delete”. Zmiana ta powinna jednak wystąpić tylko, gdy żądanie
jest wysłane metodą GET.
Kiedy nasza klasa jest już gotowa należy poinformować ASP.NET MVC aby z niej
korzystała w tym celu zmodyfikujmy definicję naszej trasy z:
routes.MapRoute(
"WgNazwiska"
, "Kontakty/{nazwisko}/{action}"
, new { controller = "Osoba", action = "DetailsByName" }
);
Na:
routes.Add("Nazwisko",
new Route(
"Kontakty/{lastName}/{action}"
, new RouteValueDictionary(new { controller = "Osoba",
action = "Details" })
, new ChangeActionNameHandler()
)
);
Po wprowadzeniu powyższych poprawek, nasza aplikacja powinna działać już
prawie poprawnie. Prawie, gdyż została do rozwiązania jeszcze jedna kwestia –
akcja Edit oznaczona atrybutem [HttpPost] przekierowuje nas do akcji Index, lecz
nasza klasa zmieni jej nazwę na IndexByName, której nie mamy w naszym
kontrolerze. Najprostszym rozwiązaniem tej kwestii jest zmodyfikowanie klasy
ChangeNameActionHandler:
10
Zaawansowane programowanie internetowe
Instrukcja nr 6
public class ChangeActionNameHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(
RequestContext requestContext)
{
var routeData = requestContext.RouteData;
var request = requestContext.HttpContext.Request;
if (routeData.Values[“action”].toLower() == "index”)
routeData.Values[“action”] = "Details"
if (request.HttpMethod == "GET”)
routeData.Values[“action”] += "ByName"
return new MvcHandler(requestContext);
}
}
5.
Zadania do samodzielnego wykonania
1.
2.
3.
Proszę napisać program rozwiązujący wymaganie d)
Proszę rozbudować program tak aby była możliwość korzystania z komend
w języku polskim np.:
{nazwa_serwera}/Kontakty/Nowak/edytuj
{nazwa_serwera}/Kontakty/Nowak/usun
Proszę napisać ograniczenie, które pozwoli wykorzystać nowe trasy tylko
w parzyste dni miesiąca.
11
Zaawansowane programowanie internetowe
Instrukcja nr 6

Podobne dokumenty