asp mvc – from zero to hero (4), MVC architektura, view-viewmodel-controller, razor

Marian Benčat       23.11.2015       C#, ASP.NET MVC, Architektura, .NET       19889 zobrazení

Vítám vás u čtvrtého dílu seriálu ASP MVC – from zero to hero. Přechozí díly seriálu můžete nalézt zde:

http://www.dotnetportal.cz/clanky/autor/montella

Dnes se podíváme na architekturu ASP MVC  - konkrétně první 2 části této svaté trojice – tedy View a Controller. Mějte prosím na paměti, že se jedná o základy a mírně pokročilé věci ohledně této dvojice, složitější věci ohledně architektury atd., přijdou až v dalších dílech. Zároveň bych rád upozornil na to, že tento díl seriálu (a následující přibližně 4), píši již podruhé, jelikož mi to editor bohužel smazal. Toto mě vedlo k rozhodnutí, že budu jednotlivé díly seriálu vydávat sice menší, ale při větší frekvenci.

Na co se tedy dnes koukneme?

  • Těstoviny a pohádka o nich.
  • architektura MVC,
  • Controller, View, Layout, RenderSection, RenderBody, PartialView,
  • Razor, ViewEngine, syntaxe,
  • komunikace View a Controlleru, Data pro View,
  • ViewModel,
  • Model? ViewModel? View Specific Model? Data Object?

V dalším díle naleznete:

Scaffolding, UIHint

Bundling

Validace a další aspekty

Lokalizace

Helpery, Vlastní helpery, Fluent API

XHR – AJAX

Tipy a Triky pro View

 

Začněme tedy od začátku. Kdysi nebyl jeden návrhový vzor MVC,…

 

Pohádka o škaredých těstovinách

Architektura kódu se neustále vyvíjí, v následující části se koukneme na to, jaké jsou její horší varianty a také na to, čeho si přejeme dosáhnout. Podrobněji se na architekturu ideálního řešení koukneme v dalších dílech seriálu, kde se budeme zabývat tzv. SOC (seperation of concerns).

Spaghetti

Jeden z prvních “těstovinových hororů” v kódu. Vezmeme všechno co máme a napereme to ideálně všechno do 1 souboru, ať je to na jednom místě. Nebudeme rozlišovat co jaký kus kódu dělá, jednoduše to dáme vše dohromady a vyplivneme do prvního souboru, který nás napadne. Například index.php. Tento přístup přináší několik nevýhod. Jedna z nich je  míchání věcí, které spolu nijak nesouvisí. Bohužel se mi nepovedlo sehnat ukázku “lepšího” kódu - věřte ale, že na internetu naleznete i ukázky, kde se v tomto samém souboru leze do databáze, provádějí výpočty a podobné věci.

Tento přístup přináší obrovské množství nevýhod. Těžko se udržuje, těžko se upravuje a těžko se testuje. Kromě toho vám hrozí, že vám za takovýto kód někdo v temné uličce roztrhá diplom,.. tedy snad pouze diplom. Takovýto kód lze popsat jednoduše pomocí špaget. Dlouhý nepřehledný kód, nikdo neví kde končí, kde začíná, jaká je jeho zodpovědnost a co dělá. Chcete vytáhnout jednu špagetu, ale musíte dát na stranu nejdříve všechny ostatní.

imageimage

 

Hodnocení děsivosti: naprosto děsivé

Lasagne

Opakem kódu předchozího, kdy máte vše v jednom souboru a v jedné třídě je lasagne kód, tedy kde je funkcionalita rozdělena ne nepochopitelně mnoho vrstev, obsahující naprosto zbytečnou abstrakci a celkově ´je kód tzv. “overengineered”. Na světě není krásnější ukázky, než Enterprise implementace hry FizzBuzz, kterou pro pobavení naleznete na této adrese. Jedná se o implementaci jednoduché hry, která se zadává občas na přijímacích pohovorech pro zjištění, zda zvládáte alespoň základní podmíněné větvení programu. Na screenshotu můžete vidět například implementaci “vložení” nové řádky do standardního výpisu.

image

image

Lasagne kód = kód který je zbytečně složitý, než by musel být a nepřináší žádné výhody (snad kromě jistě přínosného know-how v podobě výuky nových sprostých slov u studie kódu). Lasagne jsou tedy v kódu stejně špatné jako špagety.

Hodnocení děsivosti: naprosto děsivé

Ravioli

Kód který je správně rozdělený na jednotlivé vrstvy a nemíchá dohromady věci, které k sobě nepatří – například UI, aplikační logiku, práci  s daty atp., se dá přirovnat k Ravioli. Jednotlivé části jsou striktně odděleny, je přesně definováno, jaká část kódu má dělat co. Za odměnu dostanete přehledný kód, který je lehce rozšiřitelný a čitelný. Zároveň vám nebude problém ho otestovat, případně jakoukoliv část vzít a zaměnit ji za jinou. Klíčem k tomuto je dbání na několik principů, které jsou shrnuty v SOLID, SOC a pomůže nám k tomu například abstrakce. Blíže si o tom všem ještě povíme.

image

Hodnocení děsivosti: pohádka

architektura MVC

V našem kódu se pokusíme co nejvíce psát Ravioli kód. K tomu nám pomůže několik návrhových vzorů. Prvním z nich je návrhový vzor MVC, který je - jak již název napovídá, základem ASP MVC. Tento návrhový vzor se dá vyobrazit tímto diagramem:

image

Tvoří ho 3 role. Model, View a Controller. Každá tato část má za úkol jiné věci – má jinou zodpovědnost a neměli bychom je míchat. V dnešní části se budeme věnovat View a trochu se koukneme i na Controllery. K čemu jsou tyto jednotlivé části tedy určeny?

View

View je ta vrstva, která slízne smetanu za vaši práci, protože ta jediná je pro uživatele vaší aplikace vidět. View je vaše prezentační vrstva. Má za úkol vzít určitá data, která dostane od Controlleru a ty nějakým způsobem zobrazit. Kromě tohoto úkolu, musí ještě dávat vědět Controlleru, že došlo k nějaké akci v klientovi – kterým je u webových aplikací prohlížeč.

Pokud uživatel na něco klikne, případně nastane nějaká jiná událost, prohlížeč zašle požadavek na server, kde se dostane přes mašinerii routování až ke Controlleru. Controller se na základě vstupních dat od klienta nějakým způsobem zachová (většinou zavolá nějakou metodu v Modelu, odpověď Modelu přetransformuje a vrátí zpět jako odpověď klientovi.)

Zatímco u ostatních dvou vrstev uvidíte pouze C# kód, View se bude skládat především ze směsi HTML, CSS, JavaScriptu a speciální syntaxe šablonovacího enginu Razor. I ve View lze psát C# kód, není to ale dobrý přístup a pokud vám View obsahuje C#´kód, většinou to indikuje nepochopení celého principu MVC.

View tedy komunikuje s Controllery a k Modelu nemá přímý přístup.

 

Model

Model je místem, kde by se měla vyskytovat byznys logika vaší aplikace, odkud jsou věci řízeny. Většinou není model takto plochý, ale bývá rozdělen na několik vrstev, které zajišťují logiku, přístup do databáze, mapování na / z byznys objektů (DTO) a tak podobně. Tomuto se budeme věnovat za pár dílů. Pro začátek vám bude stačit vědět, že logika aplikace by měla být v modelu a naopak by se neměla vyskytovat například v controlleru.

