Doświadczenie użytkownika końcowego aplikacji jest bardzo istotne, szczególnie dla stron internetowych. Wiele czynników wpływa na UX.

Optymalizacja SEO

  • Wiele firm, szczególnie e-comerce, opierają się na wyszukiwarkach aby pomóc użytkownikom dostać się do ich stron.
  • W ramach swoich działań SEO, trzeba usunąć przeszkody, które mogłyby utrudnić wyszukiwarkom skuteczne zindeksowanie witryny. należy upewnić się, że w kodzie nie ma brakujących lub niekompletnych tagów HTML oraz, że treść jest odseparowana od prezentacji i skryptów. Jest to bardzo istotne ponieważ większość kodu HTML jest tworzone przez helpery i jest współdzielona z szablonów i nie jest zwykle sprawdzana.
  • Narzędzia analityczne pomagają sprawdzić poprawność kodu HTML w aplikacji. Należą do nich IIS SEO Toolkit i Internet Explorer Developer Toolbar. W3C posiada walidator sieciowy który sprawdza poprawność HTML i CSS.
  • IIS SEO Toolkit nie jest narzędziem wbudowanym w IIS. Należy go pobrać ze strony Microsoft.com. Można dzięki niemu analizować strony i aplikacje, tworzyć mapy stron oraz tworzyć zasady wykluczeń dla robotów a także pliku robots.txt w którym definiujemy które strony nie powinny być zindeksowane.
  • Technologia jest ciągle ulepszana, pozwalając ludziom z upośledzeniami używać internetu coraz bardziej skutecznie. WAI-ARIA jest systemem znaczników który wspomaga technologię i pozwala użytkownikom na lepsze zrozumienie treści. Przykład kodu HTML z ARIA:
<html>

<head>
    <title>ARIA Example: Hello</title>
</head>

<body>
    <div role="application">
        <div id="name" class="name">
            <h2>Hello</h2>
            <p>
                <strong>Instructions:</strong> Insert your name in the box below. An
                <abbr title="Accessible Rich Internet Application">ARIA</abbr> dialog box will display the result. To start over, press the Try Again button.
            </p>
            <p class="input">
                <label id="name_label" for="name_text">Insert your Name:</label>
                <input type="text" id="name_text" size="3" aria-labelledby="name_label" aria-invalid="false" />
            </p>
            <p id="name_alert" role="alert" class="feedback"></p>
            <p class="input">
                <input class="button" id="name_check" type="button" role="button" aria-pressed="false" value="Check Name" />
                <input class="button" id="name_again" type="button" role="button" aria-pressed="false" value="Try Again" />
            </p>
        </div>
    </div>
</body>

</html>
  • ASP.NET MVC nie posiada wbudowanego wsparcia dla ARIA ale rozszerzalna natura ASP.NET MVC pozwala na utworzenie helperów HTML zgodnych z ARIA, np:
public static IHtmlString ARIATextBoxFor<TModel, TProperty>(this HtmlHelper<TModel>
    helper, Expression<Func<TModel, TProperty>> exp)
{
    ModelMetadata metadata = ModelMetadata.FromLambdaExpression(exp,
        helper.ViewData);
    var attr = new RouteValueDictionary();
    if (metadata.IsRequired)
    {
        attr.Add("aria-required", true);
    }
    return helper.TextBoxFor(exp, attr);
}

Globalizacja i lokalizacja

  • Globalizacja jest przygotowaniem technicznym aplikacji do wsparcia wielu kultur i dzieli się na umiędzynarodowienie (I18N) i lokalizację. Umiędzynarodowienie jest procesem w którym przystosowujemy aplikację do obsługi wielu kultur. Lokalizacja jest procesem tworzenia treści specyficznej dla danej lokalizacji.
  • najwyższym poziomem lokalizacji jest język. Podkategorią języka jest narodowość np j. angielski jest inny w UK i USA. Oba są tym samym językiem ale mają swoje kody narodu US i UK. kiedy aplikacja obsługuje globalizację to w pierwszej kolejności wyszukuje tłumaczenia dla narodowości a później dla języka a na końcu w języku domyślnym. Kiedy żądanie HTTP jest wysyłane do serwera to zawiera nagłówek podobny do poniższego:
