210 likes | 390 Views
ASP.NET MVC. Patterns and Anti-Patterns: Lessons from the Trenches. Thanks to Our Host. Anti-Patterns: Things NOT to do. Return un-typed ViewData on a GET Pass low-level data-types on POST Pass FormCollection on POST Magic Strings in Views Logic in Views ‘Fat’ Controllers
E N D
ASP.NET MVC Patterns and Anti-Patterns: Lessons from the Trenches
Anti-Patterns: Things NOT to do • Return un-typed ViewData on a GET • Pass low-level data-types on POST • Pass FormCollection on POST • Magic Strings in Views • Logic in Views • ‘Fat’ Controllers • Domain Models passed to and from Views • Exposed Primary Keys in URLs • (unverifiable) Convention over Configuration
Return UntypedViewData on a GET public ActionResultRegister() { ViewData["PasswordLength"] = MembershipService.MinPasswordLength; return View(); } <p> Passwords are required to be a minimum of <%=Html.Encode(ViewData["PasswordLength"])%> characters in length. </p>
Pass Low-Level data-types on POST public ActionResultLogOn(string username, string password) { if (!ValidateLogOn(username, password)) { return View(); } }
Pass FormCollection on POST public ActionResultLogOn(FormCollection collection) { if (!ValidateLogOn(collection["userName"], collection["password"])) { return View(); } }
Magic Strings in Views <p> Please enter your username and password. <%= Html.ActionLink("Register", "Register")%> if you don't have an account. </p>
Logic in Views <%if (((DateTime)ViewData["OrderDate"]) >new DateTime(2009, 9, 1)) { foreach(Productproductin (IList<Product>)ViewData["Products"]) {%> <li><%=product.ProductName%></li> <%} }%>
‘Fat’ Controllers [AcceptVerbs(HttpVerbs.Post)] public ActionResultEdit(intid, FormCollectioncollection) { try { var customer = customerRepository.Get(id); customer.Firstname= collection[("Firstname"]; using(vartx = context.BeginTransaction()) { customerRepository.Save(customer); tx.Commit(); } return RedirectToAction("Index"); } catch { return View(); } }
Domain Models passed betw Views public ActionResultList(intid) { var customer = customerRepository.Get(id); return View("Index", customer); } [AcceptVerbs(HttpVerbs.Post)] public ActionResultEdit(Customercustomer) { customerRepository.Save(customer); return View("Index", customer); }
Exposed Primary Keys in URLs http://host/{Controller}/{Action}/{id} http://localhost/Customer/Edit/34 http://localhost/Product/View/34 http://localhost/Customer/Edit/Bohlen http://localhost/Product/View/WSTV123
(unverifiable) Convention over Configuration public ActionResultRegister() { ViewData["PasswordLength"] = MembershipService.MinPasswordLength; return View(); }
Patterns: Things to DO • Strongly-Typed Model In and Model Out • ViewModels, UpdateModels • Encapsulate View ‘decisions’ in ViewModels • Controller controls the View, NOT the app
Strongly-Typed Model In and Out public ActionResultList(intnumber) { var customer = customerRepository.GetByNumber(number); varcustomerViewModel = newCustomerViewModel(customer); return View(“List", customerViewModel); } [AcceptVerbs(HttpVerbs.Post)] public ActionResultEdit(CustomerUpdateModelupdCustomer) { var customer=customerRepository .GetByNumber(updCustomer.Number); updCustomer.ApplyChangesTo(customer); customerRepository.Save(customer); varcustomerViewModel = newCustomerViewModel(customer); return View("Index", customerViewModel); }
Encapsulate ‘decisions’ in ViewModels <%if (CustomerViewModel.HasRecentOrders) { foreach(Productproductin (CustomerViewModel.Products) {%> <li><%=product.ProductName%></li> <%} }%>
Controller controls the View, not the app [AcceptVerbs(HttpVerbs.Post)] public ActionResultEdit(CustomerUpdateModelupdCustomer) { varcustomerViewModel=customerUpdateService(updCustomer) return View("Index", customerViewModel); }
Suggestions to Extend MVC • MVCContrib (mvccontrib.org) • xVal (xVal.CodePlex.com) • 1000s of JQueryPlugins
Discussion • Other Experiences • Other Challenges • Other Recommendations
Session Wrap-Up • Thoughts and Impressions • Positives and Deltas • Discussion of Topics for Upcoming Meetings