Co je byznys logika je potřeba odlišit například od logiky UI a nastanou i situace, kdy není úplně jednoznačné, kam danou věc umístit – kde ji implementovat. Často je nutné ji implementovat vícekrát na více vrstvách, typickým příkladem je validace vstupních dat – například formulář ve vaší webové aplikaci.

Je velmi vhodné validovat vstupní data již na klientovi, aby nebylo při každé změně nutné odesílat data na server, když je jisté, že nejsou validní – už na klientovi. Vzhledem k tomu, jak lehce jde  kód v prohlížeči upravit (případně modifikovat data na cestě po síti), musíme validovat data zároveň i na serveru – většinou v controlleru. Standardně tedy validaci implementujeme jak na klientovi (view vrstva), tak třeba v controlleru. Je potřeba ale validovat data i v modelu?

Model bývá často v .NETu projekt typu class library, který není nijak závislý na MVC, tedy de facto není ani nijak vázaný na web.  Je tedy chybou dělat validaci vstupu i na modelu? Je to jedna z metod defenzivního programování, konkrétně ošetření vstupu. Nebudu zde doporučovat, zda je vhodné psát validaci i na modelu, je to čistě na vás a závisí to například i na tom, zda budete chtít tuto  část vašeho kódu sdílet i na jiném, než ASP MVC projektu.

Model komunikuje přímo pouze s controllerem a k view nemá přímý přístup.

 

Controller

Poslední část trinity má za úkol obstarávat komunikaci mezi View a Modelem. Často zde dochází také k transformaci dat – z Data Objectu na ViewModely a naopak. Najdeme zde také pár řádek na validaci vstupních dat. Controllery v ASP MVC vracejí vždy nějaký výsledek akce  - ActionResult, tento výsledek může být cokoliv vás napadne. Od jednoduchého HTML (vrací View), Data, přesměrování, až po například REST data, případně skvělé ODATA end pointy. Nejste tedy nijak omezeni.

Controller  je ve své podstatě API, které volá klient. V okamžiku, kdy pošlete request na server, dostane se na konkrétní controller, ke konkrétní akci controlleru.

Zde se koukneme na způsob zpracování requestu, který přijde na server:

(zdroj: http://blog.thedigitalgroup.com/chetanv/)

1) První co je potřeba udělat, je rozhodnout se na základě requestu a routovacích pravidel, který controller a jaká akce bude zpracovávat požadavek,

2) po zvolení controlleru, je potřeba ho vytvořit (ano, controller je implicitně vytvářen při každém requestu) a controlleru poskytnout věci, které potřebuje – dochází k zjišťování závislostí. K tomuto se nejčastěji používá nějakého DI containeru. My budeme používat Unity a Castle windsor. v novém ASP 5, je však Dependency injection už v základu frameworku,

3) po vytvoření controlleru je nutné ještě zvolit akci, která ke zpracování requestu existuje. Poté dochází k akci, které se říká model binding. Jednotlivé části requestu se v .NETu namapují na vaše .NET objekty, podle určitých pravidel (nejčastěji  typ a název dat v requestu). Přesto, že je tento základní Model binding už poměrně inteligentní, občas si budeme muset napsat vlastní. To si také ukážeme Veselý obličej ,

4) akce controlleru vrací nějaký ActionResult, pokud se jedná o View (nebo jeho variace – viz později), ve vaší aplikaci se najde konkrétní View (.cshtml), to se zpracuje – provede se Razor kód a výsledek se pak vrátí jako ˇodpověď klientovi.

Pokud není akcí nějaké View, vrátí se data rovnou.

Do celého tohoto procesu lze jednoduše zasáhnout a chování upravit. Slouží nám k tomu nejrůznější filtry a interceptory. Můžeme tedy upravit request, než dorazí ke controlleru (například interceptnout proces, pokud uživatel nemá na něco právo), nebo naopak můžeme například upravit odpověď, kterou vygeneroval controller, před tím, než ji vrátíme klientovi – například JSON data zabalit ještě do nějakého balíku nesoucí metadata. Toto se nám velmi bude hodit, jak brzy zjistíme.

Controller, View, PartialView, Layout

Pro pokračování v této části si otevřeme solution, tam kde jsme skončili. Pokud ho nemáte, můžete si ho vytvořit jednoduše pomocí založení nového ASP MVC projektu (viz. předchozí díl), nebo stáhnout zde:

Po otevření solutionu budete muset povolit obnovu NuGet balíčků a solution zbuildit, tím se automaticky balíčky postahují.

image

Nyní zajděte do složky AppStart a otevřete soubor RouteConfig.cs. Jak již soubor napovídá, zde se nastavují jednotlivé routy. Tedy, můžete zde nastavit na jaký controller a jakou akci se má požadavek přesměrovat (kde se má požadavek zpracovat) na základě konkrétní URL.

image

Podrobnosti ohledně routingu si povíme v některém z dalších dílu o controllerech, nyní nám bude stačit opravdu jednoduchý popis. Jako první v metodě najdeme IgnoreRoute. Zde je to z toho důvodu, že pokud soubor na disku neexistuje (pokud ho IIS nenajde), postoupí zpracování requestu MVC routování, tato řádka toto chování upravuje a říká, že všechny requesty, které směrují na *.axd, mají být ignorovány a nemají být routěny pomocí ASP MVC.

Další část této metody je pro nás o hodně důležitější. Všimněte si především řádky URL a řádky Defaults.

Řádka URL nám říká, jak má vypadat URL žádosti, aby tato žádost byla odchycena (vyhovovala) této routě. V tomto případě zde najdeme "{controller}/{action}/{id}". Tedy daná adresa má obsahovat název controlleru, název akce a parametr id.

Řádka Defaults nám dále říká, že pokud není uveden název controlleru, má být použit controller home. Pokud nebude uveden název akce, má být zvolena akce controlleru index. Poté zde najdeme část id = UrlParameter.Optional. Tím jsme řekli, že parametr ID u této routy může být null. (Toto lze i říci v controlleru pomocí NULLable typu – např. int? id).

Na základě tohoto pravidla, budou vyhovovat např. tyto URL této routě:

/home/index/5

/home/index/

/home/index/jiricek

/home

/

Dejte si pozor na to, že nepovinné části routy se berou “zprava”, stejně jako je tomu u nullable parametrů metod. Tedy například u URL:

/index/

se vezme jako controller “index” a jako akce “index” a u URL:

/index/jirik

se vezme controller “index” a akce “jirik”. Nikoliv controller “home”, akce “index” a id “jirik”.

Ještě je důležité zmínit – přesto, že se k tomu dostaneme později, že se vždy použije první Route, které vyhovuje Url. Vybírá se tedy shora dolů, tak jak po sobě následuje registrace v kódu.

Nyní se již můžeme podívat na HomeController.cs, který se nachází ve složce Controllers. Do všech public metod Controlleru (které se nazývají u controlleru akce), si umístíme breakpoint pomocí F9 a aplikaci spustíme.

info

Aplikaci můžeme spustit buďto s debuggem (F5), nebo bez debugu Ctrl+F5, případně klikneme pravým tlačítkem na libovolný Html soubor a dáme View In Browser. Pokud nemáme spuštěný debug, jsme schopni celý kód libovolně upravovat, přidávat soubory do solution a další věci. Po úpravě v C# kódu, je však potřeba opět zbuildit solution, aby se změny projevily. I při debugu je možné dělat změny v C# kódu, aniž by jste museli vypínat a zapínat znovu aplikaci. Stačí kliknout pravým tlačítkem na váš webový projekt a v záložce web zapnout Edit and Continue.

image

Tato možnost vám dovolí při “přistání” na breakpointu upravit velkou část kódu a poté pokračovat dále ve vykonání programu.

