Podział aplikac ASP.NET MVC ji na warstwy

  • ASP.NET MVC wprowadza poziom separacji zagadnień SoC poprzez podział aplikacji na model, widok i kontroler.
  • Widok (ang. View) jest fragmentem widocznym przez użytkownika. Widok w ASP.NET MVC może używać dwóch silników wyświetlania: Razor i Web Forms.
  • Kontroler (ang. Controller) parsuje żądanie HTTP i wysyła komendy do modelu aby zaktualizować jego stan oraz do widoku aby odświeżyć prezentację modeli w widoku.
  • Model (ang. Model) zarządza danymi i logiką biznesową.
  • Planując podział aplikacji trzeba brać pod uwagę skalowalność (pamięć podręczną, przetwarzanie po stronie klienta przeciw przetwarzaniu po stronie serwera oraz dostęp do danych).
  • ASP.NET Entity Framework pozwala na trzy podejścia w tworzeniu modelu.
  • Bezstanowa natura ASP.NET MVC uniemożliwia użycie niektórych funkcji Entity Framework, dlatego najlepiej jest użyć warstwy abstrakcji dostępu do danych tworząc warstwę modelu np korzystając ze wzorca repozytorium.

Aplikacje rozproszone

  • Tradycyjnym podejściem służącym do przesyłania danych jest użycie serwisów sieciowych (ang. web services). W ciągu ostatnich lat używano różnych podejść do ich tworzenia. Serwisy ASMX używające WSDL zostały zastąpione przez WCF używające SOAP, które są powoli wypierane przez ASP.NET Web API używające podejścia REST oraz wzorców MVC do zarządzania żądaniami HTTP.
  • Projektowanie środowiska rozproszonego może być bardzo ciężkim zadaniem ponieważ każda część aplikacji która jest wdrożona samodzielnie musi komunikować się z innymi częściami aplikacji.
  • Aplikacje hybrydowe są aplikacjami które są wdrożone w środowisku rozproszonym częściowo wewnątrz firmy i częściowo w chmurze poza firmą. Tworząc takie rozwiązania trzeba mieć na uwadze ryzykowną naturę komunikacji sieciowej i rozważyć koncept powtarzania wiadomości.
  • Projektując środowisko rozproszone ważnym punktem to rozważenia jest obsługa stanu aplikacji oraz czy aplikacja powinna obsługiwać sesję.
  • Rozproszone aplikacje mogą poprawić niezawodność, dostępność i skalowalność. Jednym ze sposobów na poziomie serwerowym jest użycie farm serwerów.

Cykl życia w Windows Azure

  • Windows Azure jest chmurą obliczeniową używana do budowania, wdrażania i zarządzania aplikacjami.
  • Windows Azure zapewnia podejścia PaaS (platform as a service) i IaaS (infrastructure as a service)
  • Windows Azure udostępnia trzy typy rozwiązań: Virtual Machines, Web Sites i Cloud Services.
  • Istnieją trzy typy roli w Windows Azure: Web, Worker i VM. Rola Web jest używana do uruchamiania IIS, rola Worker do aplikacji warstwy pośredniej. Rola VM jest używana w przypadkach kiedy pozostałe nie spełniają warunków udostępniając całą maszynę wirtualną.
  • Ponieważ Windows Azure jest systemem bezstanowym to żadne zmiany w systemie gdy rola jest uruchomiona nie są zapisywane do następnego uruchomienia. Dlatego programista może ustawić zestaw zadań startowych używających naarzędzia AppCmd.exw.
  • Zadania startowe w Windows Azure są używane do wykonywania akcji przed wystartowaniem roli typu Web lub Worker. Pozwalają na rejestrację komponentów COM, instalację komponentów lub ustawienie kluczy rejestru.
  • Zadania startowe są definiowane w elemencie Task który jest węzłem elementu Startup w pliku ServiceDefinition.csdef. Typowym zadaniem jest aplikacja konsolowa lub plik wsadowy który startuje skrypty Windows PowerShell. Zadanie startowe jest wykonywane za każdym razem gdy rola zostaje ponownie uruchomiona. Zadania pozwalają uruchomić procesy i aplikacje wspomagające główną aplikację.
  • Po wykonaniu zadania wywoływana jest metoda OnStart którą można nadpisać aby zaimplementować własną funkcjonalność. metoda ta musi zwracać true aby uruchomienie nie zatrzymało się błędem.
  • Po wykonaniu OnStart uruchamiana jest metoda Run, która nie zwraca wyniku. Można ją nadpisać i uruchamiać w niej aplikacje równolegle.
  • Po wyłączeniu wywoływana jest metoda OnStop która również nie zwraca żadnego wyniku. Jest używana do zamknięcia i czyszczenia uruchomionych procesów.

Zarządzanie stanem aplikacji

  • Zarządzanie stanem może być ważną częścią aplikacji. W aplikacjach www jest skomplikowane ponieważ z definicji protokół HTTP jest bezstanowy. ASP.NET MVC oferuje wiele dróg implementacji stanu. Kiedy wybieramy jeden z nich musimy wziąć pod uwagę czynniki takie jak: miejsce przechowywania i używania stanu (klient czy serwer), ilość danych do przechowania oraz opóźnienia.
  • W ASP.NET MVC stan aplikacji może być przechowywany w: Cache, Session, Cookies, QueryString, Context.Items i Profile.
  • Obiekt Cache zapewnia szerszy zakres niż inne rodzaje ponieważ jego dane są dostępne dla wszystkich klas aplikacji. Obiekt Cache umożliwia przechowywanie par klucz-wartość dostępnych dla każdego użytkownika lub stronę w domenie aplikacji. Jest przetwarzany w wewnętrznym procesie dlatego jest dostępny na indywidualnym serwerze. W scenariuszach wieloserwerowych trzeba zapewnić aby każdy serwer miał własną kopię obiektu.
  • Najbardziej powszechnym sposobem zarządzania stanem jest sesja. Sesja może być przechowywana w bazie SQL Server, na osobnym serwerze lub w pamięci serwera IIS. Sesję można skonfigurować tak aby przekazywała session ID w cookie lub query string. Implementując sesję można użyć domyślnego magazynu przychodzącego z ASP.NET lub utworzyć własny dziedzicząc z klasy SessionStateStoreProviderBase.
  • Cookies są małymi porcjami informacji (do 4 KB) przechowywanymi po stronie klienta które mogą być utrwalane w sesji. Są przypisane do konkretnej domeny lub subdomeny i przesyłane do serwera z każdym żądaniem. Cookies można odczytać używając HttpContext.Request.Cookies i zapisać używając HttpContext.Response.Cookies. Cookie może posiadać również datę wygaśnięcia.
  • Query string jest również miejscem gdzie można wstawić pewną ograniczoną ilość informacji (długość żądania URL jest ograniczona),które mogą być przesłane do serwera i z powrotem dla konkretnego użytkownika. ASP.NET MVC zapewnia łatwy dostęp do query string poprzez HttpContext.Request.QueryString[“attributeName”] po stronie serwera i window.location.href po stronie klienta.
  • Context.Items zawiera informacje dostępne dla jednego żądania. Zwykle używa się go do dodania informacji do żądania w komunikacji między modułami HTTP, np. informacje o uwierzytelnieniu.
  • Profile jest przechowywane w bazie danych dla nazwy użytkownika i może być dostępne używając HttpContext.Profile[“miscellaneousData”]. Profile jest częścią dostawcy Membership and Roles i trzeba skonfigurować dostawcę w pliku konfiguracyjnym oraz używać ASP.NET membership.
  • Istnieje również możliwość przechowywania informacji o stanie po stronie klienta np używając HTML5 Web Storage API. Niestety ASP.NET MVC nie posiada domyślnych narzędzi do pracy z informacjami przechowywanymi po stronie klienta poza biblioteką jQuery. HTML5 Web Storage pozwala na wybór pomiędzy sessionStorage a localStorage. Zasięg sessionStorage pozwala na dostęp do zasobów dla stron w tej samej domenie URL. Obiekty w sessionStorage są przechowywane do czasu zamknięcia okna. Zasięg localStorage rozszerza sessionStorage i pozwala na przechowywanie obiektów poza zasięg życia okna komunikującego się z tym samym URL. HTML5 Web Storage API posiada również zdarzenia obsługujące zmiany w magazynie danych.
  • Skalowalność jest głównym problemem przy określaniu, jak najlepiej zarządzać stanem. Projektowanie skalowalnej architektury odrzuca automatycznie pewne dostępne rozwiązania jak np. proces w serwerze przechowujący stan ponieważ serwer może nie mieć dostępu do procesu innego serwera w klastrze.
  • ASP.NET MVC wspiera również protokoły bezstanowe pomocne przy skalowalności. Mechanizmy których można użyć przy obsłudze bezstanowej aplikacji to utworzenie identyfikatora przy pierwszym żądaniu użytkownika i przesyłanie go z każdym następnym żądaniem, użycie ukrytego pola przekazującego dane między żądaniami, dodanie funkcjonalności w kodzie JavaScript po stronie klienta obsługującej HTML5 Web Storage i wysyłającej do serwera potrzebne informacje, dodanie unikalnego fragmentu do query string lub adresu URL.

Przykład konfiguracji sesji InProc w pliku Web.config:

<system.web>
    <sessionState mode="InProc" cookieless="false" timeout="20" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" stateConnectionString="tcpip=127.0.0.1:42424"
/>
</system.web>

Przykład konfiguracji sesji StateServer w pliku Web.config:

<system.web>
    <sessionState mode="StateServer" stateConnectionString="192.168.1.103:42424" />
</system.web>

Przykład konfiguracji ASP.NET Membership w pliku Web.config:

<profile defaultprovider="DefaultProfileProvider" inherit="MyApplication.Models.CustomProfile"/>

Projektowanie strategii buforowania

Caching jest strategią pomagającą poprawić wydajność. Polega na przechowywaniu często uzywanych danych w pamięci o szybkim czasie dostępu.

Page output caching

  • Page output caching jest strategią dzieloną pomiędzy klientów i serwery. typy buforowania stron zawierają pełne i częściowe buforowanie strony. Przeglądarka może buforować każde żądanie HTTP GET przez zdefiniowany okres czasu. ASP.NET MVC pozwala na ustawienie czasu przez użycie filtru:
[OutputCache(Duration=120, VaryByParam="Name", Location="ServerAndClient")]
Public ActionResult Index()
{
    Return View("Index",myData);
}

Niestety OutputCache działa dobrze tylko do buforowania całych stron.

  • Donut caching (buforowanie pączkowe) i donut hole caching są typami częściowego buforowania stron. Pierwsze buforuje większość strony umożliwiając na część zawartości dynamicznej. Drugie pozwala na większość treści dynamicznej i część buforowanej. Razor nie obsługuje bezpośrednio Donut caching ale można użyć Substitution API i utworzyć helper MVC. Donut hole caching jest obsługiwane przez podzielenie fragmentów strony na różne akcje i użycie widoków częściowych które są buforowane np:
[ChildActionOnly] [OutputCache(Duration=60)]
public ActionResult ProductsChildAction()
{
    // Fetch products from the database and
    // pass it to the child view via its ViewBag
    ViewBag.Products = Model.GetProducts();
    return View();
}

można również dodać atrybut buforowania do całego kontrolera.

  • Windows AppFabric jest przykładem narzędzia tworzącego zawartość buforowaną podzieloną pomiędzy wiele serwerów w farmie. jest to zestaw serwisów zbudowany na Windows server które zarządzają buforowaniem rozproszonym. Może również zarządzać sesją w ASP.NET MVC.

Data caching

  • Data caching jest techniką po stronie serwera pozwalającą na wstawienie pośredniego bufora pomiędzy logikę biznesową i bazę danych. pozwala na ponowne użycie danych bez odczytu bezpośrednio z bazy danych do czasu, aż dane są nieaktualne lub ich czas życia wygaśnie.
  • Można użyć .NET 4 Caching Framework. Domyślna implementacja używa ObjectCache i MemoryCache. Generalnie buforowanie polega na utworzeniu implementacji interfejsu ICacheProvider użytej jako warstwa pośrednicząca pomiędzy logiką a dostępem do danych.

Application caching

  • Application caching jest właściwością HTML 5 która pozwala na tworzenie manifestu buforowania który opisuje ustawienia dla całej strony lub aplikacji i daje dostęp do pamięci podręcznej przeglądarki.
  • Dostęp dla programistów z poziomu Application Cache API (AppCache) Przykład manifestu:
# Cached entries.
CACHE:
/favicon.ico
default.aspx
site.css
images/logo.jpg
scripts/application.js
# Resources that are "always" fetched from the server.
NETWORK:
login.asmx
FALLBACK:
button.png offline-button.png

Sekcja CACHE opisuje zasoby które powinny być buforowane. Sekcja NETWORK opisuje zasoby które nie powinny być buforowane. Sekcja FALLBACK definiuje zasoby które powinny być zwrócone jeżeli zasoby nie zostaną odnalezione.

Definicja referencji do manifestu powinna być zapisana w tagu HTML:

<html manifest="site.manifest">

Manifest musi mieć ustawiony MIME-type o wartości “text/cache-manifest”.

HTTP caching

  • HTTP caching jest mechanizmem buforowania wbudowanym w protokół HTTP który obsługuje własną wersję wyliczenia wygaśnięcia i używa jej do określenia odpowiedzi która ma zostać wysłana do klienta.

Implementacja WebSocket

  • HTTP polling jest metodą JavaScript służącą do odpytywania serwera w regularnych odstępach czasu o nowe informacje o których powinien wiedzieć klient. Niestety nie jest to wydajna metoda ale działa w każdej przeglądarce wspierającej JavaScript.
  • HTTP long polling jest sposobem w którym ustanawia się długowieczne połączenie do serwera w celu monitorowania zmian. Połączenie zostaje utrzymane do czasu otrzymania wiadomości lub upływu określonego czasu po czym znowy zostaje ustanowione.
  • WebSockets jest sposobem zapewnienia dwustronnej komunikacji pomiędzy serwerem a klientem. Klient łączy się przez HTTP i wysyła żądanie aktualizacji do serwera, który ustanawia połączenie WebSockets. Trzeba zaimplementować komunikację po jednej i drugiej stronie aby współpracować z WebSockets. Kiedy tego dokonamy to każda komenda jest obsługiwana jak zdarzenie zgłaszane kiedy wiadomość jest otrzymywana.
  • WebSockets może być użyte do długotrwałej dwustronnej komunikacji. Nie zawsze jest najlepszym rozwiązaniem, szczególnie kiedy klientem jest starsza przeglądarka która nie wspiera HTML 5.
  • WebSockets wymaga ASP.NET 4.5 i IIS 8.

Przykład kodu jQuery ustanawiającego połączenie WebSocket:

var socket;
$(document).ready(function () {
    socket = new WebSocket("ws://localhost:1046/socket/handle");
    socket.addEventListener("open", function (evnt) {
        $("#display").append('connection');}, false);
    socket.addEventListener("message", function (evnt) {
        $("#display ").append(evnt.data);}, false);
    socket.addEventListener("error", function (evnt) {
        $("#display ").append('unexpected error.');}, false);

Przykład połączenie w ASP.NET MVC:

public async Task MyWebSocket(AspNetWebSocketContext context)
{
    while (true)
    {
        ArraySegment<byte> arraySegment = new ArraySegment<byte>(new byte[1024]);
        // open the result. This is waiting asynchronously
        WebSocketReceiveResult socketResult =
            await context.WebSocket.ReceiveAsync(arraySegment,
            CancellationToken.None);
        // return the message to the client if the socket is still open
        if (context.WebSocket.State == WebSocketState.Open)
        {
            string message = Encoding.UTF8.GetString(arraySegment.Array, 0,
                socketResult.Count);
            userMessage = "Your message: " + message + " at " +
                DateTime.Now.ToString();
            arraySegment = new
                ArraySegment<byte>(Encoding.UTF8.GetBytes(message));
                // Asynchronously send a message to the client
            await context.WebSocket.SendAsync(arraySegment,
                WebSocketMessageType.Text,
                true, CancellationToken.None);
        }
        else { break; }
        }
}

HTTP modules and handlers

Umożliwiają programiście na bezpośrednią interakcję z żądaniami HTTP. Kiedy żądanie startuje to jest przetwarzane przez wiele modułów HTTP modules, takich jak sesja i moduł uwierzytelnienia. Różniej żądanie jest przetwarzane przez jeden HTTP handler zanim wróci z powrotem przez stos żądania żeby ponownie być przetworzonym przez moduły.

  • Moduły pasują do przetwarzania w drodze do handlera i w drodze powrotnej od handlera. Moduł synchroniczny posiada metodę Init która umożliwia ustawienie obsługi jednego ze zdarzeń załączonych do żądania.
  • Moduł asynchroniczny jest trudniejszy w obsłudze ale z async, await i Task można utworzyć moduł który potrafi obsłużyć długotrwałe zadanie bez zatrzymywania procesu.
  • Moduły pozwalają na przechwycenie, dzielenie i modyfikację każdego żądania.
  • Utworzenie modułu wymaga implementacji interfejsu System.Web.IHttpModule który ma dwie metody void Init(HttpApplication) i Dispose. System.Web.HttpApplication posiada zdefiniowane 22 zdarzenia które mogą być subskrybowane w metodzie Init i pozwalają modułowi na pracę w różnych stadiach obsługi żądania.
  • Plik Web.config posiada sekcję odpowiedzialną za konfigurację modułu HTTP aplikacji.
  • Handlery są celem żądań i wysyłają żądania do pojedynczego URL. Handlery mogą być synchroniczne i asynchroniczne w zależności od klasy z której dziedziczą.
  • Handler musi implementować interfejs IHttpHandler z metodą ProcessRequest(HttpContext) i właściwością IsReusable.
  • Wybór pomiędzy modułem a handlerem zależy od miejsca w którym trzeba zmienić funkcjonalność. Jeżeli musisz obsłużyć specyficzny adres URL to prawdopodobnie potrzebujesz utworzyć handler. Jeżeli chcesz zadziałać kiedy coś się wydarzy w czasie przetwarzania to powinieneś użyć modułu.

Przykład asynchronicznego modułu:

private async Task ScrapePage(object caller, EventArgs e)
{
    WebClient webClient = new WebClient();
    var downloadresult = await webClient.DownloadStringTaskAsync("http://www.msn.com");
}
public void Init(HttpApplication context)
{
    EventHandlerTaskAsyncHelper helper =
        new EventHandlerTaskAsyncHelper(ScrapePage);
    context.AddOnPostAuthorizeRequestAsync(
        helper.BeginEventHandler, helper.EndEventHandler);
}

Przykład asynchronicznego handlera:

public class NewAsyncHandler : HttpTaskAsyncHandler
{
    public override async Task ProcessRequestAsync(HttpContext context)
    {
        WebClient webClient = new WebClient();
        var downloadresult = await
        webClient.DownloadStringTaskAsync("http://www.msn.com");
    }
}