Daniel Kolman

Jak naprogramovat blog za 18 minut

| 7 comments |

Dnes jsme s lidma z práce měli přednáškový blok na hradecké univerzitě, bylo to moje poprvé na akademické půdě! Během mé části vznikla jednoduchá webová demo aplikace postavená na ASP.NET MVC 3, využívající Entity Framework 4.1 Code-First pro uložení dat, Razor jako view engine a Markdown syntaxi pro formátování textu. Protože vymezený čas byl dost krátký, mám pár poznámek, na které nedošlo.

Když to shrnu, pokusil jsem se napsat "kompletní" "blog" za 18 minut. Proč 18 minut? Abychom byli o minutu lepší než Nette a o dvě minuty lepší než Ruby on Rails:-) A jak to dopadlo? Nakonec to trvalo stejně jako v Nette (19 minut), ale zato s dvěmi kůl fíčurami navíc!

kompletnejsi

Na konci tohoto postu najdete slajdy z přednášky a záznam z dema, ale nejdřív vás musím trochu varovat.

Za 18 minut se totiž blog naprogramovat nedá. Ani žádná jiná aplikace. A to ani v ASP.NET MVC, ani v Ruby on Rails, a dokonce ani v Nette. Za 18 minut můžete stihnout jen takovou malou ochutnávku toho, jak se s kterou platformou pracuje. Co bych tedy v reálné aplikaci udělal jinak? (Poznámka: Nějak jsem se rozjel, takže kdykoliv toho budete mít dost, přeskočte na sekci Závěrem)

Testování

Začal bych unit testy. Mám totiž zkušenost, že psát testy před kódem pomáhá navrhnout strukturu tříd a jejich závislostí. Psát unit testy až po té, co napíšete kód, přináší hrozně malou přidanou hodnotu. A nejlepší je s testováním začít, dokud je aplikace ještě mladá:-)

ASP.NET MVC bylo sice vytvořeno tak, aby testování bylo jednoduché, ale záleží jak napíšete Controller. Pokud vaše controllery závisí na objektech, které nelze v testu nahradit, nebude možné napsat jednoduchý test (no dobře, bude to možné, ale jen za cenu strašlivých prasáren). V našem případě je tím hlavním zlem třída BlogDataContext, odvozená od DbContext.

V Entity Framework 4.1 lze tento problém naštěstí celkem elegantně vyřešit. Místo property typu DbSet<T> můžete použít rozhraní IDbSet<T> a místo aby se v Controlleru použila přímo implementační třída BlogDataContext, můžete si vytvořit rozhraní s metodou SaveChanges() a repository pro každou entitu. Více si o tomto přístupu můžete přečíst zde.

Co se mi dál na BlogDataContext nelíbí je, že obsahuje repository pro všechny entity ve vaší aplikaci. V takto malém demu to není vidět, ale představte si, že máte v aplikaci třeba 40 entit. Každý controller pak dostane k dispozici univerzální super-objekt, který se bude s každou novou entitou měnit. Také metoda SaveChanges() je taková podezdřelá, co všechno se při jejím zavolání vlastně uloží? Může tak k dojít uložení i jiných změn, než které způsobíme v metodě controlleru? A teď si představte, že budeme chtít explicitně pracovat s transakcemi (velmi reálný a rozumný požadavek). Je to stále zodpovědnost controlleru? Začíná se nám to nějak komplikovat.

Aplikační logika

Jak by to tedy mělo ideálně vypadat? Controller by měl zůstat co nejjednodušší. Pokud možno by měl pouze zavolat aplikační logiku, sehnat data a předat je view. Jednou z možností, jak to udělat hezky čistě, je navrhnout lokální message bus a nechat metodu controlleru pouze poslat zprávu aplikační vrstvě a pak odpověď předat view. Zajímavý příklad naleznete zde (implementační detaily jsou popsány zde). Díky tomu controller potřebuje pouze jeden jednoduchý objekt a nebude vědět nic o detailech aplikační logiky, persistence atp. Taková architektura bude navíc krásně testovatelná.

Tím se dostáváme k tomu, že objekty, které reprezentují "M" v MVC, zřejmě nebudou ty samé, jako jsou ukládány do databáze. Entity, které slouží k ukládání dat, validaci a vykonávání aplikační logiky, budou schovány někde uvnitř aplikační vrstvy. Controller si bude s aplikační vrstvou vyměňovat jiné objekty, někdy zvané Data Transfer Objects (DTO), a pro view může konstruovat speciální objekty na míru, viewmodely.

Persistence dat

V demu jsem pro ukládání dat použil Entity Framework, a to hlavně proto, že je optimalizovaný pro dema všeho druhu;) Zda je to vhodná volba pro větší aplikaci je otázka. Na poli ORM je totiž nekorunovaným králem NHibernate – robustní, spolehlivý, léty vyzkoušený. Ne že bych chtěl nějak shazovat jiné frameworky, ale pokud už NHibernate používáte, nemáte důvod nic měnit. Pokud chcete používat NHibernate podobným stylem jako Entity Framework, vyzkoušejte Castle.ActiveRecord. Lze to nainstalovat i jako Nuget balíček. Bohužel dokumentace není úplně ideální.

