Interfejs użytkownika w aplikacjach web

  • Język HTML pozwala opisać strukturę informacji zawartych wewnątrz strony internetowej. CSS wprowadza dodatkową kontrolę nad wyglądem i prezentacją strony internetowej. Kombinacja HTML i CSS pozwala różnym stronom wyglądać inaczej od pozostałych pomimo użycia tych samych konstrukcji.
  • Podstawową funkcją ASP.NET MVC jest dostarczanie informacji do użytkownika serwisu. HTML i CSS pozwalają na formatowanie tych informacji w atrakcyjny wizualnie sposób.
  • Dynamiczna zawartość strony jest głównym powodem do korzystania z ASP.NET MVC.
  • Używając silnika wyświetlania Razor, plik Views\Shared_Layout.cshtml zawiera główny szablon aplikacji a w tym odnośnik do plików ze stylami CSS, podstawowe elementy UI jak na menu, nagłówki i stopkę.
  • Silnik wyświetlania ASPX używa pliku Views\Shared\Site.Master.
  • Plik szablonu bazowego dziedziczy z System.Web.Mvc.ViewMasterPage niezależnie od silnika wyświetlania.
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
  • Helpery są konstrukcjami kodu ASP.NET MVC tworzącymi kod HTML.

Helper wyświetlający style css w silniku Razor:

@Styles.Render("~/Content/css")

Przykład szablonu w pliku Views\Shared_Layout.cshtml:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title - My ASP.NET MVC Application</title>
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <meta name="viewport" content="width=device-width" />
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <header>
        <div class="content-wrapper">
            <div class="float-left">
                <p class="site-title">
                    @Html.ActionLink("your logo here", "Index", "Home")
                </p>
            </div>
            <div class="float-right">
                <section id="login">
                    @Html.Partial("_LoginPartial")
                </section>
                <nav>
                    <ul id="menu">
                        <li>@Html.ActionLink("Home", "Index", "Home")</li>
                        <li>@Html.ActionLink("About", "About", "Home")</li>
                        <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                    </ul>
                </nav>
            </div>
        </div>
    </header>
</body>

Nowe tagi HTML 5

  • - sekcja strony zawierająca niezależną treść.
  • - treść powiązana do otaczającej treści.
  • - podpis ilustracji.
  • - ilustracja.
  • - stopka dokumentu lub sekcji.
  • - nagłówek dokumentu lub sekcji.
  • - sekcja z odnośnikami.
  • - grupa treści które są ze sobą powiązane.

Projektowanie zachowania UI

  • Walidacja po stronie klienta jest istotną właściwością JavaScript i ASP.NET MVC dzięki której eliminowane są przesyłania danych pomiędzy klientem a serwerem poprzez sprawdzenie po stronie klienta czy dane wprowadzone do formularza są poprawne. Zasady walidacji ASP.NET MVC są zbudowane w oparciu o adnotacje. Przykład klasy z adnotacjami służącymi do walidacji:
namespace ArticleApp.Models {
    public class Article {
        public int ID { get; set; }
        [Required] [StringLength(50,MinimumLength=5)]
        public string Title { get; set; }
        [RegularExpression[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}")]
        AuthorEmail { get; set;}
        [DataType(DataType.Date)]
        [Range(300, 3000)]
        public int NumberOfAuthors { get; set; }
        [Required]
        public DateTime CreateDate { get; set; }
        [Required]
        public string Description { get; set; }
        [Range(1, 250)]
        [DataType(DataType.Currency)]
        [Required]
        public decimal Price { get; set; } 
    }
    public class ArticleDBContext : DbContext
    {
        public DbSet<Article> Articles { get; set; }
    }
}

Przykład widoku dodawania nowego artykułu:

@model MvcApplication1.Models.Article @{ ViewBag.Title = "Create"; }
<h2>Create</h2> @using (Html.BeginForm()) { @Html.ValidationSummary(true)
<fieldset>
    <legend>Articles</legend>
    <div class="editor-label">
        @Html.LabelFor(model => model.Title)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Title) @Html.ValidationMessageFor(model => model.Title)
    </div>
    <div class="editor-label">
        @Html.LabelFor(model => model.CreateDate)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.CreateDate) @Html.ValidationMessageFor(model => model.CreateDate)
    </div>
    <div class="editor-label">
        @Html.LabelFor(model => model.Description)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Description) @Html.ValidationMessageFor(model => model.Description)
    </div>
    <div class="editor-label">
        @Html.LabelFor(model => model.Price)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Price) @Html.ValidationMessageFor(model => model.Price)
    </div>
    <p>
        <input type="submit" value="Create" />
    </p>
</fieldset>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts { @Scripts.Render("~/bundles/jqueryval") }

Sprawdzenie poprawności modelu po stronie akcji kontrolera:

[HttpPost]
public ActionResult Create(Article article)
{
    if (ModelState.IsValid)
    {
        db.Articles.Add(article);
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(article);
}
  • Aby UI mogło wykonać akcję walidacji po stronie serwera należy dodać atrybut System.Web.Mvc.RemoteAttribute do walidacji w modelu. Atrybut Remote akceptuje nazwę kontrolera i akcji które mają być wywołane. Walidacja po stronie serwera wymaga też dodatkowej konfiguracji.

Wpis wymagany w konfiguracji aby można było wykonać walidację po stronie serwera:

<appSettings>
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

Przykład modelu z walidacją po stronie serwera:

[Required]
[StringLength(6, MinimumLength = 3)]
[Remote("IsUserAvailable", "Validation")]
[RegularExpression(@"(\S)+", ErrorMessage = "White space is not allowed.")]
[Editable(true)]
public string UserName { get; set; }

Przykład walidacji po stronie serwera:

public JsonResult IsUserAvailable(string username)
{
    if (!WebSecurity.UserExists(username))
    {
        return Json(true, JsonRequestBehavior.AllowGet);
    }
    string suggestedUID = String.Format(CultureInfo.InvariantCulture,
        "{0} is not available.", username);
    for (int i = 1; i < 100; i++)
    {
        string altCandidate = username + i.ToString();
        if (!WebSecurity.UserExists(altCandidate))
        {
            suggestedUID = String.Format(CultureInfo.InvariantCulture,
                "{0} is not available. Try {1}.", username, altCandidate);
            break;
        }
    }
    return Json(suggestedUID, JsonRequestBehavior.AllowGet);
}
  • Biblioteki JavaScript mogą być pomocne przy projektowaniu UI. Przykładowo dzięki jQuery można manipulować drzewem DOM, tworzyć efekty i animacje dzięki czemu strona staje się bardziej interaktywna.
  • JavaScript jest interpretowanym językiem skryptowym bazującym na prototypowniu.

Przykład kodu JavaScript używającego prototypów:

var Contact = function(pageTitle) {
    this.pageTitle = pageTitle;
    this.bindEvents(); // binding events as soon as the object is instantiated
    this.additionalEvents(); // additional events such as DOM manipulation etc
};
var Contact.prototype.bindEvents = function() {
    $('ul.menu').on('click', 'li.email, $.proxy(this.toggleEmail, this));
};
var Contact.prototype.toggleEmail = function(e) {
    //Toggle the email feature on the page
};
  • AJAX służy do asynchronicznej aktualizacji części strony bez całkowitego jej przeładowania. ASP.NET MVC posiada przestrzeń nazw System.Web.MVC.Ajax która zawiera zestaw rozszerzeń wspierających użycie AJAX.

Przykład formularza używającego AJAX:

@model MvcApplication1.Models.Article @{ ViewBag.Title = "Create"; }
<link rel="stylesheet" href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" />
<script src="http://code.jquery.com/jquery-1.8.3.js"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<link rel="stylesheet" href="/resources/demos/style.css" />
<script>
    $(function () {
        $(".ReleaseDate").datepicker();
    });
</script>
<h2>Create</h2> @using (Ajax.BeginForm("PerformAction", new AjaxOptions { OnSuccess = "OnSuccess", OnFailure = "OnFailure" })) {
<fieldset>
    <legend>Article</legend>
    <div class="editor-label">
        @Html.LabelFor(model => model.Title)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Title) @Html.ValidationMessageFor(model => model.Title)
    </div>
    <div class="editor-label">
        @Html.LabelFor(model => model.CreateDate)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model. CreateDate) @Html.ValidationMessageFor(model => model. CreateDate)
    </div>
    <div class="editor-label">
        @Html.LabelFor(model => model.Description)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model. Description) @Html.ValidationMessageFor(model => model. Description)
    </div>
    <div class="editor-label">
        @Html.LabelFor(model => model.Price)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Price) @Html.ValidationMessageFor(model => model.Price)
    </div>
    <p>
        <input type="submit" value="Create" />
    </p>
</fieldset>
}
<p id="errorMessage" />
<script type="text/javascript">
    function OnSuccess(response) {
        //do something
    }

    function OnFailure(response) {
        //show failure
        document.getElementById('errorMessage').innerHTML = 'THERE WAS AN ERROR';
    }
</script>
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts { @Scripts.Render("~/bundles/jqueryval") }

Komponowanie szablonu UI

Partial view

  • Widok częściowy jest sposobem na użycie tej samej funkcjonalności na wielu stronach. Pozwala na zawieranie pod-widoku w wielu miejscach aplikacji. Zwykle przechowuje się je w folderze Views/Shared
  • Widok częściowy można utworzyć poprzez menu kontekstowe na folderze z widokami i wybranie opcji Add View oraz zaznaczenie Create as a partial view.
  • Użycie modelu w widoku częściowym wymaga dodania jego deklaracji:
@model ApplicationName.Models.ModelName
  • Wstawienie widoku do aplikacji odbywa się za pomocą składni @Html.Partial np:
<section id="login">
    @Html.Partial("_LoginPartial")
</section>

Razor

  • Silnik Razor pozwala na tworzenie szablonów które mogą być używane wielokrotnie. Szablony są przypisywane według typu obiektu, mogą służyć do wyświetlania (DisplayTemplates) lub edycji (EditTemplates). Szablony są przechowywane w ~Views/Shared/EditorTemplates i wywoływane przez @Html.EditorFor i @Html.DisplayFor, np:
@Html..EditorFor(model=>model.Article)
  • Razor tag @RenderBody() wstawia różne widoki do aplikacji z użyciem tagu
    .
  • Widoki i widoki częściowe powinny być używane wielokrotnie wszędzie gdzie jest to możliwe. Jeżeli widoki lub widoki częściowe używają tego samego modelu i kontrolera to można sprawdzić poprawność modelu za pomocą adnotacji i helperów HTML. W innym przypadku trzeba obsłużyć sprawdzanie poprawności samemu używając np AJAX do sprawdzenia poprawności po stronie klienta lub modyfikując kontroler i modele do tego zadania.
  • Szablony master lub layout mogą być przełączane w locie z wykorzystaniem kodu. ponieważ szablony zawierają zwykle informacje o bibliotekach JavaScript i stylach CSS to ich przełączenie może zmienić znacząco UI.
  • Biblioteka Modernizr.js ułatwia pisanie warunkowego kodu JavaScript i CSS w celu ustalenia, czy przeglądarka obsługuje funkcję, zwłaszcza HTML5.

Przykład kompletnego szablonu tworzonego przez Visual Studio:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")

</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                </ul>
                @Html.Partial("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

Wykrywanie cech przeglądarki

  • Ponieważ nie wszystkie przeglądarki obsługują standardy W3C w taki sam sposób to należy być ostrożnym wybierając sposób wyświetlania informacji. Biblioteki takie jak jQuery lub Modernizer pomagają w dostosowaniu wyglądu we wszystkich przeglądarkach.
  • Powszechną metodą wykrywania przeglądarki jest użycie Java Script do sprawdzenia nagłówka userAgent.

Przykład wykrywania czy przeglądarka użytkownika to Microsoft Internet Explorer:

<script type="text/javascript">
    if ( navigator.userAgent.indexOf("MSIE")>0 )
    {
        <!--[if lte IE 7]>
        <style TYPE="text/css">
            @import url(ie7.css);
        </style>
        <![endif]-->
    }
</script>

Przykład sprawdzenia czy metoda window.addEventListener jest wspierana przez przeglądarkę:

<script type="text/javascript">
    if(window.addEventListener)
    {
        // Browser supports "addEventListener"
        window.addEventListener("load", myFunction, false);
    }
    else if(window.attachEvent)
    {
        // Browser supports "attachEvent"
        window.attachEvent("onload", myFunction);
    }
</script>
  • Ponieważ nie wszystkie przeglądarki w pełni wspierają HTML5 to zalecaną metodą obsługi niektórych cech jest użycie alternatyw dla tych właściwości kiedy przeglądarka nie potrafi obsłużyć zasobu. Przykładowo tag pozwala na użycie alternatywnych zasobów do wyświetlenia i jeżeli wszystkie zawiodą to jeszcze odnośnika z którego można pobrać materiał wideo, np:
<video>
    <source src="video.mp4" type='video/mp4' />
    <source src="video.webm" type='video/webm' />
    <object type="application/x-silverlight-2">
        <param name="source" value="http://url/player.xap">
        <param name="initParams" value="m=http://url/video.mp4">
    </object>
    Download the video <a href="video.mp4">here</a>.
</video>
  • Aby obsłużyć wiele przeglądarek, w tym także przeglądarki urządzeń mobilnych można użyć różnych widoków dla specyficznych urządzeń lub użyć CSS3 media queries i tagu .
  • Informacje o dostępnych rodzajach widoków są dostępne w System.Web.Mvc.VirtualPathProviderViewEngine.DisplayModeProvider. Domyślnie istnieją tam dwa wpisy mobile i default. Widok mobilny może być stworzony w tym samym folderze lecz musi posiadać słowo mobile w nazwie np Index.Mobile.cshtml.
  • Dostosowanie widoku dla konkretnej platformy i przeglądarki jest możliwe dzięki DisplayModeProvider. Przykład dodania dostosowania widoku dla Windows Phone:
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iemobile")
{
    ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
        ("iemobile", StringComparison.OrdinalIgnoreCase) >= 0)
});

Kiedy serwer otrzyma żądanie iemobile to będzie próbował znaleźć widok Index.iemobile.cshtml.

  • Aby aplikacja była responsywna i dostosowywała się do rozmiarów przeglądarki można użyć stylu CSS i dodanie tagu . Dodatkowo można użyć @media query które sprawdzają pewne funkcje multimedialne jak szerokość, wysokość i kolory np:
/* header */
header .content-wrapper {
    padding-top: 20px;
}
/* logo */
.site-title {
    color: #c8c8c8;
    font-family: Rockwell, Consolas, "Courier New", Courier, monospace;
    font-size: 2.3em;
    margin: 0;
}
@media only screen and (max-width: 850px) {
    /* header mobile */
    header .float-left,
    header .float-right {
        float: none;
    }
    /* logo mobile */
    header .site-title {
        margin: 10px;
        text-align: center;
    }
  • Aby właściwości CSS3 działały dobrze we wszystkich przeglądarkach można użyć rozszerzeń specyficznych dla dostawcy (vendor prefix) np -ms-, -mso- dla Microsoft, -moz- dla Mozilla, -webkit- dla Google i Apple i -o-, -xv- dla Opera. Przykład CSS z rozszerzeniami specyficznymi dla dostawcy:
<style>
.corners
{
    width: 350px;
    margin: 0px;
    background-color: #222;
    color: #fff;
    padding: 8px;
    /* regular style */
    border-radius: 15px;
    /* -moz extension */
    -moz-border-radius: 18px;
}
</style>

Planowanie responsywnego układu UI

  • ASP.NET MVC obsługuje wiele podejść dla użytkowników mobilnych. Można utworzyć nadpisane widoki, które są uniwersalne dla każdego urządzenia mobilnego lub specyficzne dla urządzenia. System.Web.Mvc.VirtualPathProviderViewEngine.DisplayModeProvider ocenia przychodzące żądania i na podstawie wartości userAgent przekierowuje do skonfigurowanego DisplayModeProviders.
  • Innym sposobem jest użycie tagu viewport i @media queries.
  • Biblioteka z repozytorium jQuery Mobile MVC umożliwia wykorzystanie znaczników, aby zapewnić dodatkowe funkcje obsługiwane przez przeglądarkę klienta. Jeśli przeglądarka nie obsługuje funkcjonalności, biblioteka jQuery będzie obniżać funkcjonalności.
  • Można zmodyfikować plik global.asax w celu dostosowania różnych przeglądarek mobilnych, np:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Web.WebPages;
namespace MvcApplication
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            DisplayModeProvider.Instance.Modes.Insert(0, new
                DefaultDisplayMode("windows")
            {
                ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
                    ("Windows", StringComparison.OrdinalIgnoreCase) >= 0)
            });
            AreaRegistration.RegisterAllAreas();
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
        }
    }
}