GET http://localhost/HTTP/1.1
Connection: keep-alive
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.8

Aby aplikacja mogła to obsłużyć należy dodać do pliku Web.config wpis:

<globalization culture="auto" uiculture="auto" enableclientbasedculture="true"/>

To ustawienie mówi ASP.NET MVC aby automatycznie ustawić narodowość klienta i po otrzymaniu żądania w innym języku niż domyślny ASP.NET ładuje tę informację do właściwości CurrentThread.CurrentUICulture.

  • Globalizacja wymaga aby wszystkie treści które mogą być wyświetlane zostały umieszczone w plikach zasobów. Przy tworzeniu plików zasobów powinno unikać się powtórzeń ponieważ zwiększają one koszt tłumaczenia. klasa pomocnicza ResourceManager pomaga zwrócić przetłumaczone zasoby. ResourceManager pobiera język z klasy CultureInfo.
  • Alternatywą dla plików z zasobami jest tworzenie osobnych stron dla różnych języków. Aby użyć tego podejścia należy nadpisać metodę Controller.OnActionExecuted tak aby budowała ścieżkę do widoku na podstawie klasy UICulture aktualnego wątku.
  • W ASP.NET zaleca się aby pliki zasobów były rozdzielone na języki a sugerowana konwencja nazewnictwa to .
  • Pliki zasobów mogą być używane przez wiele widoków. Dla języków zachodnich w których czyta się zwykle od prawej do lewej trzeba użyć innego zestawu widoków.
  • Należy zapewnić zasoby globalizacji dla jQuery. Java Script nie może pobrać kultury z przeglądarki, nawet jeżeli ta informacja jest dostępna i przesyłana przez przeglądarkę. Microsoft utworzył moduł globalizacji dla jQuery. Przykład dołączenia plików JS dla globalizacji:
<script src="scripts/jquery.globalize/globalize.js" type="text/javascript"></script>
<script src="scripts/jquery.globalize/cultures/globalize.cultures.js" type="text/javascript"></script>

Aby używać języka po stronie klienta należy zapisać go do zmiennej np:

var language = "@Request.UserLanguages[0]";
jQuery.preferCulture(language);

Lub ustawić jako język w przestrzeni globalnej:

$.global.preferCulture(language)
  • Można umożliwić użytkownikom wybór kultury w której chcą obsługiwać aplikację. kiedy użytkownik wybierze narodowość to można ją zapisać w sesji lub jako ciasteczko. Później należy nadpisać domyślny jeżyk z przeglądarki przez język wybrany przez użytkownika. Dobrym miejscem na nadpisanie języka jest filtr akcji, np:
Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-MX");
  • Pliki z zasobami można aktualizować lub tworzyć z pomocą narzędzi takich jak Assembly Linker i Resource File Generator. przykład utworzenia pliku tłumaczen przez Assembly Linker:
al.exe /t:lib /embed:strings.de-DE.resources /culture:de-DE /out:MyApp.de-DE.resources.dll

Implementacja kontrolerów i akcji MVC

Atrybuty i filtry

  • Atrybuty filtrów pozwalają na zbadanie i podjęcie akcji na podstawie informacji zawartych w żądaniu przed i po wywołaniu akcji.
  • ASP.NET MVC posiada wbudowane atrybuty pomagające przy uwierzytelnieniu, autoryzacji, zabezpieczeniu dostępu i zarządzaniu błędami. jeżeli istnieje taka potrzeba to można zbudować własne filtry.
  • Główną rolą atrybutów jest analiza informacji, szczególnie zawartych w klasie HttpContext, przychodzących i wychodzących z kontrolera.
  • Atrybuty filtrów dziedziczą z klasy System.Web.Mvc.FilterAttribute i pozwalają programiście na wstawienie pewnej logiki do przetwarzania akcji kontrolerów.
  • Jeden zestaw atrybutów standardowych zawiera tradycyjne filtry sprawdzające pewne oczekiwania . Są to filtry RequireHttpsAttribute, ValidateAntiForgeryTokenAttribute, ValidateInputAttribute, AuthorizeAttribute, AllowAnonymous i ChildActionOnlyAttribute. Wszystkie można znaleźć w przestrzeni nazw System.Web.Mvc.
  • Filtry HandleErrorAttribute i ActionFilterAttribute są bardziej podobne do kodu otaczającego akcję niż do filtrów. Pierwszy obsługuje wyjątki występujące w akcjach, domyślnie w ASP.NET MVC wyświetlany jest widok ~/Views/Shared/Error. Filtr ten pozwala na użycie różnych widoków dla różnych wyjątków. Aby zmienić jego zachowanie należy przesłonić metodę OnException atrybutu. Atrybut ActionFilterAttribute jest klasą abstrakcyjną z której filtry dziedziczą. Pozwala ona na tworzenie własnych filtrów i klas które mogą być atrybutami akcji. podstawowe metody do nadpisania to:
  • OnActionExecuting - wywoływana przed wywołaniem akcji. Pozwala na zbadanie informacji dostępnych w HttpContext i podjęcie decyzji czy akcja powinna być kontynuowana.
  • OnActionExecuted - pozwala zbadać wynik akcji i zdecydować czy coś musi sie wydarzyć w tym punkcie.
  • OnResultExecuting - wywoływana przed przetworzeniem wyniku akcji.
  • OnResultExecuted - wywoływana po przetworzeniu wyniku akcji ale przed załadowaniem wyniku do strumienia wyjściowego.
  • Atrybuty akcji mogą być zamieszczone w trzech miejscach. Dla poszczególnych akcji kontrolerów. Dla klasy kontrolera co spowoduje, że zostaną zastosowane dla wszystkich akcji kontrolera. Globalnie co spowoduje, ze wszystkie akcje aplikacji będą używać tego filtru. Aby dodać filtr globalny należy dodać linię kodu w metodzie RegisterGlobalFilters w pliku App_Start/FilterConfig.cs:
filters.Add(new RequireHttpsAttribute());

Implementacja akcji i wyników akcji

  • ASP.NET MVC zapewnia programistom dużą elastyczność w jaki sposób mogą one przetwarzać przychodzące żądania . początkowa granica akcji rozpoczyna się w momencie w którym routing okresli która akcja musi być wywołana aby pasowała do ścieżki URL. Ta początkowa granica zawiera również dopasowanie parametrów wejściowych. kiedy decyzja o trasie jest podjęta to przepływ zostaje przekazany do obsługi zdarzeń i atrybutów filtrów.
  • Wynikiem akcji aplikacji MVC są wyniki akcji Action results. Są one ostatnim krokiem obsługi HttpRequest i są odpowiedzialne za zapis informacji do strumienia odpowiedzi.
  • Powszechnie stosowany wynik ViewResultBase jest bazowym wynikiem zwracającym HTML do klienta.
  • ViewResult i PartialViewResult dziedziczą z ViewResultBase.
  • Jeżeli akcja zwraca plik to istnieją wyniki akcji FileResult i ContentResult, pierwszy dla treści binarnej a drugi dla ASCII. Zwracając ContentResult należy zdefiniować treść jako ciąg znaków, podać kodowanie i określić typ treści ContentType np application/pdf. FileResult wymaga podania ContentType i FileDownloadName.
  • JavaScriptResult i JsonResult są zaprojektowane tak aby wspierać przetwarzanie po stronie klienta poprzez zwracanie kodu JavaScript lub obiektów JSON.
  • RedirectResult i RedirectToRouteResult przekierowują przetwarzanie do innego adresu URL lub innej akcji MVC.
  • EmptyResult reprezentuje brak wyniku.

Implementacja wiązania danych

  • Model binding jest elastycznym procesem który mapuje pola w interfejsie użytkownika na właściwości modelu. Istnieją trzy typy wiązania modelu: strongly-typed binding, weakly-typed binding, i using the value provider.
  • Strongly-typed binding jest narzędziem działającym w dwie strony w taki sposób, że helper HTML rozumie atrybuty w modelu i może ustawić sprawdzanie danych po stronie klienta bazując na tych informacjach. Framework może zidentyfikować te informacje kiedy są przesyłane z powrotem do metody akcji. W modelu można używać atrybutów sprawdzających poprawność danych jak np Required lub DataType(DataType.Password). Przykład silnie typowanego wiązania pola tekstowego:
@Html.TextBoxFor(m => m.UserName)

Do klas wiązania danych należą: DefaultModelBinder, LinqBinaryModelBinder, ModelBinderAttribute i ModelBinderDictionary.

  • Weakly-typed binding jest wiązaniem jednostronnym które nie dostarcza sprawdzania danych po stronie klienta ale tworzy model kiedy żądanie jest zwracane. Można dodać helpery pomocnicze w Weakly-typed binding oraz dodać listę akceptowanych i blokowanych atrybutów które powinny być wypełniane z formularza poprzez użycie parametrów Include i Exclude w atrybucie Bind. Przykład:
@Html.TextBox("model.UserName")
// ...
Public ActionResult Login([Bind(Prefix="login")]LoginUser user)
// ...
Public ActionResult Save([Bind(Exclude="Salary")]Employee emp)
  • Kiedy nie mamy dostępu do wiązania typów z formularzem a otrzymujemy żądanie POST to możemy użyć obiektu ValueProvider aby umożliwić wiązanie do modelu.
  • Istnieją cztery główne rodzaje wiązania ValueProvider w ASP.NET MVC wyspecjalizowane w wiązaniu innych stosów żądań.
  • FormsValueProvider pozwala na pracę z danymi zwracanymi jako wartości formularza.
  • QueryStringProvider pozwala na pracę z danymi zawartymi w query string.
  • HttpFileCollectionValueProvider - pozwala na pracę z załącznikami zawartymi w żądaniu.
  • RouteDataValueProvider - pozwala na pracę z danymi trasowania lub URL.
  • Dane zwracane przez wybranego dostawcę można wiązać z modelem używając metody rozszerzającej ToValueProvider obiektu FormCollection. ten proces próbuje dopasować pola z wartością do pustego modelu.
Public ActionResult Save(FormCollection formColl)
{
    Employee emp = new Employee();
    If (TryUpdateModel(emp, formColl.ToValueProvider())
    {
        UpdateModel(emp, formColl.ToValueProvider());
    }
    // more code here
}

Routing

  • Jeżeli w aplikacji istotne jest SEO to rozważ nadawanie adresów URL zrozumiałych dla człowieka np Product/ zamiast Product/1. Jeżeli powiązanie pomiędzy adresem URL a treścią strony nie jest jasne to może być wymagane wsparcie dla wielu różnych dróg do dotarcia do określonej strony, np wg tytułu, ISBN lub nazwy autora. Odpowiednio skonfigurowany routing pomaga aplikacji szybko zdecydować która akcja powinna obsłużyć żądanie.
  • Kolejność tras zawartych w RouteCollection lub tablicy routingu jest istotna. Routing wyszukuje na liście tras pierwszego dopasowania dla przychodzącego wzoru. Lista tras powinna zaczynać się od ignorowanych adresów, potem bardziej specyficzne adresy a na końcu adresy ogólne. Domyślna obsługa tras jest zapisana w metodzie RegisterRoutes w pliku App_Start/RouteConfig.cs. Aby dodać trasę do RouteCollection użyj funkcji MapRoute. Metoda ta przyjmuje nazwę i wzór adresu jako parametry wymagane oraz domyśle wartości i ograniczenia jako parametry dodatkowe.
  • Tworząc trasę można dodać domyślne wartości które mają zastąpić brakujące części w adresie URL. Jako częśc tej operacji można oznaczyć parametry jako opcjonalne tak aby obsługa routingu wiedziała żeby sprawdzić przesłonięte metody z innymi parametrami. Można również użyć tej strategii aby obsłużyć specjalne wymagania jak np tworzenie prostych adresów URL które serwer będzie tłumaczył na bardziej złożoną kombinację kontrolerów i akcji. Przykład RouteConfig.cs:

    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home",
              action = "Index", id = UrlParameter.Optional },
          constraints: new { id = @"\d+" }
      );
    }
    

    Enumeracja UrlParameter.Optional oznacza, że parametr jest parametrem opcjonalnym.

  • Ograniczenia (ang. constraints) są sposobem na filtrowania żądanych adresów URL i zdefiniowania innego routingu dla elementów w oparciu o ich typ lub zawartość. Obsługa routingu traktuje ograniczenia jako wyrażenie regularne i wylicza odpowiednią zmienną w celu ustalenia, czy istnieje dopasowanie. Powszechnie stosowanym ograniczeniem jest ograniczenie trasy tylko do takich w których jako odpowiedni segment adresu rozpoznano liczbę, generalnie do wyszukiwania id, np: constraints: new { id = @”\d+” }. Przykład tras określających różne metody dla różnych typów parametru:

    routes.MapRoute(
      name: "Videos",
      url: "Product/Details/{id}",
      defaults: new { controller = "DVD", action = "Index",
      Page = UrlParameter.Optional },
      constraints: new { id = @"[a-zA-Z]+"}
    );
    routes.MapRoute(
      name: "Videos2",
      url: "Product/Details/{id}",
      defaults: new { controller = "DVD", action = "Details",
      Page = UrlParameter.Optional },
      constraints: new { id = @"\d+" }
    );
    
  • W aplikacji ASP.NET MVC można również zdefiniować trasy które mają być ignorowane. Służy do tego metoda IgnoreRoute. Ponieważ routing analizuje trasy w kolejności to trasy ignorowane powinny znajdować się przed trasami które chcesz zidentyfikować jako akcje. Ponieważ ASP.NET MVC opiera się na IIS to żądanie do poszczególnego pliku przechodzi przez obsługę tras MVC ponieważ adres URL nie jest rozpoznawany przez IIS jako taki który powinien przejść przez MvcHttpHandler. Wzór dopasowania dla plików ignorowanych jest troszkę inny od mapowania. Poniższy przykład ignoruje żądania do stron .htm lub .html:
routes.Ignore("{*allhtml}", new {allhtml=@".*\.htm(/.*)?});
  • Duże i złożone aplikacje ASP.NET MVC mogą wymagać obsługi setek akcji w kontrolerach. Podział na obszary (ang. area) umożliwia podział funkcjonalności na grupy logiczne. Tworzy to nowe kopie folderów dla modeli, widoków i kontrolerów w folderze obszaru. Każdy obszar ma swój routing odseparowany od innych obszarów. Obszary są rozdzielone w aplikacji poprzez AreaName/Controller/Action.

Punkty rozszerzalności MVC

ASP.NET MVC zapewnia programistom wielu punktach rozszerzalności które umożliwiają wstawienie potrzebnej funkcjonalności do aplikacji. W czasie przetwarzania żądania można przechwycić je i rozszerzyć w każdym kroku.

Action Filters

Jednym z najpotężniejszych punktów rozszerzalności i najczęściej wykorzystywanym jest możliwość używania filtrów akcji (ang. action filter). Można nadpisać istniejące filtry lub stworzyć własne implementując interfejs IActionFilter i przypisując go kiedy jest wymagany. Filtry akcji pozwalają dostać się do stosu przetwarzania, zanim akcja zostanie wykonana, lub bezpośrednio po wykonaniu akcji. Cztery główne typy filtrów to:

  • Authorization - jest zaimplementowany wSystem.Web.Mvc.IAuthorizationFilter i ma na celu ocenę bezpieczeństwa oparte o to, czy metody powinny być wykonywane w zależności od uwierzytelnienia i uprawnień.
  • Action - jest zaimplementowany w System.Web.Mvc.IActionFilter i pozwala programiście na otoczenie wykonania akcji przez dodatkową logikę a w tym wprowadzić dodatkowe informacje do akcji, przetestować dane wchodzące i wychodzące oraz przerwanie wykonania akcji. Posiada dwie metody: OnActionExecuting, która jest wykonywana kiedy akcja jest wywoływana i OnActionExecuted, która jest wywoływana po wykonaniu akcji.
  • Result - jest zaimplementowany w System.Web.Mvc.IResultFilter i otacza wynik zwracany przez akcję. Pozwala na wykonanie dodatkowego przetwarzania wyniku akcji. Posiada on dwie metody: OnResultExecuting, która jest wywoływana przed wykonaniem wyniku i OnResultExecuted, która jest wywoływana kiedy wynik zakończyła realizację.
  • Exception - zaimplementowany w System.Web.Mvc.IExceptionFilter i jest wykonywany kiedy zgłoszony zostanie nieobsłużony wyjątek w jakimkolwiek etapie życia akcji.

Controller factory

W ASP.NET MVC można utworzyć własną fabrykę kontrolerów (ang. controller factory), która pozwala na wykonywanie nietradycyjnych decyzje o tym, jak są skonstruowane kontrolery. Takie podejście jest użyteczne kiedy trzeba przekazać pewne informacje jak np zależności lub referencje w czasie wykonywania. Najczęstszym powodem utworzenia fabryki jest wsparcie dla Dependency Injection (DI) i Inversion of Control (IoC). Utworzenie własnej fabryki wymaga zaimplementowania interfejsu Web.Mvc.IControllerFactory. Interfejs ten posiada trzy metody:

  • CreateController - obsługuje tworzenie kontrolerów.
  • ReleaseController - czyści zasoby kontrolera.
  • GetControllerSessionBehavior - definiuje i kontroluje współpracę kontrolera z sesją. Po utworzeniu fabryki kontrolerów trzeba ją zarejestrować w metodzie Application_Start pliku Global.asax:
ControllerBuilder.Current.SetControllerFactory(
    typeof(MyCustomControllerFactory());

Action results

Poza zdefiniowanymi wynikami akcji w ASP.NET MVC można utworzyć własne wyniki poprzez dziedziczenie z klasy System.Web.Mvc.ActionResult i nadpisanie metody ExecuteResult, np:

public class CustomResult<T> : ActionResult
{
    public T Data { private get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        // do work here
        string resultFromWork = "work that was done";
        context.HttpContext.Response.Write(resultFromWork);
    }
}

View engines

Nadpisanie silnika widoków pozwala na wtrącenie dodatkowej logiki w czasie renderowania HTML. Można również stworzyć i zarejestrować swój własny silnik widoków. Oba główne silniki widoków dziedziczą z klasy abstrakcyjnej System.Web.Mvc.VirtualPathProviderViewEngine. W zależności od tego co ma zostać zmienione w silniku można pominąć klasę VirtualPathProviderViewEngine i stworzyć własną implementację interfejsu System.Web.Mvc.IViewEngine który posiada trzy metody: FindView, FindPartialView i ReleaseView. Można również nadpisać któryś z istniejących silników.

Przykład własnego silnika widoków:

public class CustomViewEngine : VirtualPathProviderViewEngine
{
    public MyViewEngine()
    {
        this.ViewLocationFormats = new string[]
            { "~/Views/{1}/{2}.mytheme ", "~/Views/Shared/{2}.mytheme" };
        this.PartialViewLocationFormats = new string[]
            { "~/Views/{1}/{2}.mytheme ", "~/Views/Shared/{2}. mytheme " };
    }

    protected override IView CreatePartialView
        (ControllerContext controllerContext, string partialPath)
    {
        var physicalpath =
            controllerContext.HttpContext.Server.MapPath(partialPath);
        return new myCustomView (physicalpath);
    }

    protected override IView CreateView
        (ControllerContext controllerContext, string viewPath, string masterPath)
    {
        var physicalpath = controllerContext.HttpContext.Server.MapPath(viewPath);
        return new myCustomView(physicalpath);
    }
}

Przed użyciem własnego silnika trzeba go zarejestrować w metodzie Application_Start:

ViewEngines.Engines.Add(new CustomViewEngine());

W trakcie tworzenia własnego silnika widoków może okazać się konieczne utworzenie klasy widoku lub dodanie funkcjonalności do istniejącej klasy. Praca która wyświetla i przesyła informacje o stronie do odpowiedzi jest wykonywana w tej klasie. Utworzenie własnej klasy widoku wymaga zaimplementowania interfejsu System.Web.Mvc.IView i metody Render, która pobiera jako parametry System.IO.TextWriter i System.Web.Mvc.ViewContext (ViewContext zawiera informacje o obiektach HttpContext, ViewBag i FormData). Obiekt TextWriter jest narzędziem które jest używane do pobrania informacji, parsowania i wstawienia ich do obiektu HttpResponse. Przykład niestandardowej klasy widoku:

public class MyCustomView : IView
{
    private string _viewPhysicalPath;

    public MyCustomView(string ViewPhysicalPath)
    {
        viewPhysicalPath = ViewPhysicalPath;
    }

    public void Render(ViewContext viewContext, System.IO.TextWriter writer)
    {
        string rawcontents = File.ReadAllText(_viewPhysicalPath);
        string parsedcontents = Parse(rawcontents, viewContext.ViewData);
        writer.Write(parsedcontents);
    }

    public string Parse(string contents, ViewDataDictionary viewdata)
    {
        return Regex.Replace(contents, "\\{(.+)\\}", m => GetMatch(m,viewdata));
    }

    public virtual string GetMatch(Match m, ViewDataDictionary viewdata)
    {
        if (m.Success)
        {
            string key = m.Result("$1");
            if (viewdata.ContainsKey(key))
            {
                return viewdata[key].ToString();
            }
        }
        return string.Empty;
    }
}

Model binding

Wiązanie modeli (ang. model binding) jest narzędziem ułatwiającym komunikację jedno i dwu kierunkową pomiędzy modelami i formularzami. Czasami nie ma bezpośredniej korelacji pomiędzy widokiem i modelem. W takich wypadkach można utworzyć niestandardowy modelu wiązania. Przykład niestandardowej klasy wiązania z listą właściwości używanych w różnych modelach, które są pobierane z trzech pól formularza i mają być wiązane jako DateTime:

public class DropDownDateTimeBinder : DefaultModelBinder
{
    private List<string> DateTimeTypes = new List<string>{ "BirthDate",
        "StartDate", "EndDate" };

    protected override void BindProperty(ControllerContext contContext,
        ModelBindingContext bindContext, PropertyDescriptor propDesc)
    {
        if (DateTimeTypes.Contains(propDesc.Name))
        {
            if (!string.IsNullOrEmpty(
                contContext.HttpContext.Request.Form[propDesc.Name + "Year"])
            {
                DateTime dt = new DateTime(int.Parse(
                        contContext.HttpContext.Request.Form[propDesc.Name
                    + "Year"]),
                    int.Parse(contContext.HttpContext.Request.Form[propDesc.Name +
                        "Month"]),
                    int.Parse(contContext.HttpContext.Request.Form[propDesc.Name +
                        "Day"]));
                propDesc.SetValue(bindContext.Model, dt);
                return;
            }
        }
        base.BindProperty(contContext, bindContext, propDesc);
    }
}

Rejestracja klasy odbywa się w metodzie Application_Start pliku Global.asax:

ModelBinders.Binders.DefaultBinder = new DropDownDateTimeBinder();

Zamiast nadpisania domyślnej klasy DefaultModelBinder można utworzyć własne klasy wiązań. Klasa musi implementować System.Web.Mvc.IModelBinder i implementować metodę BindModel. Taką klasę również rejestrujemy w Application_Start pliku Global.asax:

ModelBinders.Binders.Add(typeof(MyNewModelBinder), new MyNewModelBinder ());

Routing

Domyślny moduł obsługi routingu daje programiście wiele możliwości lecz czasami może zaistnieć potrzeba dodania dodatkowej funkcjonalności. ASP.NET MVC pozwala na utworzenie własnych modułów obsługi routingu. Można albo nadpisać istniejący moduł albo całkowicie go zastąpić. Główny moduł obsługi tras jest zaimplementowany w klasie System.Web.Mvc.MvcRouteHandler. Nadpisując klasę MvcRouteHandler trzeba nadpisać metodę GetHttpHandler ponieważ pozwala ona na badanie wartości i ich zmianę jeżeli to konieczne. Przykład niestandardowego modułu routingu:

public class MyCustomRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext reqContext)
    {
        string importantValue = reqContext.HttpContext.Request.Headers.Get(
            "User-Agent");
        if (!string.IsNullOrWhiteSpace(importantValue))
        {
            reqContext.RouteData.Values["action"] = importantValue +
                reqContext.RouteData.Values["action"];
        }
        return base.GetHttpHandler(reqContext);
    }
}

Rejestracja niestandardowego modułu routingu dla poszczególnych tras odbywa się w metodzie MapRoute:

routes.MapRoute(
    "Home",
    "{controller}/{action}",
    new { controller = "Home", action = "Index"
    ).RouteHandler = new MyCustomRouteHandler ();

Jeżeli potrzebujemy całkowitego nadpisania modułu routingu to należy zaimplementować interfejs System.Web.Routing.IRouteHandler i metodę GetHttpHandler. Rejestracja w systemie jest trochę inna ponieważ metoda MapRoute automatycznie mapuje na MvcRouteHandler:

Route watermarkRoute = new Route("images/{name}",
    new WaterMarkRouteHandler("CodeClimber - 2011"));
routes.Add("image", watermarkRoute);

Redukcja obciążenia sieci

Gdy pasmo jest minimalne lub zdławione, można podwyższyć dobre doświadczenia użytkownika poprzez zapewnienie, że liczba i rozmiar plików, których potrzebuje pobrać jest minimalna.

  • Większość nowoczesnych przeglądarek ogranicza liczbę jednoczesnych połączeń do tej samej domeny do sześciu.
  • Istnieje kilka procesów które można uruchomić aby zmniejszyć rozmiar plików i zmniejszyć ilość plików do pobrania. ASP.NET MVC wspiera minimalizację plików JavaScript i CSS. W tym procesie usuwane są komentarze, białe znaki oraz inne nieużywane i zaśmiecające plik znaki. Proces ten również zmniejsza nazwy metod i zmiennych w plikach JavaScript. Minifikacja w ASP.NET MVC odbywa się po przełączeniu trybu kompilacja debug na false:
<compilation debug="false" />

Można tego również dokonać dodając wpis do metody RegisterBundles:

BundleTable.EnableOptimizations = true;
  • Innym procesem oferowanym w ASP.NET MVC jest tworzenie pakietów skryptów (ang. bundle). ASP.NET MVC bundling pozwala na tworzenie jednego pliku połączonego z wielu innych plików tak aby zmniejszyć liczbę połączeń pobierających pliki. Przykład tworzenia pakietu w pliku BundleConfig.cs:
bundles.Add(new ScriptBundle("~/bundles/myBundle").Include("~/Scripts/myScript1.js",
    "~/Scripts/myScript2.js",
    "~/Scripts/myScript3.js"));

Aby odwołać sie do pakietu używamy poniższego kodu:

@BundleTable.Bundles.ResolveBundleUrl(("~/bundles/myBundle")

Do pakietu dodawany jest hashtag dzięki któremu przeglądarka wie czy ma pobrać nową wersję pliku czy też tą która jest zapisana w pamięci podręcznej.

  • IIS pozwala na skonfigurowanie serwera tak aby przesyłał skompresowaną treść do przeglądarek które akceptują spakowaną treść. Serwer może skompresować statyczne pliki jak JS i CSS jak również wszystkie inne pliki dynamiczne. Można ustawić minimalny rozmiar pliku dla którego uruchamiana będzie kompresja. Przeglądarki akceptujące spakowaną treść przesyłają nagłówek:
Accept-Encoding: gzip, deflate

Przykład wysyłania pliku skompresowanego przez System.IO.Compression.GZipStream jako ContentResult:

using (FileStream oFileStream = article.LocalFile.OpenRead())
{
    using (FileStream cFileStream = File.Create(
        Guid.NewGuid().ToString() + ".gz"))
    {
        using (GZipStream compressionStream =
            new GZipStream(cFileStream, CompressionMode.Compress))
        {
            oFileStream.CopyTo(compressionStream);
            StreamReader reader = new StreamReader(compressionStream);
            results = reader.ReadToEnd();
        }
    }
}

Zwracając ContentResult należy rozważyć dodanie typu MIME.

  • Content Delivery Network (CDN) pozwalają na rozprowadzanie treści przez cały szereg dostawców co daje pewne korzyści. Twój serwer musi przetworzyć mniej operacji GET do pobrania zdjęć, skryptów itp. CDN potrafi umieścić treść bliżej klienta co prowadzi do potencjalnego mniejszego czasu pobierania plików i wzrostu wydajności.