Po spuštění se nám hned aplikace zastaví na breakpointu v akci Index:

image

Je to logické, jelikož URL vypadá takto: “/”, tedy controller se zvolí defaultní Home a akce defaultní Index. To samé i pro ostatní akce:

image

 

ActionResult

Každá akce controlleru vrací určitý výsledek. Je několik druhů těchto výsledků a my se na ně jistě v dalších dílech koukneme. Všichni mají společné jedno a sice, že dědí od typu ActionResult. Většinou pokud vrací akce *Result, uvidíme v akci vracet výsledek jako return *;

Tedy například pro ViewResult bude akce vracet return View(), pro PartialViewResult return PartialView() atd.

Akci Index() by jsme tedy mohli změnit takto a nic by se nezměnilo:

public ViewResult Index()
        {
            return this.View();
        }

 

Pro začátek nám bude stačit, pokud budeme znát 2 základní ActionResulty a to je View a PartialView. Jak jste se asi už mohli dovtípit, View nám vrací samotnou stránku, stejně tak jako PartialView, přesto je mezi nimi podstatný rozdíl, který si brzy ukážeme. Pro pochopení si ale nejdříve musíme vysvětlit, jak se výsledná HTML stránka poskládá.

Pokud kliknete v akci About na View(); pravým tlačítkem a dáte “Go to view” (případně si toto View najdete ručně ve složce Views/Home/About.cshtml, uvidíte kódu skutečně pomálu.

image

Kde se vezme ten zbytek stránky? Patička? Hlavička? Styly? Odpovědí je Layout. Rozložení stránky, jakási kostra, kam se vždy výsledek dané akce (pokud je to View/PartialView) includne. View najdeme ve složce Views/Shared/_Layout.cshtml. Pro WebForms vývojáře můžeme říci, že Layout je takový MasterPage.

image

V okamžiku, kdy tedy vrátíme jako výsledek nějaké akce View(), automaticky se použije Layout. Než se koukneme na rozdíl mezi View a PartialView, zastavme se zde u Layoutu. Kromě standardního HTML, zde také najdeme speciální tagy začínající na @. Tyto tagy náleží šablonovacímu enginu Razor, přesněji R@zor.  Tagum @Styles a @Scripts se budeme věnovat později, stejně jako dalším, nyní se zaměříme na dva a to @RenderBody a @RenderSection.

@RenderBody a @RenderSection

Namísto @RenderBody se do Layoutu vloží konkrétní vew, které vrací akce controlleru. Pokud tedy jste na andrese /home/about, naleznete namísto RenderBody v layoutu kód z About.cshtml. RenderSection je velmi podobný, avšak je trošku užitečnější.

Umožňuje totiž definovat v Layoutu místo, kam lze načíst různé části View. Dejme tomu že by jsme chtěli do patičky stránky (Layoutu) umístit vždy nějaký text z View. Definujeme si tedy v patičce novou sekci:

<footer>
            @RenderSection("InFooter")
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>

V  běžném View si tuto sekci jednoduše definujeme pomocí @section InFooter {   }:

Pokud zajdeme na /Home/About, zjistíme, že nám stránka bez problémů funguje:

image

Při příchodu na Index, dostaneme ale exception, která nám přesně říká, kde je problém:

image

Index.cshtml (ani žádný další) nemá totiž definován sekci InFooter – tu jsme definovali pouze v About.cshtml. Pokud v Layout.cshtml upravíme naši @RenderSection() o druhý parametr false, určíme tím, že tato sekce není povinná.

image

 

info 

Sekcí se používá velmi často právě pro různé hlavičky a patičky. Zároveň ho téměř pokaždé nalezneme na stránkách jako optimalizaci načítání JavaScriptu. Kdy se JavaScript potřebný pouze pro stránku “A”, načítá až na stránce “A” v @section Scripts, tak jako můžete vidět ve vygenerované šabloně. Načítání JavaScriptu až za tělem stránky je také jedna z optimalizací loading time.

Jak se určí Layout u View

V okamžiku, kdy se vrátí z akce controlleru View, se na základě názvu controlleru a akce, začne hledat soubor Viewčka ve složce Views. Controller Home, akce About –> Views/Home/About.cshtml. V okamžiku, kdy je soubor nalezen se engine podívá, zda se v něm nenachází explicitně stanovený Layout pomocí kódu:

@{
    Layout = "PathToLayout";
}

Pokud ano, použije se layout na této cestě – pokud není layout nalezen = chyba.

V okamžiku, kdy se zpracovává View, se engine pokouší najít i soubor _ViewStart.cshtml a to ve stejné složce jako se nachází view a pokud nenajde, postupuje o složku výše. Kód tohoto souboru se automaticky vykoná. Takto lze sdílet různé nastavení proměnných pro view na stejné úrovni a nižší,  například – jak už asi tušíte, nastavení Layout. Těchto _ViewStart.cshtml lze mít více a lze tak ovlivňovat view, jaké potřebujete na zákaldě hierarchie ve složkách (v solutionu)

image

View() vs PartialView()

Z akce můžeme kromě View vracet i PartialView. Narozdíl od View(), které se vloží do Layoutu, PartialView vloženo nikam není, pokud tedy změníme v About metodě return View() za return PartialView(), dostaneme pouze obsah stránky About.cshtml:

image

 

Výsledek bude stejný i v případě, kdy sice vrátíme View(), ale v .cshtml povíme, že Layout je null:

image

¨info

Přesto, že je výsledek View s Layout=null a PartialView stejný, upřednostňujte připravenou variantu s PartialView(). Je totiž rychlejší než vracení standardního View, které navíc ještě hledá soubor _ViewStart.cshtml a kontroluje nastavení Layoutu. PartialView s Layoutem nikdy nepočítá a tak rovnou zpracovává samotné View.

Razor, ViewEngine, syntaxe

ASP MVC má v základu dva šablonovací (chcete-li renderovací) enginy - Razor a ASPX (Web Forms View Engine). My se bdueme věnovat tomu novějšímu, který přišel právě s ASP MVC / WebPages a to je Razor. ASPX přenecháme pánům z WebForms. Implicitně jsou v ASP MVC zapnuty oba dva enginy, což může trošku brzdit – minimálně se vyhledávají View nejen s příponami .cshtml (CSharp Razor) a .vbhtml (Visual Basic Razor), ale i ASPX, což se nám příliš nehodí a hlavně je nám to k ničemu. Z tohoto důvodu si ASPX engine odebereme a necháme si pouze Razor.

Otevřeme si soubor Global.asax a v metodě Application_Start přidáme následující dvě řádky:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());

Razor

Syntaxe Razoru je velmi jednoduchá a rychle se ji i naučíte. V podstatě vše začíná znakem zavináče @, Pomocí razoru lze psát jakékoliv konstrukty jazyka C#, navíc lze jim i vložit blok kódu, pomocí @{}, takže ani složitější textové konstrukty nejsou problém – prostě složíte string tak jako to děláte v C#. Nebudu zde ručně opisovat celou tabulku Razoru a tak prachsprostě ukradnu tabulku z mého oblíbeného blogu Haacked.com:

image

image

image

 

 

komunikace View a Controlleru, Data pro View

Jak jsem již zmínil, mezi Víew a Controller probíhá určitá komunikace. Controller připravuje data pro view a view naopak odesílá určité requesty, které dorazí do akce (public metody) controlleru. Předat data pro view, můžeme z controlleru několik způsoby. Popíšeme si jak jednotlivé způsoby fungují povíme si jejich klady a zápory a jedno velké pokrevní tajemství  mezi těmito způsoby.

