asp.net mvc stefan lieser email: [email protected]@lieser-online.de web: :
TRANSCRIPT
WAS IST LEGACY CODE?
Code ohne Tests
ist Legacy Code
Michael Feathers
Code without tests is bad code. It doesn't matter how well written it is; it doesn't matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behavior of our code quickly and verifiably. Without them, we really don't know if our code is getting better or worse.
Agenda
Einführung MVC Pattern Routing Dependency Injection Tests
Controller, View, Routing
Einführung
ZieleFunktionsweiseWas ändert sich gegenüber WebForms?
Einführung
Erste Präsentation durch Scott GuthrieALT.NET Conference Oktober 2007 Austinhttp://www.hanselman.com/silverlight/ScottGuAtAltnetConf/
Bisher CTP1, CTP2, Source Drop,sozusagen pre-CTP3
Ziele
Testability Interfaces, abstract base classes (Mock
Objects) Designed with TDD in mind
Extensibility Keine sealed classes
Pluggability z.B. Austausch der ViewEngine
Einführung
Map URL to Class Front Controller
Einführung
Basierend auf ASP.NET Standard ViewEngine verwendet .aspx,
MasterPages und CSS Kein PostBack Kein ViewState Kein WebForms Keine UserControls Kein ASP.NET AJAX
Auf Wiedersehen
Ok, jetzt geht‘s los
Verwendete Komponenten
ASP.NET MVC (Microsoft) http://www.codeplex.com/aspnet
MVCContrib (Community) http://www.codeplex.com/MVCContrib
Castle Windsor http://www.castleproject.org/container/
Verwendete Komponenten
Rhino.Mocks http://www.ayende.com/projects/rhino-
mocks.aspx
NUnit http://www.nunit.org
ReSharper 4.0 http://www.jetbrains.com/resharper/ http://www.jetbrains.net/confluence/display/
ReSharper/ReSharper+4.0+Nightly+Builds
Model View Controller
und andere Patterns
Model View Controller
Ziele Entkopplung Separation of concerns Single responsibility principle
Model: enthält die Daten View: präsentiert die Daten Controller: vermittelt zwischen Model
und View
Abgrenzung zu MVP
MVP geht weiter als MVC MVC Model: nicht zwangsläufig ein
Business Domain Modell sondern häufig ein Application Modell
MVC Varianten
Front Controller Supervising Controller Passive View
Routing
http://localhost/was/kommt/jetzt
Wie wird eine URL interpretiert?
Routing ASP.NET WebForms
Routing ASP.NET MVC
public class ShopController : Controller{ public RenderViewResult Artikel(string id) { Artikel artikel = repository.Get(id); return RenderView("Artikel", artikel); }}
Der Unterschied
ASP.NET WebFormsView first
ASP.NET MVCController first
Routing ASP.NET MVC
Keine fixe Zuordnung von URL zu Verzeichnissen und Dateien
Stattdessen bestimmen Routingregeln wie eine URL auf Controller und Action abgebildet wird.
Routing Tabelle wird in Global.asax initialisiert.
System.Web.Routing ist Bestandteil des .NET Framework 3.5 SP1
Routing Map
routes.MapRoute( "Default", "{controller}/{action}/{id}“, new { controller = "Home", action = "Index", id = "" }, new { controller = @"[^\.]*" });
Name URL mit Parametern Parameter Defaults Parameter Constraints
Controller, Action und Parameter
{controller}/{action}/{id} URL -> Routing ->
Controller.Action(…) /Backlog/Item/5public class BacklogController : Controller {
public RenderViewResult Item(string id) { BacklogItem item = repository.Get(id); return RenderView("Item", item); }}
Routing
/BlogsBlogsController.List("Lieser", "01.01.2008", "")
/Blogs/Meier/05.07.2006/ein-artikelBlogsController.List("Meier", "05.07.2006", "")
routes.MapRoute( "Blogs", "Blogs/{username}/{date}/{article}", new { controller = "Blogs", action="List", username = "Lieser", date = "01.01.2008", article = "" });
Routing
Signatur MapRoute:(string name, string url, object defaults, object constraints)
Warum sind defaults und constraints vom Typ object? Parameter!!
Beachte den anonymen Typ!new { controller = "…", action = "…", myParameter = "42" }
controller und action müssen vorhanden sein, sonst Laufzeitfehler.
Routing
DemoÄndern der Routing Map
BlogsController
Routing „rückwärts“
Erzeugen einer URL aus Controller und Action:
Erzeugt die URL /Backlog/Add gemäß der Routingregel
DRY – Don‘t repeat yourself Refactoring Intellisense
<%= Html.ActionLink<BacklogController>( a => a.Add(), "Hinzufügen")%>
Controller
Controller
Implementiert IController Alle public methods sind Actions [NonAction] verhindert das Jede Action liefert ein ActionResult
zurück Das ActionResult bestimmt was als
nächstes passiert
Vom Controller zum View
Controller Action liefert ein ActionResult
ExecuteResult(ControllerContext context) wird aufgerufen und rendert den View.
public BacklogController : Controller { public ActionResult Index() { return RenderView("Index"); }}
RenderView
Verzeichnisstruktur bei RenderViewResult
ViewData
ViewPage<T> Property ViewData vom Typ T
ViewPage ViewData vom Typ
System.Web.Mvc.ViewData Dictionary: ViewData["Artikel"] = artikel
ViewData verwenden
<h1>Backlog Item</h1>
Id: <%= ViewData.BacklogItem.Id %><br />Name: <%= ViewData.BacklogItem.Name %><br />
public BacklogController : Controller { public ActionResult Index() { BacklogItem backlogItem = … return RenderView("Index", backlogItem); }}
Vom View zum Controller
Form
Führt zu folgendem Methodenaufruf:BacklogController.Save(string name)
<form action="/Backlog/Save" method="post" > <input type="text" name="name" id="name“ value="<%= ViewData.Backlog.Name %>" /> <input type="submit" value="Ok" name="submit“ id="submit" /></form>
Form mit Html Helper
<% using (Html.Form("Backlog", "SaveItem")) { %> <%= Html.TextBox("name", ViewData.BacklogItem.Name) %> <%= Html.SubmitButton("submit", "Ok") %><% } %>
TestsModelRouting ControllerView
Routing Tests
GetHttpContext ist eine Helper Methode (siehe Demo)
using (mocks.Record()) { context = GetHttpContext(mocks, "~/Backlog/List");}using (mocks.Playback()) { RouteData routeData = routes.GetRouteData(context); Assert.That(routeData.Values["Controller"], Is.EqualTo("Backlog")); Assert.That(routeData.Values["Action"], Is.EqualTo("List"));}
Controller Tests
ActionResult der Actions können gut getestet werden
RenderViewResult renderViewResult = backlogController.List();
Assert.That(renderViewResult.ViewName, Is.EqualTo("List"));
Assert.That(renderViewResult.ViewData, Is.TypeOf(typeof(BacklogViewData)));
Assert.That(((IViewData)renderViewResult.ViewData).ErrorMessage, Is.EqualTo(expectedErrorMessage));
View Tests
Im View steckt Code! -> Unit Tests
ASP.NET Engine erforderlich um einen View zu rendern
Bislang keine explizite Unterstützung für View Tests
Integrationstest z.B. mit WatiN möglich
Alternative View Engines
Im MVCContrib Projekt: Brail NVelocity NHaml Xslt
Dependency Injection
Controller benötigt Repository
public class BacklogController : Controller {
private IRepository<Backlog> m_Repository;
public BacklogController(IRepository<Backlog> repository) { m_Repsitory = repository; }
public RenderViewResult Show(string id) { Backlog backlog = m_Repository.Get(id); return RenderViewResult("Show", backlog); }}
Dependency Injection (DI)
ASP.NET MVC unterstützt den Einsatz beliebiger IoC Container zur Dependency Injection.
Vorteil der Dependency Injection: die Konstruktoren der Controller können Parameter erhalten um darüber die Abhängigkeiten zu definieren.
ControllerBuilder.Current.SetControllerFactory(...)
MvcContrib
Bietet Factories für Castle Windsor StructureMap Spring.NET ObjectBuilder Unity NInject
DI am Beispiel Castle Windsor
Zusätzliche Referenzen: Castle.Core.dll Castle.MicroKernel.dll Castle.Windsor.dll MvcContrib.Castle.dll
Castle
MVCContrib
DI am Beispiel Castle Windsor
protected virtual void InitializeWindsor() { if (m_Container == null) { m_Container = new WindsorContainer();
m_Container.AddComponentWithLifestyle( "repository", typeof(IRepository<>), typeof(Repository<>), LifestyleType.Transient);
m_Container.RegisterControllers( Assembly.GetExecutingAssembly());
ControllerBuilder.Current.SetControllerFactory( typeof(WindsorControllerFactory)); }}
Fragen?
Email: [email protected]: http://www.lieser-online.de
Links
Michael Feathers,Working Effectively With Legacy Codehttp://www.objectmentor.com/resources/articles/WorkingEffectivelyWithLegacyCode.pdf
Vielen Dank!