Paweł Łukasiewicz
2019-03-05
Paweł Łukasiewicz
2019-03-05
Wprowadzenie
Na bazie prostego API omówimy problem na który możemy natrafić pracując nad aplikacją biznesową, która udostępnia niezliczoną liczbę metod różnym klientom. Załóżmy, że musimy zwrócić niestandardową wiadomość, która nie została zdefiniowana w naszym systemie. W takim wypadku po wprowadzeniu takiej wiadomości musimy dokonać kolejnej publikacji naszego API - dokonaliśmy modyfikacji kodu naszego projeku. Rozwiązaniem jest jednak wczytywanie tych wiadomości z poziomu pliku konfiguracyjnego.
W ramach artykułu przygotujemy odpowiednio plik konfiguracyjny, dokonamy załadowania w taki sposób, iż każda zmiana tekstu wymusi automatyczne przeładowanie konfiguracji. Przejdziemy przez ten proces krok po kroku.
WebAPI
W pierwszej kolejności tworzymy nowy projekt korzystając z szablonu API:
Następnie przygotujemy odpowiedni model, który będzie przechowywał interesujące nas właściwości: Code oraz Message:
namespace ResponseFromConfigAPI.Models
{
public class ApiResponse
{
public int Code { get; set; }
public string Message { get; set; }
}
}
Równie ważny jest plik responsemessages.json, w którym będziemy przechowywać wszystkie możliwe wiadomości zwracane przez nasze API:
{
"ResponseMessage": {
"Values": {
"200": "Operacja zakończona sukcesem!",
"404": "Żądanie niepoprawne!"
}
}
}
W kolejnym kroku tworzymy klasę, która będzie pasowała do naszej konfiguracji. W pliku konfiguracyjnym korzystamy z pary: klucz – wartość, dlatego w naszym przypadku musimy skorzystać ze słownika: Dictionary:
namespace ResponseFromConfigAPI.Models
{
public class ResponseMessageModel
{
public Dictionary<string, string> Values
{
get;
set;
}
}
}
Dodatkowo musimy stworzyć klasę przechowującą typ wyliczeniowy. Będziemy jej używać do przekazania klucza i zwrócenia odpowiedniej wiadomości konfiguracyjnej:
namespace ResponseFromConfigAPI.Common
{
public class Enums
{
}
public enum MessageStatus
{
Sucess = 200,
NotFound = 404
}
}
Szkielet naszegp projektu został przygotowany, jesteśmy gotowi na kolejny krok, konfigurację.
Konfiguracja
Ten etap wykonamy w specjalnie przygotowanym do tego pliku: Startup.cs. W pierwszym kroku dodamy do konstruktora klasy startowej plik json, oraz ustawimy parametr reloadOnChange na true. Dzięki temu każda zmiana wiadomości spowoduje przeładowanie konfiguracji:
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("responsemessage.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
configuration = builder.Build();
Configuration = configuration;
}
Stworzyliśmy nasza własną konfigurację. Nie wyjaśniłem parametru optional - definuje on czy plik jest wymagany. W przypadku naszego pliku jest ustawiony na false ponieważ potrzebujemy tego pliku do poprawnego działania naszej usługi internetowej.
Kolejnym ważnym krokim jest skorzystanie z zalet Dependency Injection. Dodajemy klasę ResponseMessageModel do konfiguracji usług – będziemy mogli ją wstrzykiwać za każdym razem, kiedy będzie nam potrzebna. Dodatkowo, pobierzemy dane z konfiugracji i powiążemy je ze słownikiem:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton(Configuration.GetSection("ResponseMessage").Get<ResponseMessageModel>());
services.Configure<ResponseMessageModel>((setting) =>
{
Configuration.GetSection("ResponseMessage").Bind(setting);
});
}
Pozostałe metody występujące w klasie startowej nie zostały zmodyfikowane dlatego nie wklejam całego kodu Startup.cs.
Implementacja
Wstrzykujemy klasę ResponseMessageModel w kostruktorze kontrolera ValuesController (zdefiniowanego domyślnie). Używamy do tego interfejsu IOptionsSnapshot, który pozwala nam na dostęp do treści naszego komunikatu przez cały okres trwania żądania:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly ResponseMessageModel _responseMessage;
public ValuesController(IOptionsSnapshot<ResponseMessageModel> responseMessage)
{
_responseMessage = responseMessage.Value;
}
...
...
...
}
Z kolei w metodzie GET pobieramy wiadomość z instacji ResponseMessageModel przez przekazanie odpowiedniej wartości wyliczeniowej jako parametru:
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
ApiResponse apiResponse = new ApiResponse();
apiResponse.Code = Ok().StatusCode;
apiResponse.Message = _responseMessage.Values[Ok().StatusCode.ToString()];
return new string[] { apiResponse.Code.ToString(), apiResponse.Message};
}
Testy
Po urchomieniu aplikacji możecie zobaczyć, że wszystko działa poprawnie:
Na początku artykułu powiedziałem, że każda zmiana naszej wiadomości spowoduje odswieżenie konfiguracji projektu. Dokonajmy zatem prostej modyfikacji w pliku responsemessages.json:
{
"ResponseMessage": {
"Values": {
"200": "Operacja zakończona sukcesem! (live update)",
"404": "Żądanie nie poprawne!"
}
}
}
Nasza usługa internetowa ciągle działa, dokonaliśmy zmiany w pliku a po ponowym wywołaniu metody z naszego API widzimy zmienioną treść komunikatu.
Podsumowanie
Powyższy przykład miał na celu pokazanie korzyści płynących z zastosowania mechanizmu konfiguracji służącego (w tym przykładzie) do zwracania różnych komunikatów z naszego API. Warto również mieć na uwadze, że ASP.NET Core posiada wbudowane metody dla najpopularniejszych komunikatów:
- Ok() - zwraca status 200;
- Created() - zwraca status 201;
- NoContent() - zwraca status 204;
- BadRequest() - zwraca status 400;
- Unauthorized() - zwraca status 401;
- Forbid() - zwraca status 403;
- NotFound() - zwraca status 404;
Jeżeli ta lista nie jest dla Was wystarczająca możecie skorzystać z klasy StatusCodes zawierającej pełną listę statusów zdefiniowanych dla HTTP: