ASP.NET MVC Web API–obsługa błędów

W poprzednim poście pisałem o podstawach Web API. Dzisiaj zajmiemy się obsługą błędów. Sprawdźmy najpierw, co stanie się, gdy nasz kontroler (patrz poprzedni wpis), zwróci jakiś wyjątek np.:

public Person GetPersonById(int id)
{
  if(id<=0)
      throw new ArgumentOutOfRangeException();
  return _personRepository.GetPersonById(id);
}

Po wpisaniu adresu “http://localhost:40521/api/persons/-1”, zostanie zwrócony internal error (kod 500) oraz następująca wiadomość:

<Error><Message>An error has occurred.</Message><ExceptionMessage>Specified argument was out of the range of valid values.</ExceptionMessage><ExceptionType>System.ArgumentOutOfRangeException</ExceptionType><StackTrace>   at MvcApplication7.Controllers.PersonsController.GetPersonById(Int32 id) in c:\projects\Microsoft\CQRS II\Source\MvcApplication7\MvcApplication7\Controllers\ValuesController.cs:line 74
   at lambda_method(Closure , Object , Object[] )
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.&lt;&gt;c__DisplayClass13.&lt;GetExecutor&gt;b__c(Object instance, Object[] methodParameters)
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.&lt;&gt;c__DisplayClass5.&lt;ExecuteAsync&gt;b__4()
   at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)</StackTrace></Error>

Nie jest to zbytnio trafne i lepiej zwrócić np. kod 404 Not Found, gdy dana osoba nie zostanie znaleziona w repozytorium. Można posłużyć się wyjątkiem HttpResponseException:

public Person GetPersonById(int id)
{
  if(id<=0)
      throw new HttpResponseException(HttpStatusCode.NotFound);
  return _personRepository.GetPersonById(id);
}

Za pomocą HttpResponseException możemy zwracać konkretny status HTTP. Zaglądając do dokumentacji dowiemy się, że wyjątek posiada konstruktor, który również posiada jako argument wejściowy pewną strukturę HttpResponseMessage:

public HttpResponseException(
    HttpResponseMessage response
)

Pozwala to zdefiniować dodatkowe dane takie jak np. zawartość zwrotnego pakietu.

Możliwe jest również zwrócenie HttpResponseMessage jako rezultat akcji:

public HttpResponseMessage GetPersonById(int id)
{
  if (id <= 0)
      return Request.CreateErrorResponse(HttpStatusCode.NotFound, "Nie znaleziono danej osoby w repozytorium.");

  return  Request.CreateResponse(HttpStatusCode.OK,_personRepository.GetPersonById(id));
}

W celu stworzenia obiektu, zwykle korzysta się z metod pomocniczych takich jak CreateErrorResponse czy CreateResponse. Komunikat błędu zostanie zwrócony w zależności od tego, jaki format został określony w zapytaniu HTTP. W IE zwykle jest to JSON:

{"Message":"Nie znaleziono danej osoby w repozytorium."}

Z kolei w Chrome, XML:

<Error>
<Message>Nie znaleziono danej osoby w repozytorium.</Message>
</Error>

Ponadto, łatwo  skorzystać z dobrodziejstw walidacji i ModelState. Rozważmy przykład:

[HttpPost]
public HttpResponseMessage AddPerson(Person person)
{
  if (!ModelState.IsValid)
      return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);

  _personRepository.AddPerson(person);
  return Request.CreateResponse(HttpStatusCode.OK);
}

Wszelkie błędy związane z walidacją, zostaną automatycznie przekazane w odpowiedzi.

Warto wspomnieć na zakończenie, że nic nie stoi na przeszkodzie, aby skorzystać z metod pomocniczych (CreateResponse, CreateErrorResponse) i wynik przekazać do opisanego wcześniej HttpResponseException.

ASP.NET MVC Web API, wprowadzenie

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:

image

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):

image

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:

image

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.

ASP.NET MVC: Walidacja danych za pomocą wywołania Ajax

Walidacja danych to bardzo szeroki temat. Sprawdzamy poprawność danych zarówno po stronie klienta (JavaScript) jak i serwera. Występuje ona we wszystkich warstwach systemu. Dzisiaj napiszemy metodę po stronie serwera, która będzie weryfikowała dane. W przeciwieństwie jednak do klasycznego podejścia, nie będziemy przeładowywać całej strony od nowa. Wywołanie będzie Ajaxowe czyli w tle (asynchroniczne). Użytkownik wpisując jakieś dane do formularza, spowoduje tym samym wysyłanie w tle żądania do serwera, który dokona weryfikacji.

Zacznijmy od naszej metody po stronie serwera:

public JsonResult Validate(string login)
{
  if (string.IsNullOrEmpty(login))
      return Json("Login nie moze byc pusty", JsonRequestBehavior.AllowGet);
  if (string.Equals(login, "piotr", StringComparison.OrdinalIgnoreCase))
      return Json("Login zajety.", JsonRequestBehavior.AllowGet);

  return Json(true, JsonRequestBehavior.AllowGet);
}

Jak widać, jest to zwykła metoda serwisowa, zwracająca JSON. Jeśli dane są nieprawidłowe to zwracamy komunikat błędu. W przeciwnym razie zwracamy TRUE. Oczywiście zawsze jest to format JSON.

Następnie zdefiniujemy nasz model:

public class Person
{
   [Remote("Validate","Home")]
   public string Login { get; set; }
   public string Password { get; set; }
}

Atrybut Remote jest kluczowy tutaj. To za pomocą niego określamy, która metoda i w jakim kontrolerze będzie wywoływana. Wskazujemy tutaj po prostu, że Validate będzie wykonany w tle, gdy użytkownik wpisuje jakiś tekst. Tak naprawdę, całą robotę wykona za nas jquery.  Pamiętajmy, aby zwrócić wspomniany model w jakieś akcji:

public ActionResult Create()
{
  return View(new Person());
}

Widok z kolei nie różni się niczym od klasycznego:

@using (Html.BeginForm ()) {
   @Html.AntiForgeryToken()
   @Html.ValidationSummary(true)

   <fieldset> 
       <div class = "editor-label">
           @Html.LabelFor(model => model.Login)
       </div> 
       <div class = "editor-field">
           @Html.EditorFor(model => model.Login)
           @Html.ValidationMessageFor(model => model.Login)
       </div>
          <p>
           <input type="submit" value="Create" />
       </p>
   </fieldset> 
}

Pamiętajmy również, aby dołączyć odpowiednią bibliotekę jQuery:

@Scripts.Render("~/bundles/jqueryval")

Zaglądając do kodu HTML, przekonamy się, że odpowiednie atrybuty został dodane:

<form action="/Home/Create" method="post"><input name="__RequestVerificationToken" type="hidden" value="Vii8pmoVPcEaAPbTlWVrUsJXFWfjoiUMEbvQPGK-9pvGuetx2RVKnchLsBtkaZ7btU7xFSNoMFLCcRiP8znCSqe9cnpYkD9v38fw-W21X7o1" />   <fieldset> 
       <div class = "editor-label">
           <label for="Login">Login</label>
       </div> 
       <div class = "editor-field">
           <input class="text-box single-line" data-val="true" data-val-remote="&#39;Login&#39; is invalid." data-val-remote-additionalfields="*.Login" data-val-remote-url="/Home/Validate" id="Login" name="Login" type="text" value="" />
           <span class="field-validation-valid" data-valmsg-for="Login" data-valmsg-replace="true"></span>
       </div>
          <p>
           <input type="submit" value="Create" />
       </p>
   </fieldset> 
</form>

Pozostaje już tylko uruchomić solucję i przetestować stronę:

image

Z logów widać wyraźnie, że wpisując kolejne znaki, nowe zapytania są wysyłane do serwera z zaktualizowanym parametrem login. W przypadku błędu, informacja zostanie odpowiednio wyświetlona. Wszystko dzięki atrybutowi Remote oraz jQuery. Jak widać, jest to bardzo proste do zrealizowania.

ASP.NET MVC: Atrybut BIND

W ASP.NET MVC do dyspozycji jest dość mało popularny atrybut Bind, który pozwala określić zachowanie bindingu pomiędzy modelem a widokiem. Załóżmy, że mamy następujący model:

public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Email { get; set; }
}

Następnie napiszemy prostą akcję, zwracającą model do widoku:

public ActionResult Create()
{
  var person=new Person();       
  return View(person);
}

Widok stanowić będzie prosty formularz:

@using (Html.BeginForm()) {
   @Html.AntiForgeryToken()
   @Html.ValidationSummary(true)

   <fieldset>
       <legend>Person</legend>

       <div class="editor-label">
           @Html.LabelFor(model => model.FirstName)
       </div>
       <div class="editor-field">
           @Html.EditorFor(model => model.FirstName)
           @Html.ValidationMessageFor(model => model.FirstName)
       </div>

       <div class="editor-label">
           @Html.LabelFor(model => model.LastName)
       </div>
       <div class="editor-field">
           @Html.EditorFor(model => model.LastName)
           @Html.ValidationMessageFor(model => model.LastName)
       </div>

       <div class="editor-label">
           @Html.LabelFor(model => model.Email)
       </div>
       <div class="editor-field">
           @Html.EditorFor(model => model.Email)
           @Html.ValidationMessageFor(model => model.Email)
       </div>

       <p>
           <input type="submit" value="Create" />
       </p>
   </fieldset>
}

Następnie przyjrzyjmy się szablonowi akcji POST:

[HttpPost]
public ActionResult Create(Person person)
{
  try
  {
      // TODO: Add insert logic here

      return RedirectToAction("Index");
  }
  catch
  {
      return View();
  }
}

Widzimy obiekt person jako parametr wejściowy. ASP.NET MVC sam zadba, aby połączyć dane z formularza HTML w klasę C#. Framework zrobi to za pomocą bindingu. Domyślnie wszystkie właściwości klasy, występujące w formularzu są wiązane na podstawie nazw. Zaglądając do html, zobaczymy:

<div class="editor-label">
 <label for="FirstName">FirstName</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="FirstName" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
 <label for="LastName">LastName</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="LastName" name="LastName" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="LastName" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
 <label for="Email">Email</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="Email" name="Email" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span>
</div>

Nazwy właściwości klasy oraz kontrolek input pokrywają się. Atrybut BIND daje nam większą kontrolę nad tym jak dane będą wiązane. Można np. wiązać wyłącznie wskazane właściwości tzn.:

[HttpPost]
public ActionResult Create([Bind(Include = "FirstName,LastName")]Person person)
{
}

Po przecinku podaje się właściwości, które chcemy wiązać. W powyższym przykładzie tylko imię i nazwisko zostaną przekazane, pomijając adres email. Analogicznie można wyłączyć jakieś właściwości za pomocą Exclude:

[HttpPost]
public ActionResult Create([Bind(Exclude = "FirstName,LastName")]Person person)
{
}

Atrybutu nie trzeba wcale doczepiać do konkretnego parametru wejściowego. Możliwe jest również jego użycie bezpośrednio, globalnie na modelu:

[Bind(Exclude = "FirstName,LastName")]
public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Email { get; set; }
}

Bardzo użyteczną właściwością Bind jest Prefix. W praktyce używa się go, gdy mamy do czynienia z częściowymi widokami oraz zagnieżdżonymi typami. Spróbujmy zmodyfikować powyższy przykład, aby pokazać zastosowanie prefiksu. Zacznijmy od encji:

public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Email { get; set; }
}

public class EmployeeInfo
{
   public Person PersonInfo { get; set; }
   
}

Zmodyfikowana akcja:

public ActionResult Create()
{
  var employee = new EmployeeInfo();
  employee.PersonInfo=new Person();

  return View(employee);
}

Wygenerowany HTML:

<div class="editor-label">
 <label for="PersonInfo_FirstName">FirstName</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="PersonInfo_FirstName" name="PersonInfo.FirstName" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="PersonInfo.FirstName" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
 <label for="PersonInfo_LastName">LastName</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="PersonInfo_LastName" name="PersonInfo.LastName" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="PersonInfo.LastName" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
 <label for="PersonInfo_Email">Email</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="PersonInfo_Email" name="PersonInfo.Email" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="PersonInfo.Email" data-valmsg-replace="true"></span>
</div>

Jak widać identyfikatory zawierają prefiks np. PersonInfo.FirstName zamiast FirstName . Oczywiście wynika to z tego, że wiążemy teraz model Employee, który zawiera zagnieżdżony obiekt o nazwie PersonInfo. Chcemy jednak być w stanie skorzystać z następującej akcji:

[HttpPost]
public ActionResult Create(Person person)
{
  try
  {
      // TODO: Add insert logic here

      return RedirectToAction("Index");
  }
  catch
  {
      return View();
  }
}

Innymi słowy, przekazujemy Employee, ale chcemy otrzymać wyłącznie Person ponieważ taki model akurat podana akcja tylko obsługuje. Można stworzyć Person samemu na podstawie identyfikatorów oraz FormsCollection:

[HttpPost]
public ActionResult Create(FormCollection formCollection)
{
  try
  {
      Person person=new Person();
      person.FirstName = formCollection["PersonInfo.FirstName"];
      person.LastName = formCollection["PersonInfo.LastName"];
      person.Email = formCollection["PersonInfo.Email"];

      return RedirectToAction("Index");
  }
  catch
  {
      return View();
  }
}

Rozwiązanie oczywiście bardzo mało eleganckie. Lepiej skorzystać ze wspomnianego atrybutu Bind:

[HttpPost]
public ActionResult Create([Bind(Prefix = "PersonInfo")]Person person)
{
}

Factory Method a Abstract Factory

Dzisiaj całkowicie o podstawach ale jednak warto przypomnieć sobie słownictwo. Każdy kojarzy chyba wzorzec factory. Oficjalnie wyróżnia się jednak kilka typów tego wzorca. Najpopularniejsze z nich to Factory method oraz Abstract Factory. W podręczniku do wzorców projektowych, znajdziemy je jako dwie osobne konstrukcje.

Czy to naprawdę tak ważne, aby znać różnice w nazewnictwie? Wzorce projektowe traktuję jako słownictwo. Zamiast wyjaśniać drugiej osobie, dokładnie co chcę napisać, używam wzorców projektowych, aby po prostu opisać daną konstrukcje. Traktuję więc podstawowe wzorce projektowe jako czyste słownictwo bo chyba większość programistów używa każdego dnia Template Method, ale niestety nie każdy jest tego świadomy.

Wracając do głównego tematu… Istnieje wiele konstrukcji, które można nazywać fabryką. Pierwszą z nich jest choćby następująca delegata:

Func<IDataPointReader> readerFactory = ()=>new ConcreteImplementation();

Kolejną konstrukcją może być statyczna metoda np.:

class StaticFactory
{
    public static IDataPointReader CreatePointReader(ReaderType type)
    {
        switch(type)
        {    
            case Type1:
                return new Type1PointReader();
            case Type2:
                return new Type1PointReader();
            default:
                return new AnotherPointReader();
        }
    ]
}

Jak prawie każde statyczne rozwiązanie jest to według mnie anty-wzorzec – nic innego jak globalny pojemnik, którego nie da się wstrzyknąć.

Można zmodyfikować trochę StaticFactory i używać niestatycznych metod. Ponadto można zdefiniować bazowy interfejs i potem po prostu wstrzykiwać implementację, tzn.:

interface IDataPointReaderFactory
{
    IDataPointReader Create(ReaderType type);
}

class ProductionFactory: IDataPointReaderFactory
{
    public IDataPointReader Create(ReaderType type)
    {
        //...
    }
}

Kolejna fabryka to wspomniana w tytule Factory Method:

abstract class Product{}
class ProductA:Product{}
class ProductB : Product { }


abstract class Factory
{
   public abstract Product Create();
}

class ConcreteFactoryA : Factory
{
   public override Product Create()
   {
       return new ProductA();
   }
}
class ConcreteFactoryB : Factory
{
   public override Product Create()
   {
       return new ProductB();
   }
}

Innymi słowy, klasy pochodne decydują jaki obiekt ma zostać stworzony. Powyższy przykład jednak jest bardzo sztuczny. Bardziej podoba się np. ten znaleziony na Wikipedia:

public class MazeGame 
{
    public MazeGame() 
    {
        Room room1 = MakeRoom();
        Room room2 = MakeRoom();
        room1.Connect(room2);
        
        this.AddRoom(room1);
        this.AddRoom(room2);
    }
 
    protected virtual Room MakeRoom() 
    {
        return new OrdinaryRoom();
    }
}

public class MagicMazeGame:MazeGame 
{    
    protected override Room MakeRoom() 
    {
        return new MagicRoom();
    }
}

Z przykładu widać wyraźnie, że decyzja o typie może zapaść na dowolnym poziomie dziedziczenia – nie koniecznie na tym  w którym jest instancja wykorzystywana.

Następnym wzorcem jest Abstract Factory. Zacznijmy znów od przykładu:

abstract class FactoryBase
{
   public abstract ProductABase CreateProductA();
   public abstract ProductBBase CreateProductB();
}
class ConcreteFactory1 : FactoryBase
{
   public override ProductABase CreateProductA()
   {
       return new ConcreteProductA1();
   }
   public override ProductBBase CreateProductB()
   {
       return new ConcreteProductB1();
   }
}
class ConcreteFactory2 : FactoryBase
{
   public override ProductABase CreateProductA()
   {
       return new ConcreteProductA2();
   }
   public override ProductBBase CreateProductB()
   {
       return new ConcreteProductB2();
   }
}

Fabryka we wzorcu Abstract Factory jest odpowiedzialna za tworzenie kilka różnych typów w przeciwieństwie do Factory Method. Jak widać w powyższym kodzie, każda z fabryk skojarzona może być z różnymi obiektami w ramach tej samej grupy ( w tym przypadku są to produkty).

Podsumowując:

  1. Factory Method to pojedyncza metoda, tworząca ten sam typ obiektu. Tworzenie obiektów jednak może być oddelegowane do klas pochodnych. W praktyce, często tworzymy normalny obiekt biznesowy, w którym występujące po prostu metoda Create. Rzadko tworzy się specjalnie osobną klasę “Factory”, aby zaimplementować factory method.
  2. Abstract Factory jest odpowiedzialny za tworzenie obiektów należących do tej samej grupy, rodziny. Zwykle tworzy się osobne klasy, aby zaimplementować ten wzorzec. Warto wspomnieć, że różne frameworki IoC wspierają fabryki i automatycznie generują je w formie delegaty Func<T1,T2,…>

Asynchroniczne kontrolery

W poprzednim wpisie zająłem się wpływem sesji na wydajność kontrolerów. Dzisiaj zajmiemy się asynchroniczny kontrolerami, które znaczącą zostały uproszczone w .NET 4.5.

Żeby zrozumieć jak działają asynchroniczne kontrolery, należy zdawać sobie sprawę, jak działa przetwarzanie zapytań. Załóżmy, że wysłanych jest 1000 zapytać do serwera. Czy zostaną one obsłużone jednocześnie, a może sekwencyjnie, jedno po jednym? ASP.NET MVC ma pulę wątków, przeznaczoną do przetwarzania zapytań. Jeśli zatem pula ma pojemność 5, wtedy wyłącznie 5 zapytań może zostać obsłużonych jednocześnie – reszta musi poczekać na swoją kolej.

Szczegóły te może nas nie interesują, ale co w przypadku gdy któryś z kontrolerów czeka na jakieś zasoby? Łatwo sobie wyobrazić scenariusz, w którym akcja łączy się z inną usługą w celu pobrania danych. Jeśli połączenie się z usługą zajmuje 5 sekund, wtedy wątek jest blokowany przez cały ten czas – uniemożliwiając przetworzenie innych zapytań, które być może nie muszą czekać na żadne inne zasoby.

Szkoda więc marnować cenny wątek z puli, tylko po to, aby czekał na jakieś zasoby. Z pomocą przychodzą kontrolery asynchroniczne, które zostały znacząco uproszczone w .NET 4..5. Operacje IO, w których trzeba czekać długo na zasoby, umieszcza się po prostu w osobnym wątku. Ponadto kontroler powinien dziedziczyć po AsynContorler. Stwórzmy zatem dwa kontrolery, jeden synchroniczny, a drugi asynchroniczny. Oba będą wykonywać operacje, które po prostu czekają na jakieś zasoby:

public class AsynchronousController : AsyncController
{
   //
   // GET: /Async/

   public async Task<ActionResult> Index()
   {
       int value = await Task.Factory.StartNew<int>(WaitingForResponse);
       return Content(value.ToString(CultureInfo.InvariantCulture));
   }

   private int WaitingForResponse()
   {
       Thread.Sleep(1000);
       return 5;
   }
}
public class SyncController : Controller
{
   //
   // GET: /Sync/

   public ActionResult Index()
   {
       int value = WaitingForResponse();
       return Content(value.ToString(CultureInfo.InvariantCulture));
   }
   private int WaitingForResponse()
   {
       Thread.Sleep(1000);
       return 5;
   }
}

Proszę zwrócić uwagę, że koniecznie należy dziedziczyć po AsyncController .

Wiele osób nie rozumie tak naprawdę asynchronicznych kontrolerów i umieszcza w nich logikę, która jest po prostu czasochłonna. Nie przyniesie to żadnej optymalizacji. Asynchroniczne kontrolery są dla kodu, który musi czekać na jakieś zasoby a nie dla logiki, która konsumuje wiele cykli CPU. Jeśli mamy jakaś operację konsumującą CPU, wtedy tworzenie własnych wątków nie przyśpieszy wykonania a jedynie spowolni wątki ASP.NET MVC. Programowanie asynchroniczne nie jest tym samym co programowanie współbieżne.

