W dzisiejszym poście będzie o tzw. Web API. Usługi REST są dzisiaj już wszechobecne ze względu na liczbę dostępnych platform oraz co za tym idzie, skalowalność. Web Api ułatwia pisanie serwisów REST. Nic nie stoi na przeszkodzie, abyśmy używali starego podejścia czyli implementacji akcji w klasycznych kontrolerach. Web Api jednak ułatwia pracę z REST oraz jest tak naprawdę rozszerzeniem MVC.
Jeśli ktoś zna ASP.NET MVC, nie będzie miał żadnego problemu z Web Api. Cała infrastruktura jest analogiczna do klasycznych aplikacji ASP.NET MVC.
Zasadnicza różnica między Web Api a ASP.NET to sposób w jaki wybierane są wywoływane metody. W Web Api akcje zwykle zwracają konkretne dane, a nie ActionResult. Akcja nie określa w jaki sposób (JSON czy XML) mają zostać zwrócone. W ASP.NET MVC często akcję REST definiowało się w następujący sposób:
public ActionResult GetData() { Person persons = _repository.GetPersons(); return Json(persons); }
Analogiczna metoda w WebApi wygląda następująco:
public Person[] GetData() { Person persons = _repository.GetPersons(); return persons; }
Jak to w usługach REST, akcje są wybieranie na podstawie nagłówka HTTP (GET, PUT, POST, DELETE).
Przejdźmy na chwilę do przykładu, zanim znów wrócimy do teorii API.
Stwórzmy nowy projekt Web API wybierając odpowiedni szablon z kreatora:
Nic nie stoi na przeszkodzie, abyśmy dodali odpowiednie kontrolery do istniejącego już projektu ASP.NET MVC – nie ma to znaczenia.
Stwórzmy również repozytorium, które będzie odpowiedzialne za podstawowe operacje:
public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } public interface IPersonRepository { void AddPerson(Person person); bool RemovePerson(int id); Person GetPersonById(int id); Person[] GetAllPersons(); } public class MemoryPersonRepository : IPersonRepository { private List<Person> _persons=new List<Person>(); public void AddPerson(Person person) { _persons.Add(person); } public bool RemovePerson(int id) { Person person = GetPersonById(id); if (person == null) return false; _persons.Remove(person); return true; } public Person GetPersonById(int id) { return _persons.FirstOrDefault(p => p.Id == id); } public Person[] GetAllPersons() { return _persons.ToArray(); } }
Powyższy kod jest oczywiście tylko dla testów i przechowuje dane w pamięci.
Kolejnym krokiem jest stworzenie specjalnego kontrolera (Empty Api Controller):
Z listy wybraliśmy API kontroler. To jest pierwsza różnica w stosunku do klasycznego ASP.NET MVC. Po wygenerowaniu klasy, zobaczymy, że nowy kontroler dziedziczy po ApiController:
public class PersonsController : ApiController { }
Przejdźmy do implementacji kontrolera, na co składają się standardowe operacje CRUD:
public class PersonsController : ApiController { private readonly IPersonRepository _personRepository; public PersonsController(IPersonRepository personsRepository) { _personRepository = personsRepository; } public void PostPerson(Person person) { _personRepository.AddPerson(person); } public void DeletePerson(int id) { _personRepository.RemovePerson(id); } public Person GetPersonById(int id) { return _personRepository.GetPersonById(id); } public Person[] GetAllPersons() { return _personRepository.GetAllPersons(); } }
Jak wyżej już wspomniałem, zwracamy generyczne dane a nie konkretny format typu JSON czy XML – framework odpowiada za sformatowanie danych.
Standardowy adres do zwrócenia konkretnej osoby wygląda następująco:
/api/persons/1
Jeśli chcemy zwrócić listę osób wtedy:
/api/persons
Proszę zwrócić uwagę, że w adresie nie ma nazwy metody. Wyłącznie posługujemy się nazwą zasobu. Konkretna akcja, która zostanie wywołana zależy od metody HTTP. Wpisując w przeglądarkę /api/persons, zostanie użyta metoda HTTP GET dlatego zmapowane będzie to do metody GetAllPersons. Analogicznie /api/persons/1 – wywołanie HTTP GET zawęża akceptowalne metody do GetPersonById oraz GetAllPersons. Ze względu na to, że w adresie występuje parametr (id), zostanie wywoła akcja GetPersonById.
Drugą drobną różnicą w domyślnym mapowaniu jest “api” poprzedzające nazwę zasobu.
Przetestujmy więc zwracanie danych w przeglądarce:
Chrome zwróci dane w formacie XML:
<ArrayOfPerson xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MvcApplication7.Controllers"> <Person> <FirstName>Piotra</FirstName> <Id>0</Id> <LastName>Zielinski</LastName> </Person> <Person> </ArrayOfPerson>
Dlaczego XML a nie JSON? Zajrzyjmy do wysłanego pakietu:
Request URL:http://localhost:40521/api/persons
Request Method:GET
Request Headersview source
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Widzimy, że w sekcji Accept mamy dokument XML. Web API rozpoznaje podany nagłówek i na podstawie tego zwraca dane w najlepiej dopasowanym formacie – w tym przypadku jest to dokument XML. Wywołując ten sam link w IE zobaczymy inny nagłówek accept, a co za tym idzie wynik będzie również inny (JSON):
[{"Id":0,"FirstName":"Piotra","LastName":"Zielinski"},{"Id":0,"FirstName":"Piotr","LastName":"Zielinski"},{"Id":0,"FirstName":"Piotr","LastName":"Zielinski"},{"Id":0,"FirstName":"Piotr","LastName":"Zielinski"}]
W celu przetestowania dodawania danych, musimy wysłać HTTP POST. Stwórzmy zatem prosty formularz:
@using (Ajax.BeginForm(new AjaxOptions() { Url = Url.HttpRouteUrl("DefaultApi", new { controller = "Persons" }) })) { <p><label>Imie:</label>@Html.Editor("FirstName")</p> <p><label>Nazwisko:</label>@Html.Editor("LastName")</p> <button type="submit">Dodaj</button> }
Formularz wyśle następujący nagłówek:
Request URL:http://localhost:40521/api/Persons
Request Method:POST
Spowoduje to, że akcja PostPerson zostanie wywołana, na podstawie nagłówka HTTP. Jeśli nazwalibyśmy akcje AddPerson albo InsertPerson nie została ona by dopasowana. Standardowe nazewnictwo i mapowanie wygląda następująco:
HTTP | Link | Akcja |
GET | /api/persons | GetAllPersons |
GET | /api/persons/1 | GetPersonById |
POST | /api/persons | PostPerson |
PUT | /api/persons | PutPerson |
DELETE | /api/persons/1 | DeletePerson |
Nazywanie metod PutPerson albo PostPerson nie jest zbyt naturalne dlatego warto skorzystać z następujących atrybutów:
[HttpPost] public void AddPerson(Person person) { _personRepository.AddPerson(person); } [HttpPut] public void UpdatePerson(Person person) { _personRepository.AddPerson(person); }
Na wpis wprowadzający myślę, że wystarczy. W kolejnych postach więcej o WebApi.
Niestety ale podany przykład nie działa zbyt dobrze. Wszystko przez dość spory skrót myślowy. Przepisując niemal 1:1 przykład trafiam na problem z brakiem konstruktora bezparametrowego. Fajnie jakby tego typu przykłady były opisywane od A do Z.
Też mam takie same zdanie, jak Paweł. Niestety dużo artykułów, ale nie są jakby dokończone, brakuje zawsze szczegółów i szczególików.
ee tam. Paweł nie przesadzaj 🙂 Fajnie wyszło
Ogólnie spoko tylko szkoda że wywołanie PUT zrobiło format C i bluescreen. Poza tym ok!