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

Odpalování akcí měnících data odkazem

09.29 - 16. listopadu 2009 | Webdesign

V RESTové architektuře webu byste neměli odpalovat akce, které mění data, pomocí odkazů. Měli byste tak činit pomocí sloves HTTP protokolu (PUT, POST, DELETE). Přejdu fakt, že dnešní prohlížeče znají jen sloveso POST a ostatní musíte simulovat pomocí XHR. Stejně se dostanete do situací, kdy potřebujete nějakou akci měnící data odpalít odkazem.

Abych pro příklad nemusel chodit daleko, vezmu si hlasování k akcím na Tropu. U každé akce se můžete vyjádřit k tomu, zda na akci půjdete, možná se ukážete nebo nepůjdete. Pokud je uživatel nepřihlášený, vidí pod názvem akce tři ikonky s počtem hlasů. Po přihlášení se stanou aktivními a lze s nimi hlasovat. Původně byla tato „tlačítka“ pouhými odkazy na akci, která hlasování zpracovávala. Jenže po tom, co jsem pouštěl některé spidery, začaly přibývat hlasy.

Tím se dostáváme k praktickému důsledku porušení doporučení z prvního odstavce. Problém jsem nakonec vyřešil následujícím vzorem: Do stránky jsem přidal formuláře, které bezpečným způsobem odpalují akce měnící data. Každý formulář má své id. Odkazy, které původně vedly na akce měnící data, nyní odkazují na id jednotlivých formulářů a mají třídu form-submit. Jednoduchý jQuery kód pak projde tyto odkazy a na click jim naváže submit odkazovaného formuláře a ten pak schová.

Stránka vypadá pořád stejně, chová se stejně, ale dělá to jinak. Pokud nemá uživatel povolený JavaScript, tak se po kliknutí na ikonku dostane na správný formulář, který musí odeslat stiskem tlačítka submit. Funkčnost tak zůstává přístupná i bez JavaScriptu. Zároveň nehrozí, že by došlo k nechtěné aktivaci akce nějakým spiderem, který si ošahává odkazy na stránce.

Snad se bude hodit i vám.

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… :)

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

REST aplikace pomocí ASP.NET MVC

11.20 - 26. října 2008 | ASP.NET 2.0

Jednou z důležitých novinek v beta verzi ASP.NET MVC je bezesporu možnost přetěžování akcí a ve spolupráci s filtry na typ požadavku můžeme přejít na REST přístup k architektuře webové aplikace.

Ačkoli si mnoho lidí myslí, že když píšou v ASP.NET MVC, mají rovnou REST out of the box, není tomu tak. V plném RESTu se totiž nedostávají do URL slovesa (akce), nýbrž pouze předměty. Typický scénář CRUD operací může vypadat následovně:

POST /Customers/Create/
GET /Customers/Detail/1
POST /Customers/Update/1
GET /Customers/Delete/1

Takovéto akce snadno pokryjeme s výchozí routou /{controller}/{action}/{id} a řadič CustomersController může vypadat nějak tak:

public class CustomersController : Controller {
  public ActionResult Create(Customer customer) {
    // TODO: create customer
  }

  public ActionResult Detail(int id) {
    // TODO: find customer by id
  }

  public ActionResult Update(int id, Customer customer) {
    // TODO: update customer
  }

  public ActionResult Delete(int id) {
    // TODO: delete customer
  }
}

Jenže POSTování a GETování je jen slabou podmnožinou povolených operací na HTTP protokolu. Správný REST přístup pro CRUD operace by měl vypadat následovně:

POST /Customers
GET /Customers/1
PUT /Customers/1
DELETE /Customers/1

Co je pro to třeba udělat?

Začneme od routy. Přidáme novou routu pro CustomersController:

routes.MapRoute(
  "REST Customers",
  "Customers/{id}",
  new { controller = "Customers", action = "Rest", id = "" }
);

A původní kód obohatíme o pár atributů:

public class CustomersController : Controller {

  [ActionName("Rest")]
  [HttpPost]
  public ActionResult Create(Customer customer) {
    // TODO: save Customer to database
    return new HttpStatusCodeResult((int)HttpStatusCode.OK);
  }

  [ActionName("Rest")]
  [HttpGet]
  public ActionResult Detail(int id) {
    // TODO: load Customer from database
    return View("Detail");
  }

  [ActionName("Rest")]
  [HttpGet]
  public ActionResult Index() {
    // TODO: load Customers from database
    return View("Index");
  }

  [ActionName("Rest")]
  [HttpPut]
  public ActionResult Update(int id, Customer customer) {
    // TODO: update Customer in database
    return new HttpStatusCodeResult((int)HttpStatusCode.OK);
  }

  [ActionName("Rest")]
  [HttpDelete]
  public ActionResult Delete(int id) {
    // TODO: delete Customer from database
   return new HttpStatusCodeResult((int)HttpStatusCode.OK);
  }
}

A máme REST :)

Reference na objekty v ADO.NET Data Services

06.44 - 10. září 2008 | Webdesign

Pokud se rozhodnete pro vaše AJAXové aplikace požívat RESTové služby ADO.NET Data Services, jistě se dostanete do situace, kdy máte komplexní typ a potřebujete vytvořit novou entitu s odkazem na již existující objekt.

Řešení tohoto problému jsem hledal dost dlouho, nakonec bylo celkem jednoduché.

Problém

AJAXově vytvářím entitu, která má vlastnost User, což je reference na uživatele, který danou entitu vytvořil – kterému patří.

Cesta k řešení

Nejprve jsem zkoušel využít něco podobného, jako je u entit, které jsou vráceny pomocí metody GET. Tedy odkaz na jinou entitu je vyjádřen pomocí vlastnosi __deffered a ta má atribut uri, který odkazuje na již existující entitu pomocí jejího URL. Bohužel, to nefungovalo, protože __deffered je metodou POST ignorováno. Kód vypadal asi následujícně:

User: { __deffered: { uri: "/Users(13)" } }

Pak jsem tedy zkusil druhý zoufalý krok. Zkusil jsem použít atribut ID, který je vlastně jediný důležitý pro správné persistování do databáze. Kód následující:

User: { Id: 13 }

Jenže ani to není správná cesta, navíc si tak můžete Usera poškodit.

Konečné řešení

Konečné funkční řešení se od toho původního pokusu skoro neliší, ano byl jsem hodně blízko a vzal to oklikou a až důkladné čtení dokumentace mi otevřelo oči. Vlastnost __metadata byla aktuálně tou správkou, která se měla použít! Takže správný kód vypadá takto:

User: { __metadata: { uri: "/Users(13)" } }

Závěr

Já vím, není to žádná super extra informace, ale já k jejímu získání spotřeboval celkem dost času. Možná to někomu ušetří starosti a hledání… Pokud vás zajímají ADO.NET Data Services – což je velice zajímavá technologie – můžete se přijít podívat na naši přednášku o ASP.NET, kde o nich bude utroušeno několik slov a předvedena pěkná ukázka ve spojení s AJAXem a k databázi budeme přistupovat přes NHibernate místo Entity Frameworku.

Tagy: ,

ASP.NET MVC je venku v preview verzi

07.39 - 10. prosince 2007 | Webdesign

Je tomu pár hodin, co Microsoft uvolnil ASP.NET Extensions CTP. Součástí balíčku je ASP.NET MVC framework, nové extenze pro AJAX, podpora Silverlightu, Scraffolding framework pro WebForms i MVC a podpora REST like datových služeb.

No není toho zrovna málo, jen škoda, že se to nevešelo do 3.5 frameworku. Paralelně má vyjít „Astoria“ alias ADO.NET Entity Framework, což je ORM postavené nad LINQem.

No nebudu déle zdržovat. Stáhnout si můžete ASP.NET Extensions a ještě MVC Toolkit. Přečíst si o nich můžete tady: Zdroje o ASP.NET MVC.