Pro tuto potřebu si v controlleru nasimulujeme datové úložiště pomocí statického pole stringu, které nám bude reprezentovat emaily. Proč statické? Jak jsem již řekl, pro každý request se vytváří vždy nový controller a tak je nutné mít toto úložiště dostupné i mezi jednotlivými requesty.

Přidejme si nový controller EmailController. Vytvoříte ho kliknutím pravým tlačítkem na složku Controllers->Add->Controller. V nově otevřeném okně zvolte, že chcete prázdný controller.

V controlleru budeme mít dvě akce – List() a Add() a statické readonly pole na naše emaily. Váš controller by měl vypadat tedy nějak takto:

public class EmailController : Controller
 {
     private static readonly List<string> EmaiList = new List<string>
     {
         "[email protected]",
         "[email protected]"
     }; 

     // GET: Email
     public ActionResult List()
     {
         return View();
     }

     public ActionResult Add()
     {
         return View();
     }
 }

 

 

View Specific Model  (aka “@model”)

Prvním způsobem jak můžeme předat data View je poslat je jako parametr při vracení View. Abychom si to vyzkoušeli, musíme si nejdříve přidat View pro List – nyní ho vidíme červeně a IDE nám hlásí, že View neexistuje. Klikněme na “View” pravým tlačítkem a zvolme Add View. Prozatím nebudeme využívat generování View a tak necháme vše v implicitním nastavení:

image

 

 

V controlleru View předáme data

public ActionResult List()
      {
          return View(EmailController.EmaiList);
      }

Nyní je potřeba tyto data použít. Jakmile předáváme přes model, můžeme k těmto datum přistoupit pomocí @Model ve View. Uděláme si jednoduchou tabulku:

@{
    ViewBag.Title = "List";
}

<h2>List</h2>

<div class="container">
    <div class="row">
        <table>
            <thead>
                <tr>
                    <th>
                        Email:
                    </th>
                   </tr>
            </thead>
            <tbody>
                @foreach (string email in Model)
                {
                    <tr>
                        <td>
                            @email
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    </div>

Výsledek:

image

Toto je první a nejčastější způsob jak předávat data View, tedy pomocí modelu. Pokud ve View přejedeme myší nad @Model zjistíme, že je typu dynamic. Tedy dynamický a jaká je dynamika? Špatná! Pokud nemá View stanovený přesný typ vstupních dat (model), tak není považováno za tzv. strongly-typed.

Toto se nám tedy rozhodně nelíbí a my budeme chtít říci, jakého přesně typu náš @Model je. Toto uděláme jednoduše pomocí @model (s malým m). Víme, že vstupní data do View, jsou typu List<string>().

image

 

ViewData

Dalším způsobem, jak lze poslat nějaká data do View, je pomocí ViewData. ViewData je Dictionary přístupné kdekoliv v controlleru. Zacházíme s ním tedy jako s asociativním polem:

image

:

ViewData nijak nemusíme předávat View, jsou automaticky k dispozici.

ViewBag

ViewBag je podobně jako ViewData přístupný kdekoliv v Controlleru. Oproti ViewData je ale ViewBag dynamic a to jak v Controlleru, tak ve View:

image

 

Pokrevní tajemství

Hlavní způsob jakým předávat data do View, by měl být model, jelikož vám poskytuje strongly typed kontrolu nad vaším kódem a další věci, aniž by jste museli přetypovat. Sice vám IDE s Resharperem občas i ve View napoví u ViewBagu, jaké obsahuje informace, ale i tak musíte vrácenou hodnotu nejdříve přetypovat, než s ní budete moci plně pracovat. ViewBag a ViewData se používají poskromnu a spíše v případech, kdy jste líní dělat nějaký složitější model, případně více modelů a různě využít jejich vzájemnému dědění  a kompozici.

Přesto, že by jste se měli snažit vnímat ViewBag jako jedno velké zlo, vám něco prozradím. ViewBag a ViewModel jsou ve skutečnosti jen ViewData.

@Model je to samé, jako by jste napsali ViewData[“Model”], pokud tedy pošlete do View(data), je to to samé, jako by jste udělali v controlleru ViewData[“Model”] = data;

Oproti tomu ViewBag je naopak pouze obalující object nad ViewDaty, který z tohoto slovníku dělá dynamic objekt. Pamatujete si na přechozí díly? Říkal jsem, že dynamický typ je v .NETu pouze syntactic sugar pro Dictionary. Zde je tedy ViewBag pouze syntactic sugar pro ViewData. Pokud tedy napíšete:

ViewBag.Promenna = 5;

jedná se o stejnou funkci i pokud by jste napsali:

ViewData[“Promenna”] = 5;

TempData

Ještě je velmi dobré zmínit zde TempData, přesto, že jejich význam je úplně jiný, než ostatních. TempData je speciální Dictionary, které se udržuje i do následujícího JEDNOHO requestu. Lze ho tedy použít pouze v případě, kdy jste si jistí, jaký bude další request. TempData se na tajno uloží do Session s tím, že se tam uchová pouze do dalšího requestu. Z tohoto důvodu jediné, kde se dá TempData použít, aniž by jste se střelili do nohy, je Redirect.

Ptáte se k čemu jsou TempData vůbec dobrá? Zkuste si zde na DotNetPortalu přidat nějakou zprávu do nějaké diskuze a pak ji smažte. Po smazání zmáčkněte F5, objeví se vám následující okno prohlížeče:

image

 

 

Takovéto chování lze snadno zrealizovat i na MVC. Prohlížeč chce při zmáčknutí F5 znovu odeslat vaši akci z “minula”, což bylo smazání vašeho příspěvku. Čím je toto způsobeno?

1) Chcete akci pro nějaké například přidání dat z formuláře.. budete mít tedy POST akci na serveru

2) V Post akci po přidání do databáze vrátíte View.

3) Z pohledu prohlížeče se vykonalo POST /data/add  a jako návratová hodnota mu přišel content, tedy zobrazí opět stránku formuláře a nastaví URL na /data/add

4) Při F5 se prohlížeč snaží zopakovat Request, kterým dostal právě zobrazený obsah, tedy opět zkusí poslat POST (který obsahoval původně i data z formuláře), zeptá se tedy, zda je má přiložit znovu.

Správné řešení je tzv. PRG pattern, což je zkratka pro  Post-Redirect-Get,  neboli v POST akci data uložíte a přesměrujete klienta na normální GET request, který vrací View.

Právě k implementaci věcí jako je PRG pattern, nám poslouží skvěle TempData. Toto je ale záležitost Controlleru a povídáme se na to podrobněji v dalších dílech. Zpět k View.

 

Komunikace z View do Controlleru

Pokud chceme z View (klient – browser) komunikovat s Controllerem, jedná se o standardní HTTP komunikaci. Zde můžeme využít klasických dvou metod – GET a POST. Zatímco GET posílá hodnoty v URL – tedy například /email/add/[email protected], POST posílá data v těle requestu. Toto doufám již všichni znáte. Nyní se podíváme jak se tyto metody chovají ve spojení s nastavením ASP VC routes a jak se namapují do controlleru.

Pro ukázku si do View přidáme jednoduchý formulář do view využívající GET metodu:

<div class="row">
      <form action="/email/add/pepicek" method="GET">
          <input type="submit" value="odeslat." />
      </form>
  </div>

A v controlleru do Akce list přidáme jeden parametr a ten pak přidáme do našeho statického pole emailu:

public ActionResult Add(string email)
     {
         EmailController.EmaiList.Add(email);
         return View("List", EmailController.EmaiList);
     }

Přidáme si brakepoint a aplikaci spustíme, zkusíme odeslat nový email.

image

Zjistíme ovšem, že Email je NULL. Proč? Get requesty a jejich parametry se mapují na základě routy, pokud si tedy otevřeme RouteConfig.cs a přidáme tam tuto routu:

routes.MapRoute(
          name: "EmailRoute",
          url: "email/add/{email}",
          defaults: new { controlleer = "Email", action = "Add", email = UrlParameter.Optional }
      );

 

Hodnota se nám do controlleru správně namapuje. Zde můžeme vidět, že se bindují hodnoty na základě názvu.

image

 

image

pitfall

Všimněme si ještě jiných parametrů u View. Jako první parametr můžu použít string a říci, jaké přesně View chci zobrazit, druhý parametr je poté můj model. Na toto si dejte pozor, pokud máte jednoduchý model tvořený stringem, například string model = “jirik” a zavoláte return View(model), tak se ve skutečnosti použije přetížená verze metody, která jako první přijímá název View! MVC tedy nebude brát “jirik”, jako data, ale jako název View!

 

Druhou obvyklou metodou, jak poslat data, je POST. Odstraňme naši přidanou routu a upravme si formulář tímto způsobem:

<div class="row">
       <form action="/email/add/" method="POST">
           <input type="text" name="email" />
           <input type="submit" value="odeslat."/>
       </form>
   </div>

image

Při POST metodě se použijí hodnoty z těla requestu, opět se bindují na základě názvu – ve formu klasicky stanovíme název pomocí attributu name u inputu. Takto můžeme poslat libovolné množství dat, i složitější struktury.

info

Data můžete posílat libovolně pomocí GET a POST requestu. Přesto platí poučka, že GET použijete v momentě, kdy chcete pomocí URL stanovit určité místo ve vaší aplikaci, kdežto POST, pokud potřebujete pouze odeslat nějaké data, vykonat nějakou akci atp. Z tohoto důvodu nikdy neposílejte pomocí GET citlivé informace. Zaprvé jsou vidět v URL adrese a za druhé se velmi těžce ošetřují některé druhy útoků – jiné ani nejdou. Bezpečnosti webové aplikace v ASP MVC se budeme věnovat v samostatném díle.

 

Složitější modely

Ukažme si, jak se bindují složitější modely. Změníme si Email na objekt – data objekt Email. Kromě samotné hodnoty “value”, bude ještě obsahovat FirstName, LastName a bool hodnotu NotFromWeb indikující, zda tento email NEBYL přidán skrz webovou aplikaci. Zde nám bude tedy vyhovovat, že bool je implicitně false. Při přidání do “databáze” se tedy automaticky – pokud nenastavíme jinak, nastaví NotFromWeb = false.

Vytvoříme si tedy classu do složky Models:

public class Email
   {
       public string Value { get; set; }
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public bool NotFromWeb { get; set; }
   }

 

A upravíme si i Controller, aby pracoval s třídou Models.Email:

public class EmailController : Controller
    {
        private static readonly List<Email> EmaiList = new List<Email>
        {
            new Email{ Value =  "[email protected]", FirstName = "Jirik",  LastName = "Jirikuv"},
            new Email{ Value =  "[email protected]", FirstName = "Petr",  LastName = "Petrikuv"}
        }; 

        // GET: Email
        public ActionResult List()
        {
            ViewData["ViewDataTest"] = 123456;
            ViewBag.ViewBagTest = 6654321;
            
            return View(EmailController.EmaiList);
        }
        
        public ActionResult Add(Email email)
        {
            EmailController.EmaiList.Add(email);
            return View("List", EmailController.EmaiList);
        }
    }

 

A pozměníme i naše View:

@using DotNetPortal.Models;

@{
    ViewBag.Title = "List";
}

@model List<Email>

<h2>List</h2>
<h3>ViewData: @ViewData["ViewDataTest"]</h3>
<h3>ViewBag: @ViewBag.ViewBagTest</h3>

<div class="container">
    <div class="row">
        <table>
            <thead>
            <tr>
                <th>
                    Email:
                </th>
                <th>
                    FirstName:
                </th>
                <th>
                    LastName:
                </th>
            </tr>
            </thead>
            <tbody>
            @foreach (Email email in Model)
            {
                <tr>
                    <td>
                        @email.Value
                    </td>
                    <td>
                        @email.FirstName
                    </td>
                    <td>
                        @email.LastName
                    </td>
                </tr>
            }
            </tbody>
        </table>
    </div>
    <div class="row">
        <form action="/email/add/" method="POST">
            <input type="text" name="email.value"/>
            <input type="text" name="email.firstname"/>
            <input type="text" name="email.lastname" />
            <input type="submit" value="odeslat."/>
        </form>
    </div>
    </div>

Po spuštění, vše funguje tak, jak jsme očekávali:

image

Nic nám tedy nebrání takovýmto způsobem zobrazovat ve View libovolně složité struktury a naopak libovolně složité struktury zasílat serveru. Všimněte si v kódu tečkové konvence u name=”” attributu inputu. Zároveň vás mohlo zaujmout, že máme @using ve View, abychom nemuseli všude psát celý namespace k nalezení našeho Email modelu. V dalším díle si ukážeme, jak nastavit všem View určité vlastnosti – například namespace, pomocí nějaké základní BasePage, ze které budou všechny View dědit.

ViewModel

Než se dostaneme k ViewModelu, musíme si vysvětlit jednu věc. Email.cs obsahuje třídu Email o které jsme si řekli, že je data object, tedy že reprezentuje data, které se týkají logiky. Tím máme na mysli logiku aplikace a né logiku UI. Je tedy v pořádku, že je posíláme View jako data? Není. V dalších dílech se budeme čím dál častěji dostávat k vylepšením naší architektury, jaké objekty kam předávat, kde jaké objekty používat a kde ne. U každého použití si ukážeme výhody takového řešení. Pro tuto chvíli se ale koukneme na využití ViewModelu, jako filtru automatického bindování hodnot.

Jak jsem již řekl, Email.cs nám slouží jako reprezentace Emailové adresy včetně jména a příjmení majitele. Nyní přidáváme instanci této třídy v akci  Add() do kolekce a bool “NotFromWeb” je nastavován na false (defaultní hodnota bool), což přesně chceme. Bude to takto vždy? Ve formuláři přeci máme pouze vstup pro 3 hodnoty – email, jméno a příjmení.

Koukněme se na následující problém:

Formulář si ručně upravme v prohlížeči pomocí vývojářské konzole takto:

<form action="/email/add/" method="POST">
            <input type="text" name="email.value">
            <input type="text" name="email.firstname">
            <input type="text" name="email.lastname">
              <input type="text" name="email.notfromweb" value="true" />
            <input type="submit" value="odeslat.">
        </form>

A odešleme nový záznam, na server dorazí takováto odpověď:

image

Je to z toho důvodu, že díky úpravě formuláře se nám na server odeslala i hodnota pro NotFromWeb (je v těle requestu), automatický ModelBinder, který binduje hodnoty requestu na hodnoty parametru akce (v tomto případě Email email), nabinduje i tuto hodnotu. Jednoduchou úpravou formuláře nám tedy kdokoliv může narušit logiku naší aplikace. Tomuto problému se říká OverBinding a je taky jedním z útoku na webové aplikace obecně.

Řešení jsou primárně 3, kde až poslední je pro nás ideální. Dekorátor [Bind], přímý přístup přes FormCollection a ViewModel.

Dekorátor Bind[]

Pomocí dekorátoru Bind[] můžeme explicitně říci, které hodnoty chceme bindovat (Include=””), nebo naopak nebindovat (Exclude=””)

image

Include je přímým opakem Exclude – tedy musíme vyjmenovat veškeré property, které si přejeme nabindovat od ModelBinderu.

 

Přímý přístup přes FormCollection

U akce můžeme také dát parametr typu FormCollection. Jedná se o jednoduchý Dictionary, obsahující všechny hodnoty, které nám v requestu přišly.

image

Hodnota form[“cokoliv”] je ale vždy string, budeme tedy často nuceni ručně přetypovat a případně i kontrolovat, zda bylo přetypování úspěšné. Toto se stává často velmi pracné.

 

ViewModel

Na pomoc nám přichází vzor ViewModel. ViewModel, neboli View Specific Model je “věc”, která obsahuje informace pro prezentační vrstvu. Nemusí obsahovat nutně pouze data – která bývají z pravidla nějakou podmnožinou dat z Data Objectu, ale i nějaké detaily ohledně reprezentace těchto dat ve View – například, jak se mají zobrazit, jak se mají validovat atp. Tyto informace se vztahují jen a pouze k reprezentaci dat a z toho důvodu by neměly být v nějakém Data Objectu, který se pohybuje napříč celou aplikací (například v servisách, v data access layer atp.

ViewModel může obsahovat opravdu hodně informací, které nám opravdu pomohou s generováním frontendu, s validací a s opravdu hodně věcmi, nyní se ale koukneme pouze na to, jak ViewModel používat.

Vytvoříme si novou složku ViewModels v solution exploreru a v ní vytvoříme EmailViewModel.cs, zároveň si do něj vytvoříme ručně mapování na Email a naopak. Výsledná třída by měla tedy vypadat nějak takto:

public class EmailViewModel
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Value { get; set; }

        public EmailViewModel()
        {
        }

        public EmailViewModel(Email email)
        {
            FirstName = email.FirstName;
            LastName = email.LastName;
            Value = email.Value;
        }

        public EmailViewModel(string firstName, string lastName, string value)
        {
            FirstName = firstName;
            LastName = lastName;
            Value = value;
        }

        public Email ToEmail()
        {
            return new Email
            {
                Value = this.Value,
                LastName = this.LastName,
                FirstName = this.FirstName
            };
        }
    }

 

Mapování z ViewModelu na Model je řešení pomocí metody ToEmail(). Mapování na ViewModel je pomocí constructoru. Všimněte si, že mám v kódu i bezparametrický constructor. Ten je potřeba pro ModelBinder. Pokud by jste neměli k dispozici bezparametrický constructor a využívali tento ViewModel jako parametr akce, aplikace vám spadne.

Nyní je potřeba upravit ještě controller:

public class EmailController : Controller
 {
     private static readonly List<Email> EmaiList = new List<Email>
     {
         new Email{ Value =  "[email protected]", FirstName = "Jirik",  LastName = "Jirikuv"},
         new Email{ Value =  "[email protected]", FirstName = "Petr",  LastName = "Petrikuv"}
     }; 

     // GET: Email
     public ActionResult List()
     {
         ViewData["ViewDataTest"] = 123456;
         ViewBag.ViewBagTest = 6654321;

         var data = EmailController.EmaiList.Select(email => new EmailViewModel(email)).ToList();

         return View(data);
     }
     
     public ActionResult Add(EmailViewModel vm)
     {
         var data = EmailController.EmaiList.Select(email => new EmailViewModel(email)).ToList();
         EmailController.EmaiList.Add(vm.ToEmail());
         return View("List", data);
     }
 }

 

A samozřejmě View:

@using DotNetPortal.ViewModel

@{
    ViewBag.Title = "List";
}

@model List<EmailViewModel>

<h2>List</h2>
<h3>ViewData: @ViewData["ViewDataTest"]</h3>
<h3>ViewBag: @ViewBag.ViewBagTest</h3>

<div class="container">
    <div class="row">
        <table>
            <thead>
            <tr>
                <th>
                    Email:
                </th>
                <th>
                    FirstName:
                </th>
                <th>
                    LastName:
                </th>
            </tr>
            </thead>
            <tbody>
            @foreach (EmailViewModel emailVM in Model)
            {
                <tr>
                    <td>
                        @emailVM.Value
                    </td>
                    <td>
                        @emailVM.FirstName
                    </td>
                    <td>
                        @emailVM.LastName
                    </td>
                </tr>
            }
            </tbody>
        </table>
    </div>
    <div class="row">
        <form action="/email/add/" method="POST">
            <input type="text" name="vm.value"/>
            <input type="text" name="vm.firstname"/>
            <input type="text" name="vm.lastname" />
            <input type="submit" value="odeslat."/>
        </form>
    </div>
    </div>

 

 

Nyní máme náš Controller a naše View využívající náš ViewModel. Přesto, jsem udělal jednu chybu. Pro ViewModely platí určitá pravidla, které by se měly ctít:

1) 1 View = 1 ViewModel

2) ViewModel je určen z datové části View. Tedy ViewModel obsahuje pouze ty public property, které jsou potřeba pro zobrazení ve View, nebo které View odesílá controlleru (velmi často to samé).

3) ViewModel nesmí obsahovat logiku aplikace

4) ViewModel se pohybuje pouze mezi View a Controllerem. Nikam jinam NESMÍ  přijít. Pokud se předávají data do jiné vrstvy aplikace, dochází k mapování na data object. Buďto toto uděláme ručně (jako to děláme nyní), nebo použijeme nějaký mapovací tool, který mapuje na základě konvence (například AutoMapper).

Zde jsem porušil první dvě pravidla. Mám sice mapování z Emailu na EmailVM, ale tímto jsem vytvořil ViewModel pouze pro Formulář a tabullku. Pokud by byly na stránce ještě další informace, měl bych si vytvořit ještě “container” obsahující informace pro celou stránku a využít kompozice.

 

Model? ViewModel? View Specific Model? Data Object?

Ok, teď musíte mít skutečně problém identifikovat, co znamená co. Prozatím jsem vám s tím nepomohl  při střídání názvů pro pořád ty samé věci a popravdě nepomáhají k tomu ani základní šablony ASP MVC, které narvou vše do složky Models a dál se o to nestarají. Navíc některé věci mají více názvů a tak je z toho potom jeden velký maglajz.

Model : nazývaný často business modelem, business logikou atp. Jedná se o logiku aplikace, často se dělí na několik vrstev jako DAL, Service layer,..

Data Object : také jako business object, data object, DTO, PDO, Plain Data Object. Jedná se o logické data, které se pohybují v aplikaci – kontejnery, přepravky,..

ViewModel : také bohužel často označovaný jako “model”, lépe jako View Specific Model. Jedná se o reprezentaci dat souvisejících s prezentační vrstvou.

 

 

Pár slov na závěr tohoto dílu

V tomto díle jsme si řekli něco o architektuře MVC a kouknuli se na to, jak spolu komunikuje View a Controller. Mohli jste si často všimnout, že nedodržuji některé věci, které jsem řekl v minulých dílech – například ohledně uhlazenosti kódu, názvu proměnných atp. Je to z toho důvodu, že se zde stále učíme novým věcem a tak by jsme na uhlazování kódu strávili zbytečně moc času.  V tuto chvíli.  Zároveň není architektura tak, jak by měla být v ideálním případě, k tomu se ale také dostaneme.  Vždy, jakmile nabereme větší část know-how, které bude potřeba – vytvoříme si menší aplikaci, ve které už budeme respektovat vše, co jsme se naučili.

