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

Téměř nový rarouš.weblog

10.42 - 23. června 2008 | Moje práce

Poslední týden jsem trávil úpravami tohoto blogu. Měl jsem už nějaký nástřel grafiky už několik dní před tím, dokonce rozběhaný na úvodní straně blogu. :) Jenže abych to mohl posunout dál i do článků, musel jsem šáhnout do šablon blogu. Jenže prezentační vrstva, byla napsaná tak nepoužitelně a každá změna vyžadovala dost práce.

Měl jsem před sebou dvě možnosti. Nechat to být a počkat až napíšu nový systém, nebo upravit stávající. Jenže čekat sám na sebe, až něco napíšu, to bych se taky nemusel dočkat… ;) Nakonec jsem napsal zbrusu novou prezentační vrstvu nad starým systémem.

Rozhodl jsem se pokračovat v započatém stylu, který jsem zvolil pro svoji homepage, tedy návrhový vzor MVP pro prezentační část a Repository pro datovou. Využil a trochu rozšířil některé entity modelu o další vlastnosti a přidal některé nové. Pro výpis článků a komentářů na stránce jsem napsal serverové prvky založené na Generickém repeateru. Pak jsem se dostal k formuláři pro přidávání komentářů. Aktuální WebForms implementace se mi moc nelíbila a tak jsem se rozhodl oprášit NForms a povolat je do služby.

Musel jsem odladit několik bugů, protože tohle bylo poprvé, co jsem je použil. :) Ale nakonec se z toho vyklubal celkem použitelný a elegantní kus softwaru. Proč elegantní? Líbí se mi definování validačních pravidel (sice zatím pouze serverových), znovupoužitelnost kódu apod.

NForms v akci

Když jsem vymýšlel, jak vlastně budu nově komentáře zpracovávat, rozhodl jsem se pro handler, který bude obsluhovat požadavky na akce a zavolá správnou třídu, která má požadavek zpracovat – v tomto případě ukládač komentářů. (Tento model jsem zvolil protože, každá třída by měla dělat pouze jednu věc, proto komentáře nezpracovává ta samá, co prezentuje články.) Jenže teď jsem stál před problémem, že budu muset definovat ten samý formulář na dvou místech. Naštěstí u NForms to není třeba.

Podědil jsem novou třídu CommentForm z formuláře NForms. V ní jsem nadefinoval, jaká políčka a s jakými pravidly se mají vytvořit. Kód vypadá nějak tak:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using Rarous.NForms;
using Rarous.NForms.Validation;

namespace Rarous.Net.Components {
  public class CommentsForm : Form {
    public const string ArticleUrl = "comment_article_url";
    public const string ArticleId = "comment_article_id";
    public const string Validity = "comment_validity";
    public const string Text = "comment_text";
    public const string UserName = "comment_user_name";
    public const string UserEmail = "comment_user_email";
    public const string UserWeb = "comment_user_web";
    public const string UserAnswer = "comment_user_answer";
    public const string Submit = "comment_submit";

    public CommentsForm()
      :this(null) {
    }

    public CommentsForm(NameValueCollection data)
      : this(data, null) {
    }

    public CommentsForm(NameValueCollection data, List<string> messages)
      : base(data, messages) {
      CreateForm();
      BindData();
    }

    private void CreateForm() {
      AddHidden(ArticleUrl);
      AddHidden(ArticleId);
      AddHidden(Validity);

      AddTextArea(Text, "Text komentáře:", 10, 55).
        Required("Text příspěvku je povinný.");
      AddText(UserName, "Jméno:").
        Required("Jméno je povinné.");
      AddText(UserEmail, "E-mail:").
        SetEmptyValue("@");
      AddText(UserWeb, "Web:").
        SetEmptyValue("http://");

      AddText(UserAnswer, "Odpověď je pivo:").
        AddRule("Zadejte pivo.", s => String.Compare("pivo", s, true) == 0);

      AddSubmit(Submit, "Přidat komentář");
    }
  }
}

Tento formulář pak použivám ve View i handleru. V handleru se vytváří s daty poslanými přes POST, která se pak validují a dále zpracovávají. Líbí se mi, že data se předávají jako NameValueCollection, díky tomu se dá bez problému načítat z Request.Forms, Request.QueryString, nebo z kolekce Cookies.Values. Těmi můžu data předvyplňovat, nebo je nechat validovat a tak. V budoucnu by mohlo přibýt i inteligentní vytváření objektů…

Další featury

  1. U komentářů jsem oprášil Gravatary, které jsem tu už kdysi míval. Použil jsem implementaci z dotnetKicks, kde se používá lokální cachování.
  2. Bezpečnostní otázka se vyplňuje JavaScriptem, takže už vás nebude otravovat.
  3. Po letech jsem přidal stránkování v článcích. Jak na hlavní stránce blogu, tak v rubrikách.
  4. Zvýrazňovač syntaxe teď funguje jen napůl, protože už přestal stačit mým potřebám. Čekám na uvolnění zvýrazňavoče z Codeplexu, který by měl být o dost lepší.

Jsou to takové drobnosti, ale mám z nich radost, protože jsem je dlouho dobu odbýval.

Závěrem

Ještě mi zbývá pár věcí dopsat, aktualizovat odkazy v článcích na nové URL, ale už jsou to spíš drobnosti (doufám). Ještě bych chtěl závěrem poděkovat Davidovi a Honzovi, protože u nich jsem se dost inspiroval. Taky děkuju Exíkovi za hodnotné připomínky, Jerrymu za hosting a naší zahradě za příjemné pracovní prostředí atd.

MVC na ASP.NET – Testování jednotek

16.15 - 1. prosince 2007 | Webdesign

Již delší dobu jsem chtěl napsat článek o tom, proč je MonoRail lepší, když chceme, aby se projekt dal testovat. Jenže vždycky nějak nebyl čas. Navíc se blíží vypuštění ASP.NET MVC, takže nakonec z toho bude trošku obecnější pojednání než jsem původně plánoval.

WebForms testování

Než si ukážeme jak snadné je testovat MVC, nejprve si povíme, že i WebForms se dají testovat. Ano, ale vyžaduje to větší kázeň a víc práce. :) Většinou stejně použijete MVP pattern nebo jeho blízké příbuzné. Pokud tedy nechcete psát testy, které simulují livecycle stránky a dekódují ViewState. (Ano, dotahuju to ad absurdum, ale i tak to může vypadat.)

Aby se tedy kód dal snadno testovat, měl by se co nejvíce oddělit od stránky.

Klasickým modelem tedy je využití MVP vzoru, kdy aspx stránka obsahuje pouze logiku pro prezentaci dat a implementuje rozhranní View, přes které komunikuje s Presenterem. Díky tomu, že Presenter obsahuje téměř veškerou logiku, je ideálním místem pro zacílení našich testů. Pro účely testů pak implementujeme stub nebo mock rozhranní view. Na něm pak sledujeme jak ho Presenter ovlivňuje.

Předchozí diagram ukazuj, že Presenter zná rozhranní View. Stránka, která toto rozhranní implementuje, se však musí postarat o to, aby byl presenter ve správnou chvíli zavolán. Možný scénář zachycuje ukázka kódu.

public class BlogViewPage : System.Web.UI.Page, IBlogView {
  BlogPresenter presenter;

  protected void Page_Init(object sender, EventArgs e) {
    presenter = new BlogPresenter(this); // injekce View
  }

  protected void Page_Load(object sender, EventArgs e) {
    presenter.LoadArticles();
  }

  // implementace IBlogView ...
}

Na to, že to stránka udělá správně, se už musíme spolehnout. Otestovat i takto jednoduchý kód, je už trošku náročnější práce…

MVC testování

MVC frameworky jako MonoRail nebo ASP.NET MVC, přicházejí s podobným schématem by default. V podstatě nás přímo nutí psát web tak, aby se dal lépe testovat. :) Nebudu přehánět, když řeknu, že ASP.NET MVC byl psán se zřetelí na velice snadné testování a obsahuje i sadu mock objektů (např. pro HttpContext). MonoRail dokonce přináší i Fixtures a Asserty pro snadnější testování – bohužel jsou psané pouze pro NUnit, který nepoužívám.

Tento diagram se od předchozího mírně liší. Controller např. netuší jaké má View rozhranní, proto s ním nikterak nepracuje. Prostě zavolá RenderView(*název*, *objekt s daty*) a spoléhá se na ViewFactory že všechno zařídí. Navíc stránka do poslední chvíle netuší, jestli něco bude dělat. Tady totiž proces zpracování začíná u routingu, který vybere správný controller, viz předchozí díl.

Dostáváme se i k lepšímu objektovému návrhu, kde každá třída je zodpovědná za provedení pouze jednoho úkolu (stránka v MVP těch úkolů řeší hned několik). To se opět lépe testuje. Máme sadu několika komponent s jednoduchými závislostmi a každá má na starosti pouze jednu část z procesu.

Pro dnešek to tady ukrouhnu. Bude-li nějaké příště, ukážeme si nějaké praktické ukázky. Možná :)

Související