Category Archives: ASP .NET

ASP.NET MVC – pole autocomplete czyli Text Field z sugerowanymi wartościami

Czasami warto zasugerować użytkownikowi dozwolone wartości w TextBox. Przykładem jest pole edycyjne w Google, które pokazuje najczęściej wyszukiwane frazy. W tym poście stworzymy podobną kontrolkę, wykorzystując do tego bibliotekę jQuery. Całość działa oczywiście na Ajaxie. Użytkownik wpisuje jakieś wartości w polu, zapytanie jest w tle wysyłane do serwera a ten z kolei zwraca listę podpowiedzi. Należy wprowadzić również pewne opóźnienie przed dostarczeniem zapytania ponieważ w przeciwnym razie zostanie wysłanych zbyt wiele niepotrzebnych pakietów.

Najpierw umieszczamy pole edycyjne, które następnie będziemy rozszerzać o możliwość sugerowania podpowiedzi:

<input type="text" id="itemName" name="itemName" value='<%=Request.Form["itemName"] %>' />      

Następnie wystarczyć wstawić poniższy kod w jQuery:

<script type="text/javascript">
    $(function () {

        $("#itemName").autocomplete({
            source: function (request, response) {
                $.ajax({
                    url: "/Tags/FindItems", type: "get", dataType: "json",
                    data: { term: request.term, maxResults: 20 },
                    success: function (data) {
                        response($.map(data, function (item) {
                            return { label: item.Title, value: item.Text, url: item.Url, type: item.Type, id: item.Id }
                        }))
                    }
                })
            },
            select: function (event, ui) {

         

            }
        });

    });
</script>

Jak widać większość rzeczy robi za nas jQuery. Warto zwrócić uwagę na argument url, który zawiera adres zapytania ajaxowego. To właśnie /Tags/FindItems zostanie wywołane w tle w celu pobrania sugerowanych wartości z serwera. Z kolei w ‘select’ możemy zapieścić kod JS, który będzie wywoływany po wybraniu przez użytkownika pozycji w liście.

Na zakończenie jeszcze treść metody FindItems:

public JsonResult FindItems(string term)
{
  Repositories.Search.SearchRepository repository = new Repositories.Search.SearchRepository();
  List<Repositories.Search.SearchResult> results = new List<Repositories.Search.SearchResult>();
  results.AddRange(repository.SearchPoliticians(term));
  results.AddRange(repository.SearchParties(term));

  return Json(results, JsonRequestBehavior.AllowGet);
}

Warto zwrócić uwagę na zwracany typ – JsonResult.

Zwijanie oraz rozwijanie fragmentów strony www

Często na stronie www chcemy umieścić elementy, które powinny być domyślnie zwinięte aby zajmowały mniej miejsca. Na przykład na tej stronie zdecydowałem, że filtrowanie wyników powinno domyślnie być schowane aby zajmowało mniej miejsca. Klikając na link “Pokaż\Ukryj” użytkownik może rozwinąć okno. Warto również zwrócić uwagę na animacje – okno nie pojawia się od razu ale stopniowo jest rozszerzane.

Zaprezentowany wynik można łatwo uzyskać za pomocą biblioteki jQuery. Po ściągnięciu biblioteki oraz podłączeniu stosownych plików JS należy napisać następujący skrypt:

<script type="text/javascript">
    $(document).ready(function () {        
        $("#msg_body").hide();            
    });
</script>

Metoda hide (ukrycie formularza) zostanie wywoła w momencie zainicjowania biblioteki jQuery. Selektor #msg_body oznacza wybranie elementu w którym atrybut id jest równy msg_body. Następnie można przejść do zdefiniowania reszty formularza:

<%using(Html.BeginForm("Search","Politicians",FormMethod.Get)){ %>

<div id="msg_head">

    <a onclick='javascript:$("#msg_body").slideToggle(600);return false' href='#' class="marked_link">
        <img  src="/images/search.png" alt="Filtrowanie" style="float:left" />Filtrowanie (Pokaż\Ukryj)
    </a>

</div>
    
<br />
<div id="msg_body">
    <fieldset>

        <legend>Filtrowanie</legend>
        <p>Imię:
            <br />
            <%=Html.TextBox("FirstName", Request.QueryString["FirstName"]) %>
        </p>

        <p>Drugie imię:
            <br />
            <%=Html.TextBox("SecondName", Request.QueryString["SecondName"]) %>
        </p>

        <p>Nazwisko:
            <br />
            <%=Html.TextBox("LastName", Request.QueryString["LastName"]) %>
        </p>
        <input type=submit value="Filtruj" />
    </fieldset>
</div>
<%} %>

Element div z id równym msg_body zawiera dane, które będą zwijanie oraz rozwijane. Z kolei w msg_head został umieszczony link odpowiedzialny za wykonanie operacji. W zdarzeniu OnClick umieszczono następujący kod JS:

'javascript:$("#msg_body").slideToggle(600);return false'

Kluczem do sukcesu jest metoda slideToggle, która odpowiada za animację zwijania lub rozwijania. Jako parametr wejściowy można przekazać całkowity czas animacji.

ASP.NET MVC – obrazek jako link do asynchronicznego wywołania

W poprzednich postach pokazałem jak tworzyć asynchroniczne formularze. Nie zawsze jednak chcemy wywoływać takie zapytania za pomocą przycisku Submit. Czasami lepiej użyć linku tekstowego lub po prostu obrazka. Ajaxowy link tekstowy bardzo łatwo utworzyć za pomocą metody ActionLink:

<%=Ajax.ActionLink("Usuń","RemoveAssocation",new AjaxOptions(){}) %>

Pytanie jednak co zrobić aby obrazek posłużył jako ajaxowy link? W poście przedstawię może nieeleganckie ale za to bardzo proste rozwiązanie:

<%= Ajax.ActionLink("[replacethis]", ...).Replace("[replacethis]", "<img src=\"/images/remove.gif\" ... />" %> 

Najpierw generujemy zwykły, tekstowy link (<a href=’…’>[replacethis]</a>), a następnie wystarczy zamiast tekstu wstawić obrazek (metoda Replace). Dla porządku w kodzie można napisać rozszerzenie do AjaxHelpera:

static public class AjaxHelperEx
{
   public static string ImageActionLink(this AjaxHelper helper, string imageUrl, string altText, string actionName, object routeValues, AjaxOptions ajaxOptions)
   {
       var builder = new TagBuilder("img");
       builder.MergeAttribute("src", imageUrl);
       builder.MergeAttribute("alt", altText);
       string link = helper.ActionLink("[replaceme]", actionName, routeValues, ajaxOptions).ToHtmlString();            
       return link.Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing));
   } 
}

Teraz w widoku wystarczy, że wywołamy:

<%=Ajax.ImageActionLink("/images/remove.png","Usuń","RemoveAssocation",null,new AjaxOptions(){}) %>

ASP.NET MVC – asynchroniczne wysyłanie formularzy (AJAX) na przykładzie prostego ShoutBox’a

Podobnie jak w czystym ASP.NET, framework ASP.NET MVC pozwala na wysyłanie formularzy w tle. Załóżmy, że chcemy napisać shoutbox’a, w którym wysłanie wiadomości na serwer nie wymagałoby ponownego przeładowania całej strony. Zamiast definiowania formularza za pomocą Html.BeginForm, musimy skorzystać z Ajax.BeginForm:

<% using(Ajax.BeginForm("AddMessage",new AjaxOptions(){ OnComplete="OnFinish", UpdateTargetId="messages"}))
{ %>
// treść
<%}%>

Pierwszy parametr to nazwa akcji. W drugim parametrze przekazujemy klasę AjaxOptions określającą między innymi zdarzenie w JavaScript wywoływane po zakończeniu zapytania oraz identyfikator elementu modyfikowanego w trakcie wykonywania akcji. Stwórzmy więc prosty ShoutBox:

<div  id="messages" style="width: 350px; height: 80px; overflow:auto;">
<%Html.RenderPartial("Messages"); %>
</div>
<%
   using(Ajax.BeginForm("AddMessage",new AjaxOptions(){ OnComplete="OnFinish", UpdateTargetId="messages"}))
   { %>

   <%if(Page.User.Identity.IsAuthenticated==false){ %>
   Nick:
   <br />
   <%=Html.TextBox("Nick", "", new { maxlength = 50, style = "width:220px" })%>
   
   <%} %>
   <br />
   Wiadomość:
   <br />
   <%=Html.TextBox("Message", "Wpisz opinię, która pojawi się na mównicy...", new { maxlength = 500, title = "Wpisz opinię, która pojawi się na mównicy...", @class = "water", style = "width:220px" })%>
   
   
   <input type="submit"  value="Wyślij" />


<%} %>

Najpierw definiujemy element div z ID ustawionym na “messages”. Następnie tworzymy asynchroniczny formularz za pomocą Ajax.BeginForm. Zawartość formularza nie zawiera już żadnej nowości – jedynie standardowe pola: jedno na nick a drugie na wiadomość. Przyjrzyjmy się jeszcze akcji AddMessage:

public ActionResult AddMessage(string message)
{
  Repositories.Shoutbox.ShoutboxRepository repository = new Repositories.Shoutbox.ShoutboxRepository();
  if(string.IsNullOrEmpty(message))
      return PartialView("Messages", repository.GetNewestMessages());

  Entities.ShoutBoxMessage shoutMessage = new Entities.ShoutBoxMessage() { Message = message };
  shoutMessage.ID_MESSAGE = Guid.NewGuid();
  shoutMessage.Date = DateTime.Now;

  if (User.Identity.IsAuthenticated == false)
  {
      if (!string.IsNullOrEmpty(Request["Nick"]))
      {
          shoutMessage.UserName = Request["Nick"];
      }
  }
  else
      shoutMessage.ID_USER = new Guid(System.Web.Security.Membership.GetUser().ProviderUserKey.ToString());

  repository.Create(shoutMessage);
  repository.SubmitChanges();            
  return PartialView("Messages", repository.GetNewestMessages());
}

Kod tworzy wiadomość (klasa ShoutBoxMessage) oraz dodaje ją do baz danych. Następnie akcja zwraca widok “Messages” z zawartością wszystkich wiadomości (repository.GetNewestMessages). Widok częściowy (PartialView) jest dodawany do elementu div (messages) w sposób asynchroniczny. Nie wymaga to oczywiście przeładowania strony. Na zakończenie jeszcze widok Messages.ascx (dodawany do wspomnianego już div):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<PoliticiansPromises.Entities.ShoutBoxMessage>>" %>
<%@ Import Namespace="PoliticiansPromises.Extensions" %>
<% foreach(var item in Model){ %>

<%if(item.ID_USER==null){ 
      
      if(item.UserName==null){
      %>
      <span class="marked_link">~Anonim</span>
      <%}else{ %>
      <span class="marked_link">~<%:item.UserName %></span>
      <%} %>
<%}else{ %>


<span class="marked_link"><%:System.Web.Security.Membership.GetUser(item.ID_USER).UserName %></span>

<%} %>


<span class="small_gray_style"><%: String.Format("{0:g}", item.Date)%></span>
<%=Html.EncodeAndCut(item.Message,500) %>
<br />

<%} %>
<br />

ASP.NET MVC oraz sortowanie wyników po kolumnach

W jednym z poprzednich postów pokazałem jak zrealizować stronicowanie wyników. Kontrolka ListView z czystego ASP.NET miała jeszcze jedną zaletę – możliwość sortowania wyników po kolumnach. W dzisiejszym poście zaprezentuję sposób uzyskania takiego efektu w ASP.NET MVC w którym jak wiemy nie ma żadnych wbudowanych kontrolek.

image 

Dla zwizualizowania, efekt końcowy pokazuję na powyższym screenie. Mamy kilka kolumn, które są linkami. Po kliknięciu na daną kolumnę chcemy aby wyniki były posortowane za pomocą tej kolumny. Po drugim kliknięciu w tą samą kolumnę kierunek sortowania zostaje odwrócony (z DESC na ASC i z ASC na DESC).

Zdecydowałem się na napisanie metody rozszerzającej HtmlHelper:

public static class HtmlHelper
{
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues)
   {
       return SortableColumn(htmlHelper, linkText, columnName, routeValues, "sord", "sidx",null);
   }
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues, string sortTypeParName, string sortColumnParName)
   {
       return SortableColumn(htmlHelper, linkText, columnName, routeValues, sortTypeParName, sortColumnParName);
   }
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues,string sortTypeParName,string sortColumnParName,string anchorName)
   {
       System.Web.Routing.RouteData data = htmlHelper.ViewContext.Controller.ControllerContext.RouteData;
       string actionName = data.GetRequiredString("action");
       
       StringBuilder sb = new StringBuilder();
       var vals = new RouteValueDictionary(routeValues);
       string sidx = String.Empty;
       if (System.Web.HttpContext.Current.Request[sortColumnParName] != null)
       {
           sidx = System.Web.HttpContext.Current.Request[sortColumnParName].ToString();
       }
       //modify the sidx       
       if (vals.ContainsKey(sortColumnParName) == false)
       {
           vals.Add(sortColumnParName, columnName);
       }
       else
       {
           vals[sortColumnParName] = columnName;
       }
       
       string sord = String.Empty;
       if (System.Web.HttpContext.Current.Request[sortTypeParName] != null)
       {
           sord = System.Web.HttpContext.Current.Request[sortTypeParName].ToString();
       }
       //add the sord key if needed      
       if (vals.ContainsKey(sortTypeParName) == false)
       {
           vals.Add(sortTypeParName, String.Empty);
       }
       //if column matches    
       if (sidx.Equals(columnName, StringComparison.CurrentCultureIgnoreCase) == true)
       {
           if (sord.Equals("asc", StringComparison.CurrentCultureIgnoreCase) == true)
           {
               //draw the ascending sort indicator using the wingdings font.  
               sb.Append(" <img src='/images/asc.png' alt='asc'/>");
               vals[sortTypeParName] = "desc";
           }
           else
           {
               sb.Append(" <img src='/images/desc.png' alt='desc'/>");
               vals[sortTypeParName] = "asc";
           }
       }
       else
       {
           vals[sortTypeParName] = "asc";                
       }
       if(anchorName==null)
           sb.Insert(0, System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName,vals));
       else
           sb.Insert(0, System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName, null, null, null, anchorName, vals, null));

       return sb.ToString();
   }
}

Powyższy kod jest przeróbką przykładu znalezionego na jednym z serwisów (nie pamiętam już niestety nazwy). Metoda przyjmuje kilka parametrów:

  1. object routeValues – tak samo jak w przypadku ActionLink (dodatkowe parametry do przekazania w URL).
  2. string sortTypeParName – nazwa parametru QueryString, który będzie przechowywać typ sortowania.
  3. string sortColumnParName – nazwa parametru QueryString, który będzie przechowywać nazwę kolumny po której należy sortować.
  4. string anchorName – dodatkowy parametr przechowujący nazwę kotwicy (np. #comments). Jeśli przekażemy null metoda zignoruje po prostu kotwicę.

Sposób wykorzystania metody jest zdecydowanie łatwiejszy. W widoku wystarczy:

<table class="tableList">
    <tr>
       
        <th><%=Html.SortableColumn("Obietnica", "ModificationDate", null, "promisesSortType", "promisSortColumnName", "promises")%></th>                 
        <th><%=Html.SortableColumn("Źródło", "Source", null, "promisesSortType", "promisSortColumnName", "promises")%></th>
    </tr>
    ...
</table>

W powyższym przykładzie ostatni parametr (promises) odpowiada za dołączenie kotwicy #promises. SortableColumn wygeneruje odpowiedni link z tekstem i obrazkiem symbolizującym kierunek sortowania (zielona strzałka na pierwszym screenie).

Pozostało jeszcze pokazać zawartość kontrolera, który odpowiada za faktyczne posortowanie danych:

public ActionResult Details(Guid id)
{  
  string promisesOrderString = (Request.QueryString["promisesSortColumnName"] ?? "ModificationDate") + " " + (Request.QueryString["promisesSortType"] ?? "asc");
    
  PromisesRepository promisesRepository = new PromisesRepository();
  IQueryable<Promises> allOrderedPromises= promisesRepository.GetAllOrderedBy()(promisesOrderString);
  
  ...
  return View(viewModel);
}

Kluczem jest wygenerowania string’a w formacie ‘COLUMN_NAME ASC’ lub ‘COLUMN_NAME DESC’. W powyższym kodzie promisesOrderString zawiera właśnie taki  string. Następnie używamy jednej z przeładowanych metod OrderBy i przekazujemy promisesOrderString. Należy zaznaczyć, że domyślnie LINQ nie zawiera metody OrderBy akceptującej jako parametr wejściowy zmienną typu string. Należy więc ją dodać jako rozszerzenie (kod klasy z CodePlex).

ASP.NET MVC i stronicowanie wyników

W ASP.NET MVC nie ma wbudowanego wsparcia stronicowania dla wyświetlanych danych. W czystym ASP .NET mieliśmy do dyspozycji wiele zaawansowanych kontrolek, które wspierały mechanizm stronicowania (np. ListView). W ASP.NET MVC interfejs definiujemy za pomocą czystego HTML\XHTML i sami musimy zadbać o możliwość stronicowania. Nie jest to trudne ale wymaga jednak trochę nakładu pracy. Jeśli chcemy mieć elastyczną bibliotekę do obsługi stronicowania to implementacja jest już dość czasochłonna. Dlatego w tym poście przedstawię gotową bibliotekę MvcPager załatwiająca większość rzeczy za nas. Niezbędne pliki możecie pobrać ze strony projektu tutaj. 

Po ściągnięciu plików należy najpierw dodać bibliotekę MVCPager do referencji. Następnie w widoku, który chcemy użyć stronicowania niezbędne jest zaimportowanie biblioteki za pomocą dyrektywy import:

<%@ Import Namespace="Webdiyer.WebControls.Mvc"%>

Następnie można wygenerować elementy graficzne odpowiedzialne za stronicowanie:

<%=Html.Pager(Model, new PagerOptions
{
    
    CurrentPagerItemWrapperFormatString = "<span class=\"cpb\">{0}</span>",    
    NumericPagerItemWrapperFormatString = "<span class=\"item\">{0}</span>",   
    CssClass = "pages",
    PageIndexParameterName = "quotePageIndex",
    NextPageText = ">>>",   
    PrevPageText = "<<<",
    ShowDisabledPagerItems = false,
    FirstPageText = "Pierwszy",
    SeparatorHtml = "" ,
    NumericPagerItemCount=5,
    LastPageText = "Ostatni"
}, "quotes")
    %>

Jak widać biblioteka umożliwia bardzo dokładną konfiguracje. Programista może m.in. określić wykorzystywany styl css lub ilość wyników na pojedynczej stronie. W funkcji Pager jako pierwszy parametr przekazuje się kolekcję danych a konkretnie Webdiyer.WebControls.Mvc.PagedList. Aby stronicowanie działało koniecznie musimy wykorzystać ten konkrety typ ale istnieje konwerter, który umożliwia łatwą zamianę np. IQueryable na PagedList:

Webdiyer.WebControls.Mvc.PagedList<Entities.Quote> quotes=null;
// quotes to IQueryable
pagedQuotes = quotes.ToPagedList(colDefs.QuotePageIndex, colDefs.PageSize);

image

W wyniku powyższego kodu dostaniemy stronicowanie pokazane na screenie. Implementacja samodzielna wcale nie jest taka prosta – głównie ze względu na wyświetlanie numerów stron. Poza tym poco męczyć się z czymś co jest już od dawna rozwiązane i dostępne publicznie za darmo?

ASP.NET MVC, Html.Editor(…), Html.EditorFor(…)

W poprzednim poście przedstawiłem metodę Html.Display. Funkcja Html.Editor(…) w działaniu jest bardzo podobna – również służy do generowania szablonów na podstawie klas. Różnica polega na tym, że Html.Editor tworzy pola edycyjne TextBox. Służy więc do generowania formularzy edycyjnych a nie do prezentowania danych tak jak Html.Display. Sposób wykorzystania jest analogiczny do DisplayFor więc zachęcam do przeczytania poprzedniego wpisu. Poniżej końcowy wynik:

image

Warto wspomnieć, że również można tworzyć własne szablony. Umieszczać należy je w formie widoków częściowych w folderze EditorTemplates (zamiast DisplayTemplates).

ASP .NET MVC, Html.Display(…), Html.DisplayFor(…)

Podczas budowania formularzy często potrzebujemy wyświetlić zbiory danych. Załóżmy, że mamy klasę Contact zawierającą dane kontaktowe użytkownika (email, telefon, fax). W celu wyświetlenia informacji zawartych w klasie można oczywiście użyć zwykłych wywołań Html.Label np:

<p>Phone:</p><%=Model.Contact.Phone%>
<p>Email:</p><%=Model.Contact.Email%>
<p>Fax:</p><%=Model.Contact.Fax%>

Metoda całkowicie poprawna jednak bardzo niewygodna – co jeśli do klasy dodamy nowe pole, np. MobilePhoneNumber? Oczywiście programista będzie musiał modyfikować kod – mało interesująca opcja. Na szczęście ASP .NET MVC dostarcza funkcję Display, która wyświetli wszystkie właściwości:

<%=Html.Display("Contact")%>

ViewData[“Contact”] musi zawierać oczywiście instancję klasy Contact:

Contact contact = new Contact();
contact.Email = "contact@live.com";
contact.Fax = "252525";
contact.Phone = "45353";

ViewData["Contact"] = contact;

Dla ścisłości dołączam jeszcze deklarację Contact:

[MetadataType(typeof(Contact_Metadata))]
public class Contact 
{
   public string Phone { get; set; }
   public string Fax { get; set; }
   public string Email { get; set; }
}

Warto zwrócić uwagę na atrybut MetadataType. Dzięki niemu możemy zdefiniować właściwości pól (długość, typ itp):

internal class Contact_Metadata
{
   [Required(ErrorMessage = "Phone is required.")]        
   public string Phone { get; set; }
   
   [Required(ErrorMessage = "Fax is required.")]
   public string Fax { get; set; }

   [Required(ErrorMessage = "Email is required.")]
   [StringLength(200, ErrorMessage = "Email must be 200 characters or less.")]
   [RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", ErrorMessage = "Valid Email Address is required.")]
   [DataType(DataType.EmailAddress)]
   public string Email { get; set; }

}

Jak wspomniałem Html.Display wyświetli wszystkie pola klasy:

image

Na tym jednak nie koniec zalet płynących z wykorzystania Display. Można definiować własne szablony dla poszczególnych pól. Szablony w formie widoków częściowych należy umieszczać w folderze DisplayTemplates np:

image

Przykładowy szablon dla pola Email (dodaje element graficzny):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>

<a href="mailto:<%= Html.DisplayFor(emailaddress => emailaddress) %>">
    <%= Html.DisplayFor(emailaddress => emailaddress) %>
</a>
<img src="/Content/email.gif" />

W analogiczny sposób można definiować szablony dla całej klasy Contact a nie tylko dla poszczególnych pól. W tym celu dodajemy widok częściowy Contact.ascx i umieszczamy go również w DisplayTemplates:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcApplication2.Contact>" %>

<p>
    <%= Html.LabelFor(c => c.Phone)%>:
    <%= Html.DisplayFor(c => c.Phone)%>
</p>
<p>
    <%= Html.LabelFor(c => c.Email) %>:
    <%= Html.DisplayFor(c => c.Email) %>
</p>
<p>
    <%= Html.LabelFor(c => c.Fax) %>:
    <%= Html.DisplayFor(c => c.Fax) %>
</p>

ASP .NET MVC – widoki częściowe, MasterPages

Widoki częściowe pozwalają na wyodrębnienie ze strony pewnych części do osobnego pliku. Załóżmy, że projektujemy widok strony głównej sklepu internetowego. Z pewnością taki widok zawiera m.in. informacje o stanie koszyka, menu, listę kategorii i produkty w promocji. Można oczywiście wszystkie dane umieścić w jednym pliku aspx. Niestety w takim przypadku plik źródłowy widoku byłby bardzo długi i skomplikowany. Lepszym rozwiązaniem jest zapisanie poszczególnych fragmentów do różnych plików – widoków częściowych.

Widoki częściowe zapisane są w pliku o rozszerzeniu ascx. Sposób definiowania widoku częściowego praktycznie niczym nie różni się od standardowej strony ASP .NET MVC. Jedyną różnicą jest wykorzystanie deklaracji Control zamiast Page:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
Ostatnio dodane produkty.
To jest widok częściowy.

Widok można podpiąć za pomocą RenderParial:

<%Html.RenderPartial("NazwaWidokuCzesciowego");%>

Warto zauważyć, że widok częściowy może również zostać podpięty do innego widoku częściowego. Tworząc formularz wyświetlający profil użytkownika wszelkie dane kontaktowe i adresowe warto umieszczać w osobnych plikach (np. Contact.ascx, Address.ascx). Oprócz czytelności zyskujemy możliwość wykorzystania tych samych widoków na różnych stronach.

Z kolei MasterPages definiują powtarzalny fragment widoku. Bardzo możliwe, że w naszej aplikacji na każdej podstronie, niezależnie od prezentowanej treści, wyświetla się menu i logo. Nie warto w takim przypadku w każdym widoku pisać kod html odpowiedzialny za wyświetlenie loga czy menu. Lepiej zdefiniować szablon w pojedynczym pliku. Do tego właśnie służą MasterPage. Przykładowy szablon:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>

<body>
      <img src='/logo.jpg' alt='logo'/>
     Jakaś treść wspólna dla wszystkich podstron.
    <asp:ContentPlaceHolder ID="MainContent" runat="server" />      
</body>
</html>

MasterPage w odróżnieniu od zwykłych stron zawiera deklarację Master. W celu określenia miejsc w których będzie pojawiała się treść zależna od konkretnej strony używamy ContentPlaceHolder. W powyższym przykładzie zdefiniowano dwa PlaceHoldery – jeden odpowiedzialny za wstrzyknięcie tytułu strony a drugi za wyświetlanie konkretnej treści. Strona wykorzystująca zdefiniowany szablon powinna wyglądać następująco:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Tytul konkretnej podstrony
</asp:Content>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    Treść konkretnej podstrony   
</asp:Content>

Koncepcja MasterPage nie jest nowa i dla programistów ASP .NET WebForms z pewnością jest już znana.