Závěrečnou implementaci tohoto dílu, lze stáhnout zde:

Ještě jako poslední typ na závěr si povíme, proč jsem všude v kódu používal public property a né public proměnné (public fieldy).

Představme si dvě implementace té samé třídy pomocí dvou odlišných způsobů zápisu public memberu a e jejich použití:

public class PropertyStyle
   {
       public int Cislo { get; set; }
   }


   public class VariableStyle
   {
       public int Cislo;
   }
private void usage()
  {
      PropertyStyle propertyStyle = new PropertyStyle();
      VariableStyle variableStyle = new VariableStyle();

      var a = propertyStyle.Cislo;
      var b = variableStyle.Cislo;
      propertyStyle.Cislo = 5;
      variableStyle.Cislo = 7;
  }

Stejné že? Nyní chceme upravit chování přiřazení do Cislo, že dojde ke změně hodnoty pouze pokud je vstup větší než-li 0. Tedy jakési defenzivní programování. U PropertyStyle jde o jednoduchou úpravu geteru. U VariableStyle musíme celou třídu refaktorizovat – především změnit všude Cislo z fieldu (public variable) na Propertu. Toto je občas jednoduchá operace, jindy složitá.

Problém je ale ještě jinde. Pokud tato změna z Fieldu na Propertu nastane v nějaké externí Library (.dll), přestane nám fungovat naše zbuilděná aplikace, jelikož ta počítala  s Fieldem a najednou se jedná o propertu (jednoduchou metodu).  Dojde tedy k tzv. narušení contractu. Toto může být občas velmi nepříjemný problém.

Zároveň je Field velmi omezující oproti Propertě, nelze ho debugovat (umístit do něj brakepoint) a je pracné ho v budoucnu nějakým způsobem upravovat. Public proměnné dané třídy, tedy implementujte jako Public property. V .NET je jejich použití trapně snadné (oproti např. Javě, kde je jejcih použití velmi velmi otravné) a navíc můžete použít auto propert.

 

Připomínám, že budu rád za každý komentář pod článkem a působí na mě vždy diskuze jako silná motivace pokračovat dále. Prosím tedy zanechte svůj názor pod článkem.

Přeji vám hodně úspěchů a zase brzy u dalšího dílu.

MB.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

 

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

Bude ještě nějaký díl.

Dobrý den,

chtěl bych se jenom zeptat autora, jestli plánuje ještě nějaký díl. Nebo byla série z časových důvodů ukončena?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Pokračování

Dobrý den, bude tento seriál ještě pokračovat?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

dobrý den.

bude, bohužel způsob zadávání článků na tyto stránky mi háže jen klacky pod nohy.. na 200 řádkový příspěvek je ten sysétm dostačující, na to co dělám já, ale velmi odpadní. Live Writer mi pravidelně maže část článku, jindy mi označí 70% textu jako "součást pluginu", který nemůže najít, takže to nejsem schopný editovat a při překliknutí na náhled se vše smaže atp..

Přešel jsem nedávno na Open Live Writer, ale ten zase nemá pluginy... takže. je to past vedle pasti :-)

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý den,

a není v tomto případě lepší si obsah vytvořit ve Wordu (případně jiném textovém editoru), namísto Writeru a pak jej jen vložit skrze publikační rozhraní webu?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý den,

bylo mi řečeno od admina (který mi už půl roku dluží zavedení seriálu, asi se připomenu :) ), že žádná administrace není,.. vše se dělá jen přes metablog api (live writer) a přes databázi ;-)

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Tak to vám potom nezávidím. Snad vás to neodradí od pokračování, případně se přesunete se svými články jinam.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Taaak, prave jsem vyresil problemy s Live Writerem. Tak doufam, ze jsem to nezakriknul a jdu dopsat 5. díl ;-)

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý den,

slíbil jste 5. díl. Netrpělivě na něj čekám. Kdy ho prosím zveřejníte?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý den, máte pravdu. Díl mám dopsany, ještě zbývá jazyková korektura.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Další díly

Ahoj, chtěl bych se zeptat, zda plánujete další díly toho skvělého seriálu?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý večer,

právě jeden píši.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dotaz

Dobrý večer,

zajímá mě, proč u ActionResultu používáte "return this.View();", když v oficiálním tutoriálu od MS je pouze "return View();".

Děkuji.

PS: Jsem začátečník. :)

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý večer,

jedná se o to samé, View je internal metoda u Controlleru, od kterého dědíme. This je vynucené StyleCopem, aby jste ihned z kódu viděl, že voláte metodu třídy a že nevoláte například nějakého delegáta. Obdobně je to výhodné i u memberu, aby jste ihned viděl, že se jedná o member a né například o proměnnou v metodě.

MB

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Děkuji za odpověď. :)

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Pochvala

Velmi dobře napsané a poučné díky

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Super článek

V ASP.NET mvc jsem dělal tak před 3 lety a pamatuji si jak složité bylo dobrat se ke správným informacím. Hluboce oceňuji, že vždycky napíšeš: "Takto je to vlastně špatně, v dalším dílu si ukážeme jak je to dobře"- je z toho poznat, že to máš nějakým způsobem rozmyšlené..

Plánuji se k tvorbě webu vrátit, díky těmto článkům to bude snazší.

Díky moc, jen tak dál !!!

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý den,

děkuji za podporu a jsem rád, že se vám seriál líbí. Další díl seriálu již brzy.

S pozdravem,

MB

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

ViewModel obsahující business entity.

Je správné, aby ViewModel obsahoval business objekty. Třeba při zobrazení detailu složitějšího objektu jako Produkt. Chci vypsat detail produktu ten má kategorie a také příbuzné produkty a dokumenty a komentáře a další...je potom pracné všechno namapovat na ViewModely...

Každopádně díky za seriál. mipe

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý den,

jako ideální řešení business objektů považuji, když se nacházejí ideálně v nějakém Shared projektu, tedy solution může vypadta například takto:

/Frontend

** projekt.Web

/Shared

** projekt.Shared

/DTO

/Services

/Repositories

/Backend

** projekt.DAL

** projekt.Core

/Common (není často potřeba, pokud máte ve firmě nuget balíčky vlastní)

business modely mám tedy často v tom Shared, je to z toho důvodu, že v kombinaci se správně registrovaným IOC containerem, jediné reference, které mezi projekty jsou jsou NECO->Shared. Takže zatímco DTO (business objecty) jsou vidět odkudkoliv - reprezentují "data" co se v aplikaci přesouvají - co tam existují, tak třeba Webový projekt není referencovaný nikdě...

Toto má za efekt ten, že kterýkoliv projekt - kteroukoliv část solutionu, kromě shared lze vyměnit za jinou a nenarušíte tím kontrakt (který je definován pomocí interfacu a DTO v shared projektu).

Z tohoto důvodu je v pořádku, pokud použijete ve ViewModelu business modely, ale nesmíte přímo do těchto business modelu umístit nějaké "UI detaily", jako je třeba UIHint (jak se mají vykreslit), jejich UI validace atp..

Tyto věci musí být ve ViewModelu v projektu .WEB (webový projekt), jsou to totiž informace pouze k prezentaci a v business modelu logicky býti nemají. On taky standardní Class Library ani nemá k dispozici věci jako UIHint a Validaci, které se nacházejí standardně nareferencované ve Web projektu.

Mapování z Modelu na ViewModel je pracné.. obecně platí, že všechny úpravy pro lepší architekturu "do budoucna", pro lepší úpravy a rozšiřitelnost atp., jsou psaní navíc..

