ASP.NET MVC Stefan Lieser Web:
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 Ziele Funktionsweise Was ändert sich gegenüber WebForms?
Einführung Erste Präsentation durch Scott Guthrie ALT.NET Conference Oktober 2007 Austin 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 gehts los
Verwendete Komponenten ASP.NET MVC (Microsoft) MVCContrib (Community) Castle Windsor
Verwendete Komponenten Rhino.Mocks NUnit ReSharper er/ReSharper+4.0+Nightly+Builds er/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 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 WebForms View first ASP.NET MVC Controller 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/5 public class BacklogController : Controller { public RenderViewResult Item(string id) { BacklogItem item = repository.Get(id); return RenderView("Item", item); }
Routing /Blogs BlogsController.List("Lieser", " ", "") /Blogs/Meier/ /ein-artikel BlogsController.List("Meier", " ", "") routes.MapRoute( "Blogs", "Blogs/{username}/{date}/{article}", new { controller = "Blogs", action="List", username = "Lieser", date = " ", 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 – Dont repeat yourself Refactoring Intellisense ( a => a.Add(), "Hinzufügen")%>
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 Property ViewData vom Typ T ViewPage ViewData vom Typ System.Web.Mvc.ViewData Dictionary: ViewData["Artikel"] = artikel
ViewData verwenden Backlog Item Id: Name: 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) <input type="text" name="name" id="name value=" " /> <input type="submit" value="Ok" name="submit id="submit" />
Form mit Html Helper
Tests Model Routing Controller View
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 m_Repository; public BacklogController(IRepository 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? Blog:
Links Michael Feathers, Working Effectively With Legacy Code
Vielen Dank!