WebApiConfigCzęść NR.3 W poprzednim wpisie stworzyliśmy proste Web API. Z niego  możemy pobrać listę gier. Przykład z poprzedniego wpisu jest bardzo prosty,

Tak bardzo prosty, że nie poruszył kwestii konfiguracji API.

W tym wpisie nadpiszemy domyślne zachowanie frameworku ASP.NET WEB API. Zmienimy także ustawienia API w taki sposób, aby zwracała ona dane w formacie JSON dla każdej przeglądarki.

Domyślna konwencja, która została ukazana w poprzednim wpisie wymusza na nas stworzenie metod zgodnie z akcjami HTTP. Dla przypomnienia oto metody GET i POST, które są mapowane na funkcje wywołania HTTP.

// GET api/Games/12345
public Game Get(int id)
{
    return list.First(e => e.Id == id);
}

// POST api/Games
public void Post(Game Game)
{
    int maxId = list.Max(e => e.Id);
    Game.Id = maxId + 1;
    list.Add(Game);
}

Alternatywnie możemy nazwać metody trochę inaczej.

// GET api/Games/12345
public Game GetGame(int id)
{
    return list.First(e => e.Id == id);
}

// POST api/Games
public void PostGame(Game Game)
{
    int maxId = list.Max(e => e.Id);
    Game.Id = maxId + 1;
    list.Add(Game);
}

Nie zmienia to faktu, że jesteśmy zmuszeni dodać przed nazwą metody nazwę akcji HTTP.

Oczywiście, dopóki nie użyjemy atrybutów.

// GET api/Games
[HttpGet]
public IEnumerable<Game> AllGames()
{
    return list;
}
// GET api/Games/12345
[HttpGet]
public Game RetriveGame(int id)
{
    return list.First(e => e.Id == id);
}

// POST api/Games
[HttpPost]
public void AddGame(Game Game)
{
    int maxId = list.Max(e => e.Id);
    Game.Id = maxId + 1;
    list.Add(Game);
}
// PUT api/Games/12345
[HttpPut]
public void UpdateGame(int id, Game Game)
{
    int index = list.ToList().FindIndex(e => e.Id == id);
    list[index] = Game;
}
// DELETE api/Games/12345
[HttpDelete]
public void DestroyGame(int id)
{
    Game Game = RetriveGame(id);
    list.Remove(Game);
}

Używając atrybutów jesteśmy w stanie określić funkcję naszych metod niezależnie od tego, jak je nazwiemy.

Web API działa tak samo jak wcześniej.

Jest możliwe nadanie wielu atrybutów dla jednej metody.  Nie moglibyśmy tego osiągnąć używając domyślnej  konwencji nazewniczej.

// GET api/Games/12345
[HttpGet]
[HttpPost]
public Game RetriveGame(int id)
{
    return list.First(e => e.Id == id);
}

Alternatywnie w takim wypadku możemy użyć atrybutu „AcceptVerbs”, który przyjmuje listę metod HTTP w postaci napisu string.

// GET api/Games
[AcceptVerbs("GET","POST")]
public IEnumerable<Game> AllGames()
{
    return list;
}

Jak ścieżki URL są przypisywane

To jeszcze nie koniec. Nasze API wciąż zwraca dane w formacie XML z jakiegoś powodu.

Teraz gdy omówiliśmy jak metody działają w zależności od akcji HTTP. Trzeba postawić kolejne pytanie

Jak działa mechanizm mapowania ścieżek URL?.

Skąd aplikacja wie, jak ma ułożyć ścieżkę URL do naszej metody w Web API znajdującej się w kontrolerze.

W pliku global.asax możesz zauważyć rejestracje klasy „WebApiConfig”.

Klasa „WebApiConfig” konfiguruje ścieżki URL. Znajduje się ona w folderze „App_Start”.

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);
    }
}

Jak widzisz domyślnie ścieżki są tworzone na podstawie nazwy kontrolera.

Nasz kontroler nazywa się „GameController”.

Dlatego ścieżka do metod API wygląda tak „api/Game/”

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

Parametr ID dodawany do adresu URL jest  parametrem opcjonalnym, w zależności od tego, czy wystąpi  w metodzie.

Aby to potwierdzić dodamy kolejny parametr opcjonalny „myId”.

config.Routes.MapHttpRoute(
    name: "DefaultApi",
           
    routeTemplate: "api/{myId}/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional, 
        myId = RouteParameter.Optional}
);

Do kontrolera dodałem dwie metody, które mogą być użyte dzięki temu, że zmodyfikowaliśmy domyślny wzór adresowania.

// GET api/1234/Games
[AcceptVerbs("GET", "POST")]
public Game RetriveGame2(int myId)
{
    return list.First(e => e.Id == myId);
}

// GET api/1234/Games/1234
[AcceptVerbs("GET", "POST")]
public Game RetriveGame3(int myId,int id)
{
    return list.First(e => e.Id == myId);
}

Oba adresu wywołają nowe metody.

http://localhost:12932/api/1234/games

http://localhost:12932/api/1234/games/1234

Jeśli więc potrzebujesz kolejnego parametru do wywołania GET. Oto co musisz zrobić.

config.Routes.MapHttpRoute(
    name: "DefaultApi",
           
    routeTemplate: "api/{myId}/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional, 
        myId = RouteParameter.Optional},
   constraints: new {myId = @"\d+"}
);

Do ścieżek możemy jeszcze dodać „constraints”, czyli ograniczenia. Powyższe ograniczenia informują WEB API, że parametr „myId” musi być liczbą. Bez „ograniczenia”  jest możliwe wywołanie takiej funkcji.

http://localhost:12932/api/zzz/games/

”zzz” nie może być skonwertowane na liczbę, więc WEB API zwróci wyjątek.

Wyjątek XML

Ograniczenie informuje usługę, że myId musi być liczbą. Poprzedni adres zostanie potraktowany jako nieprawidłowy i żadna metoda w WEB API się nie uruchomi.

404 Error

Po  zmodyfikowaniu schematu ścieżki na „api/{myId}/{controller}/{{id}” sprawiliśmy, że metody działające na ścieżce  „api/{controller}/{{id}” przestały działać.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",

            routeTemplate: "api/{controller}/{id}/{myId}",
            defaults: new
            {
                id = RouteParameter.Optional,
                myId = RouteParameter.Optional
            },
            constraints: new { myId = @"\d+" }
        );

        config.Routes.MapHttpRoute(
        name: "DefaultApi2",

        routeTemplate: "api/{controller}/{id}",
        defaults: new
        {
            id = RouteParameter.Optional,
        }
        );

        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
            new System.Net.Http.Headers.MediaTypeHeaderValue("text/html"));
    }
}

Ten problem można rozwiązać poprzez dodanie kolejnego schematu mapowania.  Teraz gra może być pobrana z dwóch adresów:

http://localhost:12932/api/games/1234

http://localhost:12932/api/games/1234/1234

Warto trzymać się konwencji „api/controler/[nasze mechanizmy].

Pamiętaj, że metody API nie powinny mieć zbyt wielu parametrów. Jeśli metoda musi przyjąć dużą ilość parametrów lub złożony obiekt. Zastosuj najlepiej wywołanie POST.

Co, jeśli nasz kontroler ma dużo więcej metod zwracających. Przykładowo takich:

// GET api/Games/12345
[AcceptVerbs("GET", "POST")]
public Game RetriveGame(int id)
{
    return list.First(e => e.Id == id);
}

// GET api/Games/12345
[AcceptVerbs("GET", "POST")]
public Game RetriveGame3(int id)
{
    return list.First();
}

WEB API zwróci wyjątek, ponieważ do dwóch adresów ma przypisane dwie metody.

image

Problem taki powinien rzadko się pojawić, gdyż zgodnie z dobrymi praktykami jeden kontroler powinien posiadać tylko metody do jednej konkretnej encji. Czyli nie powinieneś posiadać wielu metod zwracających, aktualizujących, kasujących i umieszczających. Przykładowo nie powinieneś posiadać wielu metod, które swoją nazwą określają inny styl filtrowania. Od tego są parametry metody.

Jedyny słuszny powód zaistniałej, takiej sytuacji to bardziej złożone procedury, które wykonują dużo więcej niż pospolite operacje.

Oto jak można wzbogacić schemat ścieżki URL poprzez nazwy metody, które chcemy wywołać. {action}

config.Routes.MapHttpRoute(
    name: "RPC",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new
    {id = RouteParameter.Optional,}
);

Mogę teraz odwołać się do konkretnych metod w swoim API.

WEB/API

Używając atrybutu „ActionName”  możesz nadać inną nazwę metody, która będzie w adresie URL a będzie różny od tej w kodzie w C#.

[AcceptVerbs("GET", "POST")]
[ActionName("DajMIGre")]
public Game RetriveGame(int id)
{
    return list.First(e => e.Id == id);
}

Problem ze zwrotem JSON

W poprzednim wpisie ku mojemu zdziwieniu zauważyłem, że przeglądarka Chrome i Safari zwróciła mi encje gry w formacie XML.

Rzeczywiście domyślnie WEB API zwraca format JSON w innych przeglądarkach i aplikacjach.

Problem można rozwiązać dodając następujący kod do metody „Register” klasy „WebApiConfig”.

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
    new System.Net.Http.Headers.MediaTypeHeaderValue("text/html"));

Kod dodaje nagłówek HTTP wsparcia zawartości tekstowej. Inne wpisy z tego cyklu.

Edit z 2022 roku : Kod projektu można pobrać tutaj : PanNiebieski/GameWebApiForBlogPost (github.com)