Díky věcem jako ReSharper atp., to ale často znamená pouze zmáčkknout pár klávesových zkratek,.. případně pokud preferuje AutoMappera...

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Hezký večer,

souhlasím, business modely v samostatném projektu a odekorvávat je view modely. Neměli jsme s tím nikdy problém. Je to dobrá praktika i pro partial views a display templaty, když za nějaký čas zjistíte, že Vám samotný business object nestačí a potřebujete přidat nějakou UI property, nemáte jinou možnost než sáhnout na view data (celkem prasárna) nebo refactorovat (zbytečně ztracený čas).

Jinak zajímalo by mě, kde registrujete závislosti IoC kontejneru, business, web, samostatná assembly ? To první se mi jeví čistší, ale ze zkušenosti dosti nepraktické.

A ještě jedna věc, máte nějaké špatné zkušenosti z použitím servis ve ViewBase ? Mám na mysli servisy pro UI (např. lokalizace textů).

Díky.

Přeji hezký zbytek dne,

R.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Dobrý večer,

snažím se vždy zachovat (zatím se mi to daří) to, aby mezi sebou jednotlivé vrstvy solutionu neměly "kodovou" referenci, kromě reference na Shared. Z tohoto důvodu mám i rád vlastnost Identity 2, kde lze "servisy" v podobě různých managerů dát do Core projektu, story do DAL projektu atp.

K vašemu dotazu tedy,...

Samotný container (nebo více) mám v Shared projektu, registruji vždy v projektu, kde mám k dispozici konkrétní věc, tedy například Servisy registruji v Core projektu (povím si v něm pouze o container ze Shared), Repozitáře mám registrované v DAL, atp,...

V MVC projektu poté registruji Dependency resolverve kterém používám IOC container ze shared projektu.. Vím tedy zcela jasně, kde mám co zaregistrované (na základě toho o co se jedná).

Vzhledem k tomu, že se vše resolvuje až při requestu, mohu využít různých eventů jako je assembly load atp a nevadí to ničemu.

Nevím, zda přesně rozumím tomu, čím myslíte UI Servisy..

Konkrétně lokalizace textů bývá většinou jen ve stylu @Resources... pokud je to něco složitějšího, máme zase několik možností o co se jedná,...

pokud je to něco opravdu pouze pro View, mám rád extension metody (například na @html - je to na vás) s tím, že přidávám @using všem view, pomocí registrace ViewBase.

Jiné řešení by bylo ale pravděpodobně v případech, kdy si UI servisa musí šahat pro něco do jiné vrstvy,.. tam by bylo řešení jiné a ai malinko složitější (BaseController nebo něco podobného), zkuste dotaz upřesnit.

MB

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Hezký den,

ad IoC registrace) Jsem rád, že se to daří alespoň někomu. V našem provoze bohužel asi nereálné. Navíc nechceme být závislí na jedné konkrétní implementaci IoC kontejneru. Asi by se to dalo vyřešit nějakou abstrakcí nad registracemi jednotlivých servis, ale to už zavání problémy.

ad servisy ve ViewBase) Asi konkrétně tu lokalizaci a globalizaci (máme i jiné, ale bylo by na dlouho se rozepisovat, co a jak). Používáme vlastní řešení, frameworková implementace nám nevyhovuje. Dělám to tak, že si v BaseView resolvnu servisu, která to řeší a uložím do backing fieldu a k ní přistupuju přes metodu. Vidíte v tom nějaký potenciální problém ?

Díky.

R.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Zdravím,

IOC registrace) Rozumím, já nepočítám s tím, že bych chtěl nějak střídat právě IOC container. Přesto, že se jedná většinou o primitivní věc, chce bohužel každý framework (resolver) většinou nějaký svůj vlastní INecoContainer,.. Tedy není tam zaručena kompatibilita.. takže s tímto skutečně nepočítám a proto si mohu dovolit mít určitý konkrétní kontrakt. Zřejmě by jste se nějakému adaptéru / proxy nevyhnuli.

Servisy ve ViewBase) todle si myslím nejvíce záleží na způsobu, jakým tu servisu resolvujete a jaký je lifecykle.

Nejhorší, co tam asi můžete dělat je A.Resolve<ITyp>. Toto považuji za stejné zlo jako používat public propertu pro dependency injection. V tomto jsem zjistil, že nejsem sám a hodně lidí (a odborníků) to považuje za anti pattern. Tímto se nechci za svůj názor krýt, já s hodně věcma "je to antipattern" nesouhlasím.

Každopádně.. o co se jedná...

class A{} ... constructor(B b){}

Foo() // this.b.bee();

vs.

class A{} ... Dependency property B b {get; set;}

Foo() // this.b.bee();

----------------

Zatímco při constructor injection musím u většiny IOC frameworku mít už z kódu registrovánu nejdříve třídu B, než mohu zaregistrovat třídu A, jinak mi kód spadne ihned (v lepším případě nepůjde zkompilovat) - navíc mám vždy constructor k dispozici "public"..¨

tak u druhé varianty (public setter) mi vše půjde až do doby, než-li se zavolá metoda Foo(). Ideálně se volá až na produkci :-) Todle je zjednodušené vysvětlení,.. ale věřím, že mi porozumíte.. U public setteru (nebo ručně pomocí Resolve<>()) je závislost zcela skryta a může to vézt k velkým problémům..

Todle byl největší problém například u WPF / WebForms za použití standardního ServiceLocatoru, který navíc ani nehlásil, v čem je problém a prostě řekl "Něco mi chybí".

--------

--------

--------

Takže, pokud doplňujete dependence pomocí Resolve<>() , případně public setteru, asi bych se trošku obával právě tohoto problému..

Nejlepší by bylo samozřejmě mít nějaký ViewActivator (standardní řešení) a Constructor injection.

Pak je třeba si dát pozor na to, aby se tam skutečně dávaly pouze servisy, které jsou opravdu pro View.. což i18n, l10n, g11n atp. je.

---

Druhá věc, kterou si sám popravdě nejsem moc jistý a co bych si rozhodně ozkoušel je rozdílné vytváření controller vs. view. Zatímco controller je vytvářen při každém requestu max jednou, nejsem si jistý, kolikrát je vytvořené například PartialView, nebo jestli se za určitých okolností například asynchronně nezpracovávají PartialView atp., pokud je ho potřeba vícekrát. Mohlo by zde dojít k určitým problémům, pokud například vaše servisy nejsou thread safe a zároveň si ve View budete říkat o 1 instance per resolve, případně nejsou například "HttpContext" safe. (ted me asi uplne nenapada zadny priklad - ale souvisi to trosku s UOW).

K tomu, aby jste mohl toto efektivně ošetřit možná budete potřebovat znát context controlleru (například znát jeho typ), todle je podle mého názoru ale špatně a tento detail by měl být před View ukrytý.

Nedokáži tedy asi teď úplně říci jednoznačně, že vás nemohou čekat problémy, ale slibuju, že nad tím budu přemýšlet a nějaké věci si také vyzkouším ;-)

MB

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Perfektní

Díky moc za perfektní seriál. Ač programuji již dlouho, s weby jsem přišel do styku jen okrajově a tohle je přesně to co potřebuji, abych se jim začal věnovat více a zkusil něco nového. Jak se zdá děs a hrůza v podobě nekonečných php scriptů pominula a jde to i jinak.

Hodně sil do další tvůrčí činnosti.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Paráda

Super, už se těším na další.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Jen dak dále

Výborně, jen tak dále

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback