Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Užití repository v řadičích

10.18 - 25. ledna 2009 | ASP.NET 2.0

Dost už bylo infrastruktury, pojďme si ji trochu užít! :) Dneska si ukážeme jak používat naší infrastrukturu, která by nám měla spoustu věcí usnadnit. Proto jí vlastně tady tvořím.

Dependency injection a bindování modelu

Vytvoříme si řadič, který bude pracovat řekněme s uživateli – to je taková věc, která je nutná snad v každé aplikaci. Ruku na srdce, aplikace bez uživatelů je tak trochu k ničemu. Tak pojďme na to!

[HandleErrors]
public class UsersController : System.Web.Mvc.Controller {

  private IUsersRepository _usersRepository;

  public UsersController(IUsersRepository usersRepository) {
    _usersRepository = usersRepository;
  }

  [ActionName("Rest")]
  [AcceptVerbs(HttpVerbs.Post)]
  public ActionResult Create(FormCollection forms) {

    var user = new User();
    if (ModelState.IsValid == false) {
      throw new ValidationException();
    }

    UpdateModel(user, forms.ToValueProvider());
    _usersRepository.Save(user);

    return Json(user);
  }
}

Krátké a úderné. Jde o klasický řadič, který má závislost na repository uživatelů. Tuto závislost si nechává nainjektovat přes konstruktor. O její životní cyklus se nestará, to řeší IoC kontejner. Pak tu máme akci, která zpracovává POST dotazy řekněme na URL /Users, to teď není podstatné. Tato akce by měla vytvořit nového uživatele z dat, která přišla v POSTu (FormCollection).

Pokud nejsou data v pořádku, tak vyhodíme validační výjimku. O to, jak se tato data mapují na model uživatele, se opět řadič nestará, to je věc model binderu. Pokračujeme dále k uložení uživatele, někam. Kam a jakým způsobem je řadiči opět šumák, to je starost repository. Nakonec pošleme zpátky hotového uživatele jako JSON objekt.

A to je pro dnešek vše… :)

Autor: Aleš Roubíček | 6x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Oprava předchozích článků

11.10 - 4. ledna 2009 | Webdesign

Nikdo není neomylný, i já dělam chyby. A poměrně často. Bohužel i ve svých článcích. Proto jsem se jal revidovat sérii předchozích článků o ASP.NET MVC a znovu si prošel jejich kód a ověřil, že funguje.

Prošel jsem tedy následující články:

Doplnění REST

V článku REST aplikace pomocí ASP.NET MVC jsem nastínil, jak vytvořit RESTful přístup k entitám pomocí filtrů akcí. Výslednou ukázku jsem doplnil o výstupy akce. V komentářích padlo několik dotazů, jak vlastně REST služby volat, protože zatím žádný prohlížeč neumí formulář odeslat jinou metodou než POST nebo GET.

Jediným řešením, jak ze stránek REST používat je AJAX. Proto akce vrací JSON. V pohledech pak postačí následující kód:

<% using (Ajax.BeginForm("REST", "Customers", new AjaxOptions { HttpMethod = "POST" })) {%>
  <input type="submit" value="Create New" />
<% } %>

<% using (Ajax.BeginForm("REST", "Customers", new AjaxOptions { HttpMethod = "PUT" })) {%>
  <input type="submit" value="Update" />
  <input type="hidden" name="id" value="10" />
<% } %>

<% using (Ajax.BeginForm("REST", "Customers", new AjaxOptions { HttpMethod = "DELETE" })) {%>
  <input type="submit" value="Delete" />
  <input type="hidden" name="id" value="10" />
<% } %>

<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

Vylepšená verze model binderu

V článku Model binders v ASP.NET MVC jsem ukazoval jak si napsat vlastní model binder. Taky jsem psal že uvedený postup není moc dobrý. Tady je tedy lepší verze:

public class AddressBinder : BinderBase<Address> {
  public override ModelBinderResult BindModel(ModelBindingContext bindingContext) {

    IValueProvider valueProvider = bindingContext.ValueProvider;
    Address address = bindingContext.Model as Address ?? new Address();

    address.Street = GetSafeStringValue(valueProvider, "address_street");
    address.StreetNumber = GetSafeStringValue(valueProvider, "address_street_number");
    address.PostalCode = GetSafeStringValue(valueProvider, "address_postal_code");
    address.Town = GetSafeStringValue(valueProvider, "address_town");

    return new ModelBinderResult(address);
  }
}