ASP.NET MVC, kontrolery a sesje: test wydajności

W ostatnim wpisie wyjaśniłem jak bardzo sesja wpływa na wydajność i skalowalność aplikacji. Dzisiaj chciałbym pokazać przykład i konkretne liczby, które pozwolą nam oszacować skalę problemu.

Zacznijmy od ASP.NET MVC. Stworzymy trzy kontrolery:

  1. SessionlessCotroller – kontroler będzie miał zablokowaną sesję.
  2. SessionController – kontroler zapisuje dane do sesji.
  3. SessionReadOnlyController – kontroler ma dostęp tylko do odczytu.

Kod:

[SessionState(SessionStateBehavior.Required)]
public class SessionController : Controller
{        
public ActionResult Index()
{
  Session["Test"] = "test";
  Thread.Sleep(1000);
  return new HttpStatusCodeResult(200);
}
}
[SessionState(SessionStateBehavior.Disabled)]
public class SessionLessController : Controller
{     
   public ActionResult Index()
   {
       Thread.Sleep(1000);
       return new HttpStatusCodeResult(200);
   }
}
[SessionState(SessionStateBehavior.ReadOnly)]
public class SessionReadOnlyController : Controller
{
   public ActionResult Index()
   {
       var value = Session["Test"];
       Thread.Sleep(1000);
       return new HttpStatusCodeResult(200);
   }
}

Następnie napiszemy prostą aplikację konsolową, która będzie po prostu wywoływać powyższe metody z różnych wątków:

class Program
{
private static void Main(string[] args)
{
  RunTests(@"http://localhost/Session");
  RunTests(@"http://localhost/Sessionless");
  RunTests(@"http://localhost/SessionReadOnly");
}

private static void RunTests(string url,int tests=3)
{
  Console.WriteLine(url);

  for (int test = 0; test < tests; test++)
  {
      var cookieContainer = new CookieContainer();
      SendRequest(url, cookieContainer); 

      var stopwatch = Stopwatch.StartNew();

      const int requestNumber = 20;                            
      Parallel.For(0,requestNumber, (i) => SendRequest(url, cookieContainer));
  
      Console.WriteLine(stopwatch.ElapsedMilliseconds);
  }

  Console.WriteLine();
}

static void SendRequest(string sourceURL,CookieContainer cookies)
{
  var webRequest = (HttpWebRequest) WebRequest.Create(sourceURL);
  webRequest.CookieContainer = cookies;
  
  var response = (HttpWebResponse)webRequest.GetResponse();            
}
}

Kolejne wywołania dzielą te same ciasteczka, aby przekazywać id sesji.

Wyniki są następujące:

image

Wyraźnie widać, że zapis do sesji jest najwolniejszy ponieważ wtedy ASP.NET MVC (patrz poprzedni post), musi kolejkować zapytania. Ze względu na to, że korzystamy tutaj z wielowątkowej pętli, zapytania mogłyby być wykonywane po kilka naraz. W przypadku zapisu do sesji jest to niemożliwe –  kod jest wykonywany sekwencyjnie. Odczyt sesji nie spowoduje żadnych problemów (jest thread-safe) więc nie musi być kolejkowany – stąd wydajność taka sama jak w przypadku zablokowanej sesji.

Ktoś może zadać pytanie, że to sam zapis jest po prostu wolniejszy ponieważ trzeba modyfikować dane. Zmodyfikujmy powyższy kod tak, aby wykonywał się sekwencyjnie:

Parallel.For(0,requestNumber,new ParallelOptions{MaxDegreeOfParallelism = 1}, (i) => SendRequest(url, cookieContainer));

Wynik:

image

Przykład wyraźnie pokazuje, że to kwestia szeregowania zapytań a nie faktu, że zapis jest wolniejszy sam w sobie. Z tego inny wniosek nasuwa się – im więcej wątków z puli po stronie ASP.NET MVC tym bardziej obniżamy przepustowość kontrolera poprzez wprowadzenie sesji. Jeśli mamy np. 30 wątków w ASP.NET MVC wtedy moglibyśmy aż tyle zapytań obsłużyć w tym samym czasie gdyby nie sesja i jej implementacja w ASP.NET MVC.

ASP.NET MVC: Wydajność kontrolerów a przechowywanie sesji