Zajímavá varianta (zvlášť pro blog) by byla ukládat data jinam než do relační databáze. Nabízí se buď nějaká NoSQL databáze (např. RavenDB), nebo ukládat data přímo do souborů.

Bezpečnost

Internet je dnes dost nepřátelské místo, a tak je zabezpečení aplikace proti útokům hackerů něčím, čemu se prostě nevyhnete. Některé typy hrozeb za vás pořeší platforma. Např. kvůli cross-site scripting (XSS) útoku nelze do formuláře zadat některé "nebezpečné" kódy, jako třeba HTML tagy. Také view engine použitý pro generování HTML (Razor) pomáhá tím, že vše co vkládáte do stránky, je HTML-enkódováno.

Obranu proti SQL Injection dnes většinou velmi dobře řeší ORM frameworky, ale měli by jste alespoň vědět, jak to funguje a kdy se vás to může týkat. Je neuvěřitelné, že i dnes můžete nalézt fungující weby, kde i takto základní věc není ošetřená, a to i po několik let. Například zkuste jít na stránku http://www.czholding.cz/kraj/index.htm, zadejte apostrof jako kód a odešlete formulář. Zobrazí se návod, jak to hacknout.

Zajímavější je cross-site request forgery (XSRF), která útočníkovi dovoluje provádět některé akce pod jménem jiného uživatele. ASP.NET MVC bohužel tuto hrozbu neodvrátí samo od sebe, musíte do formuláře přidat tzv. AntiForgeryToken a každou akci controlleru, kde se něco provádí s daty, odekorovat atributem [ValidateAntiForgeryToken]. Více si o tomto typu útoku můžete přečíst zde.

Závěrem

Pokud se vám teď zdá, že je toho trochu moc a je to složitý, nevěšte hlavu. Ve skutečnosti jsem toho pro jednoduchost hodně vynechal. Ale nejvíc ze všeho záleží na tom, jakou aplikaci děláte. Vše to, co jsem zmínil, jsou vyzkoušené best practices pro vývoj větší aplikace v týmu několika lidí. Pokud si chcete jen vyzkoušet ASP.NET MVC, v klidu na to zapomeňte:-) Anebo ještě lépe, zkuste si pro váš příští projekt vybrat jednu věc, kterou uděláte jinak. Přečtěte si nějakou dobrou knížku (třeba Growing Object Oriented Software nebo Clean Code, jak doporučoval Miro Bajtoš) a zkuste něco z toho vyzkoušet v praxi. Happy coding!

Dobře, byli jste poučeni. Teď už vám snad můžu ukázat ten screencast bez toho, aby jste mě na twitteru a v komentářích sežrali;)

ASP.NET MVC
View more presentations from danielkolman

(7) Comments

  1. Borek Bernard said...

    Moc hezký.

    2. listopadu 2011 9:32
  2. Lukáš Doležal said...

    Zasekl jsem se u Database.SetInitializer(new DropCreateDatabaseIfModelChanges, bylo by možné poradit jak přesně ta řádka má vypadat, nedaří se mi to vyřešit a v tom videu to není.

    8. října 2012 14:37
  3. D.Mareček said...

    Zdravím, vím, že tento příspěvek je staršího data, ale i tak to zkusím. Snažil jsem se jít podle videa, ale zasekl jsem se u přidání komentáře (ve videu v čase 13:46), ať dělám co dělám, tak to na mě řve u "@foreach(var comment in Model.Comments)" NullReferenceException, když před to hodím podmínku na null, tak se mi zase komentáře vůbec neukládají...

    31. května 2013 4:39
  4. Daniel Kolman said...

    Pokud si vzpomínám má to vypadat takhle:
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BlogDataContext>());

    31. května 2013 12:40
  5. D.Mareček said...

    V MVC4 to bude hlásit chybu, takto by to mělo být v pořádku:

    Database.SetInitializer(new DropCreateDatabaseIfModelChanges());

    31. května 2013 12:43
  6. D.Mareček said...

    Ignorujte můj předchozí příspěvek, ztratila se mi zavorka. V závorce samozřejmě musí být , akorat mě to nechtělo VS pokousat, ale s to jede bez problémů.

    31. května 2013 12:47
  7. Daniel Kolman said...

    NullReferenceException asi bude způsobená tím, že Model je null, to znamená že metoda BlogCotroller.Post (čas 12:39) nenašla žádný Post. Může to být tím, že se mezi vytvořením blogpostu a zobrazením změnil model, a tak se zahodila databáze, nebo tím že ta metoda dostává špatné id. Zkontroluj si že se v linku na post předává id a pak zkus vytvořit nový post.

    31. května 2013 12:47

Leave a Response