K tomu je ale ještě potřeba rozšířit bázovou třídu binderu:

using System;
using System.Web.Mvc;

public abstract class BinderBase<T> : IModelBinder {

  protected static string GetSafeStringValue(IValueProvider valueProvider, string key) {
    ValueProviderResult value = valueProvider.GetValue(key);

    if (value != null) {
      return value.AttemptedValue.Trim();
    }

    return String.Empty;
  }

  protected static bool GetSafeBooleanValue(IValueProvider valueProvider, string key) {
    ValueProviderResult value = valueProvider.GetValue(key);

    return value != null;
  }

  public Type ModelType {
    get {
      return typeof(T);
    }
  }

  public abstract ModelBinderResult BindModel(ModelBindingContext bindingContext);
}

Tím jsme se zbavili závislosti na HttpContextu, binder je lépe testovatelný a hlavně se dá použít i v metodě UpdateModel.

Oprava integrace IoC kontejneru do ASP.NET MVC

V ukázkových kódech článku Inversion of Control v ASP.NET MVC bylo několik chybek, chyběly usingy a některé věci jsem trošku refaktoroval.

Nová verze RoutingMacros.boo

V článku DSL pro konfiguraci URL Routingu v ASP.NET MVC jsem měl jedno makro nedopsané. V nové verzi jsem přidal sekci constraints k makru ignore_route a provedl lehký refaktoring. Plně funkční kód:

import Boo.Lang.Extensions
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Ast.Visitors

import System.Web
import System.Web.Routing

class IgnoreRouteInternal(Route):
  def constructor(url as string):
    super(url, StopRoutingHandler())

  override def GetVirtualPath(requestContext as RequestContext, values as RouteValueDictionary):
    return null as VirtualPathData

def apply_constraints(block as Block, constraints as MacroStatement):
  assert block
  return if not constraints

  block.Add([| route.Constraints = RouteValueDictionary() |])

  for exp as ExpressionStatement in constraints.Block.Statements:
    binary = exp.Expression as BinaryExpression
    block.Add([| route.Constraints.Add($(binary.Left.ToString()), $(binary.Right)) |])

def apply_defaults(block as Block, defaults as MacroStatement):
  assert block
  return if not defaults

  block.Add([| route.Defaults = RouteValueDictionary() |])

  for exp as ExpressionStatement in defaults.Block.Statements:
    binary = exp.Expression as BinaryExpression
    block.Add([| route.Defaults.Add($(binary.Left.ToString()), $(binary.Right)) |])

macro ignore_route:
  path, = ignore_route.Arguments
  constraints = ignore_route["constraints"] as MacroStatement

  block = Block()
  block.Add([| route = IgnoreRouteInternal($path) |])
  apply_constraints(block, constraints)
  block.Add([| RouteTable.Routes.Add(route) |])

  return block

macro map_route:
  name, path = map_route.Arguments
  defaults = map_route["defaults"] as MacroStatement
  constraints = map_route["constraints"] as MacroStatement

  block = Block()
  block.Add([| route = Route($path, MvcRouteHandler()) |])
  apply_defaults(block, defaults)
  apply_constraints(block, constraints)
  block.Add([| RouteTable.Routes.Add($name, route) |])

  return block

macro defaults:
  allowedParents as List = [ "map_route" ]
  parent as MacroStatement = defaults.GetAncestor(NodeType.MacroStatement)
  assert allowedParents.Contains(parent.Name)

  parent["defaults"] = defaults

macro constraints:
  allowedParents as List = [ "map_route", "ignore_route" ]
  parent as MacroStatement = constraints.GetAncestor(NodeType.MacroStatement)
  assert allowedParents.Contains(parent.Name)

  parent["constraints"] = constraints

Autor: Aleš Roubíček | Zatím bez komentáře | Delicious | FriendFeed | Facebook | Linkuj!