Synchronizacja i przechowywanie sesji może być bardzo niekorzystne dla wydajności aplikacji webowej. Wyobraźmy sobie, że użytkownik wywołuje kontroler kilkukrotnie w ramach tej samej sesji. ASP.NET MVC musi zadbać o to, aby sesja zawsze miała prawidłową wartość. Niestety jest to osiągane poprzez kolejkowanie zapytaniach w ramach tej samej sesji. Jeśli zatem wywołujemy dwukrotnie metodę A, nie zostanie to wykonane współbieżnie. Dobrą stroną takiego mechanizmu jest fakt, że zapis i odczyt sesji jest bezpieczny.

W wielu przypadkach jednak, aplikacje webowe są bezstanowe. Programiści generalnie unikają sesji ponieważ zawsze wpływają one niekorzystnie na skalowalność aplikacji. W takich przypadkach warto zablokować dostęp do niej poprzez atrybut, umieszczony np. nad kontrolerem:

[SessionState(SessionStateBehavior.Disabled)]
public class SampleController : Controller
{
   public ActionResult Index()
   {
       Thread.Sleep(1000);
       return new HttpStatusCodeResult(HttpStatusCode.OK);
   }
}

Możliwe wartości SessionStateBehavior to Default, ReadOnly, Required, Disabled. W powyższym przykładzie blokujemy sesje co powinno zwiększyć wydajność, gdy kilka zapytań jest wykonywanych jednocześnie w ramach tej samej sesji.

Próba dostępu do sesji, gdy jest ona zablokowana, zakończy się wyjątkiem NullReferenceException:

public ActionResult Index()
{
  Session["test"] = "test"; // Exception!
  return new HttpStatusCodeResult(HttpStatusCode.OK);
}

Warto zauważyć, że przedstawiony scenariusz jest bardzo powszechny w Ajax – wywołujemy kilka asynchronicznych zapytań, w celu załadowania różnych fragmentów strony.

W następnym poście, napiszemy prostą aplikację, prezentującą powyższe rozważania.

SQL Server 2012: FileTables

FileTable to kolejny mechanizm dostępny w SQL Server mający na celu ułatwić przechowywanie dużej ilości danych np. plików. W starych bazach danych, często przechowywano pliki osobno na serwerze, a baza danych zawierała wyłącznie wskaźniki do tych plików. Podejście powodowało problemy, w przypadku synchronizacji danych tzn. plików dostępnych na dysku a ich identyfikatorami w bazie. Zwykle nie było innego wyjścia, ponieważ przechowywanie dużej ilości danych w bazach (plików graficznych) nie było zbyt optymalne.

SQL Server od dawna dysponuje już lepszymi rozwiązaniami takimi jak np. FileStream. FileTable to kolejny krok, mający na celu jeszcze bardziej ułatwić pracę z plikami w bazach. Nowy rodzaj tabeli jest kompatybilny zarówno z SQL Server jak i ze standardowym Windows API, służącym do odczytu plików. Innymi słowy, możemy modyfikować dane zarówno z poziomu SQL jak i Windows (przez Explorer). Plik taki zatem jest kompatybilny z wszystkimi innymi aplikacjami Windows i nie trzeba używać SQL aby uzyskać do niego dostęp. SQL Server zadba o synchronizacje tabeli z systemem plików Windows. Ponadto z poziomu SQL mamy inne dobrodziejstwa np. FULL-TEXT-SEARCH.

Przechodząc w SQL Management Studio do folderu Tables, zobaczymy nowy typ (File Tables):

image

FileTable jest oparty na wspomnianym FileStream więc najpierw musimy uaktywnić go w dwóch miejscach.

Pierwsze miejsce to poziom silnika bazy danych. Przechodzimy do SQL Server Configuration Manager i wybieramy z listy usług SQL Server:

image

Dwukrotnie klikamy na usłudze, przechodzimy do zakładki FileStream i aktywujemy FIleStream:

image

Kolejnym krokiem jest uruchomienie SQL Management Studio  i uaktywnienie FileStream na bazie danych. Wystarczy przejść do właściwości instancji i zakładki Advanced:

image

Stworzenie odpowiednich folderów i plików najlepiej dokonać za pomocą skryptu

alter database Test
add filegroup fsGroup contains filestream;
go

alter database Test
add file
  ( NAME = 'fsTest', FILENAME = 'c:\fsTest'
   )
to filegroup fsGroup;
go

ALTER DATABASE Test
SET FILESTREAM (NON_TRANSACTED_ACCESS = FULL, DIRECTORY_NAME = N'Data')

Póki co  zajmowaliśmy się wyłącznie FileStream. Musieliśmy to zrobić ponieważ FileTable bazuje na tym typie kolumny. Teraz możemy stworzyć już tabelę FileTable:

CREATE TABLE FileTableTest AS FileTable  
WITH  
(  
    FileTable_Directory = 'FileTableDirectory',  
    FileTable_Collate_Filename = database_default  
); 

W tej chwili mamy stworzoną już tabelę i możemy używać klasycznych zapytań SQL:

image

Oczywiście SELECT póki co nie zwróci żadnych danych. Z kontekstowego menu na tabeli wybierzmy “Explore FileTable Directory”. Zostaniemy przeniesieni do folderu, w którym znajdują się pliki. Stwórzmy kilka nowych plików:

image

Wykonując teraz powyższe zapytanie SELECT, rezultat będzie następujący:

image

Jak widać, system plików jest rozpoznawalny w tabeli. Spróbujmy zmienić nazwę piku z poziomu SQL:

UPDATE FileTableTest  
SET name = 'Hello World - updated.txt'  
WHERE stream_id = 'B40A2D6E-D4BC-E311-BE94-606C6688A184' 

Po wykonaniu zapytania, przechodzimy do Windows aby upewnić się, że nazwa została zmieniona:

image

Analogicznie, możemy usunąć plik:

delete from FileTableTest  
WHERE stream_id = 'B40A2D6E-D4BC-E311-BE94-606C6688A184' 

ASP.NET MVC: JSON i zwracanie danych za pomocą HTTP GET

Domyślnie ASP.NET MVC blokuje metody zwracające JSON, które wywołuje się za pomocą HTTP GET. Przykład:

public ActionResult GetData()
{
  return Json(new []{new Person("Piotr","Zielinski")});
}

Wykonanie zakończy się wyjątkiem:

This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet. 

Z tego względu, programiści często wywołują Json z parametrem AllowGet:

public ActionResult GetData()
{
  return Json(new []{new Person("Piotr","Zielinsk")},JsonRequestBehavior.AllowGet);
}

W starszych przeglądarkach powyższa metoda może stanowić ryzyko. Załóżmy, że kontroler powyższej metody jest opatrzony atrybutem Authorize, czyli dostęp do danych powinien być chroniony. Wyłącznie osoba zalogowana ma prawo wywołać GetData.

Załóżmy również, że złośliwa osoba stworzy następujący dokument HTML:

<html> 
<body> 
    <script type="text/javascript"> 
        Object.prototype.__defineSetter__('FirstName', function(obj){alert(obj);});
    </script> 
    <script src="http://localhost:62574/Home/GetData"></script> 
</body> 
</html>

Jako źródło skryptu wskazujemy JSON. Tak się składa, że tablica danych JSON to prawidłowy kod JavaScript:

[{"FirstName":"Piotr","LastName":"Zielinski"}]

Gdybyśmy zwrócili wyłącznie pojedynczy obiekt, wtedy ładowanie strony zakończyłoby się błędem. Wyżej mamy również metodę, która wykona się wyłącznie gdy ustawiana jest właściwość o nazwie FirstName. Metoda Object.prototype.__defineSetter__ umożliwia nam zdefiniowanie, co ma zdarzyć się w momencie ustawienia właściwości na jakimś obiekcie.  W tej chwili wyświetlamy Alert z zawartością obiektu co stanowi po prostu dane, które były wcześniej chronione.

Oczywiście musimy przekonać najpierw ofiarę, aby wykonała kod w jej kontekście. Jest to tak naprawdę odmiana ataku CSRF – wykonanie kodu, w kontekście innej osoby. Łatwo sobie wyobrazić kod, który zamiast wyświetlenia alertu, zapisuje dane na serwerze.

Rozwiązanie problemu jest proste – umożliwiać wyłącznie wykonanie metody za pomocą HTTP POST a nie GET. Wtedy nie będzie możliwe tzw. cross-domain request.

[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetData()
{
  return Json(new []{new Person("Piotr","Zielinski")});
}

Nowe przeglądarki są bezpieczne na tą lukę i nie jest możliwe pobranie danych w czyimś kontekście. Z tego co wyczytałem to Firefox od wersji 21, Chrome od 27 i IE od 10 nie są podatne na opisany wyżej atak.

Powyższe rozważania mają sens wyłącznie gdy dane są poufne i stąd opatrzone atrybutem Authorize. W takim przypadku nie chcemy, aby ktoś wykorzystał kontekst osoby zalogowanej w kompletnie innej aplikacji.