View modely, které obsahují kolekce objektů, s sebou nesou obvykle nutnost testovat kolekci na na null hodnotu. Což není příjemné, komplikuje to kód a ten je i méně čitelný. V tomto posledním příspěvku věnovaného kolekcím v ASP.NET MVC popíši jedno z možných řešení.
sobota 27. prosince 2014
čtvrtek 25. prosince 2014
Plánování cest
V jednom ze starších příspěvků jsem popsal, jak lze dnes snadno strávit a naplánovat dovolenou bez nutnosti cestovní kanceláře. Dnes popíšu, jak si zhruba připravuji plán na kratší dovolené:
ASP.NET MVC Binding podruhé
V minulém příspěvku jsem ukázal, jak navázat objekt s více jak jednou vlastností na ovládací prvek. Nyní popíši obrácený přístup, kdy je jedna vlastnost navázaná na dva ovládací prvky. Lze to řešit několika možnými způsoby, takže můj přístup určitě není jediný možný.
středa 24. prosince 2014
ASP.NET MVC Binding - kolekce
K ASP.NET MVC přistupují někteří vývojáři jako k zázračnému blackboxu, ze kterého vypadávají výsledky tak nějak sami, pokud ovšem nechceme dělat něco neobvyklého a který se někdy chová nevyzpytatelně. Přitom tato technologie není nijak složitá, je dobře promyšlená a umožňuje dělat téměř vše, co chceme. Jednou z prvních oblastí je binding - tedy naplnění vstupních objektů Action metody controlleru a to ze zdrojů hodnot. Zdrojů hodnot je několik (a je možné si napsat další), ale nejdůležitějším a nejpoužívanějším je FormValueProvider - tedy využíti hodnot zaslaného formuláře
Naplnění kolekcí
Nejlépe se jakékoliv plnění popisuje tak, že se ukáže, co je na vstupu a co z toho výchozí nastavení MVC udělá. Prvním předpokladem, aby se název vstupní hodnoty shodoval s názvem objektu či vlastnosti, kterou chceme plnit.
Například zde máme view, které zobrazuje seznam jmen, z nichž se může žádné a nebo všechna vybrat:
@{
IEnumerable<string> names = new[] { "Martin", "Jakub", "Jan" };
}
@using (Html.BeginForm())
{
foreach (var name in names)
{
<input type="checkbox" value="@name" name="names" />@name<br/>
}
<input type="submit" />
}
IEnumerable<string> names = new[] { "Martin", "Jakub", "Jan" };
}
@using (Html.BeginForm())
{
foreach (var name in names)
{
<input type="checkbox" value="@name" name="names" />@name<br/>
}
<input type="submit" />
}
Tato action metoda však bude obsahovat prázdnou kolekci, neboť v požadavku byly z prohlížeče poslány obsahy prvku names a tedy výchozí MVC binder neví, že mají naplnit vstupní parametr authors:
[HttpPost]
public ActionResult Index(List<string> authors)
{
return View();
}
public ActionResult Index(List<string> authors)
{
return View();
}
Stačí však přejmenovat authors na names a vstupní kolekce je naplněna korektně:
[HttpPost]
public ActionResult Index(List<string> names)
{
return View();
}
public ActionResult Index(List<string> names)
{
return View();
}
Přitom nezáleží na velikosti písmen, může se napsat nAmES a i tak se kolekce naplní - zkrátka to není case sensitive.
A jaký má být vstupní typ objektu? Takový, jaký potřebujeme, tedy v případě kolekcí a výše uvedeného případu může vypadat například takto:
- List<string> names
- IEnumerable<string> names
- string[] names
a nebo pokud to mám podat obecně, jakýkoliv typ IEnumerable<T>, ICollection<T>, IList<T>, T[], Collection<T> a List<T>.
Kolekce objektů
jak je vidět, binding do kolekcí s jednoduchým typem funguje bez problému. Co ale v případě, že máme objekty následující třídy (tedy bez nějaké jednoznačné hodnoty):
public class Person
{
public string FirstName { get; set; }
public string LastName {get;set;}
}
{
public string FirstName { get; set; }
public string LastName {get;set;}
}
Checkbox má jen jednu hodnotu a jak tedy přimět binder, aby tuto hodnotu nějak použil na vlastnosti? Můžeme si sice napsat vlastní binder a nějak to udělat, ale takové řešení není elegantní. Jde to ale takto:
@{
IEnumerable<Person> persons = new[] {
new Person{FirstName ="Martin", LastName="Strimpfl"},
new Person{FirstName = "Jakub", LastName="Strimpfl"},
new Person{FirstName = "Jan", LastName = "Strimpfl"}
};
}
@using (Html.BeginForm())
{
foreach (var person in persons)
{
var index = Guid.NewGuid().ToString();
<input type="checkbox" value="@index" name="persons.index" />@person.FirstName@: @person.LastName<br />
<input type="hidden" value="@person.FirstName" name="persons[@index].FirstName" />
<input type="hidden" value="@person.LastName" name="persons[@index].LastName" />
}
<input type="submit" />
}
IEnumerable<Person> persons = new[] {
new Person{FirstName ="Martin", LastName="Strimpfl"},
new Person{FirstName = "Jakub", LastName="Strimpfl"},
new Person{FirstName = "Jan", LastName = "Strimpfl"}
};
}
@using (Html.BeginForm())
{
foreach (var person in persons)
{
var index = Guid.NewGuid().ToString();
<input type="checkbox" value="@index" name="persons.index" />@person.FirstName@: @person.LastName<br />
<input type="hidden" value="@person.FirstName" name="persons[@index].FirstName" />
<input type="hidden" value="@person.LastName" name="persons[@index].LastName" />
}
<input type="submit" />
}
Prvky, které obsahují vlastnosti objektů, budou skryté (hidden) a jejich jméno bude určené podle vzorce:
název kolekce [index prvku].jmeno vlastnosti
Prvek typu checkbox pak bude obsahovat hodnotu indexu - nepředané indexy (=checkbox není aktivní), nebudou zpracovány.
Poznámka: v příkladu používám guid, ale je možné použít konstrukci for a vzestupnou hodnotu - fantazii se meze nekladou.
Tento přístup je i řešením případů, kdy objekt, který používáme, nemá jednoznačný jediný identifikátor, tedy kdy je jedinečnost objektu dána více jak jednou vlastností.
Nejčastěji pro kolekci v modelech pro views používám IEnumerable<T> a nebo obyčejné pole - ale používání polí pro bindování je záludné a tedy jednoznačně upřednostňuji generický typ IEnumerable<T>.
pondělí 22. prosince 2014
Nevědomě k plnému disku
Člověk se pořád učí. A když se neučí, tak je následně poučen. Stalo se mi toto - pracovali jsme na komunikačním modulu, který pro komunikaci s cizím serverem používá podepsané zprávy. Uživatel modulu nejprve do systému nahraje certifikát i s privátním klíčem a modul pak s jeho pomocí podepisuje odesílané zprávy.
sobota 20. prosince 2014
Sbírka plakátků pro C# a ASP.NET MVC
Občas se snažím zachytit doporučené postupy a nebo popisy graficky ve formě malého plakátku. Mám je pak umístěné na pracovním stole a slouží mi jako rychlá reference v případě potřeby. Část z nich dávám nyní k volnému použití a pokud chcete, můžete si je po kliknutí na obrázek s náhledem stáhnout ve formátu pdf.
Ten držák je ale dost drahý (cca 1500 Kč a výše) - to je jeho jediná nevýhoda. Má celkem deset rámečků a je tedy na 20 stránek. Za cca 800 Kč lze rozšiřovat o dalších 10 rámečků.
Mimochodem něco podobného používají v obchodech u pokladen pro ceny zboží bez kódu (pečivo, ovoce, zelenina apod.).
Volné rámečky lze použít na class diagramy apod. a alespoň není na pracovním stole nepořádek.
čtvrtek 18. prosince 2014
ASP.NET MVC - sdílený helper
Tento článek by se možná hodil do série "Poznámky z code review" - ale vlastně je to jen další variace na téme DRY :-). Narazil jsem na to, že v jednom web projektu (ASP.NET MVC) se poměrně často generoval tooltip (v daném projektu ikonka, která při najetí myší ukáže boxík s dalšími údaji). Na každém místě, kde se i zobrazoval, byl použit téměř stejný kód, lišil se jen vlastním tělem tooltipu - tedy tím, jaké informace zobrazoval. Například na jednom místě vypadal kód na cshtml stránce takto:
<img width="14" height="14" onmouseout="$('#tooltip1').hide();" onmouseover="$('#tooltip1').css('display','inline');" style="margin-bottom:3px; inline-block;" alt="" title="" src="~/info.png">
<div id="tooltip1" style="display:none; position:relative;">
<span class="arrow"></span>
<div>
<ul>
<li><strong>@Model.Name</strong> – @Model.Surname</li>
<li><strong>@Model.Position</strong> - @Descriptions.Position</li>
</ul>
<p><i>@Descriptions.GeneralNote</i></p>
</div>
</div>
<div id="tooltip1" style="display:none; position:relative;">
<span class="arrow"></span>
<div>
<ul>
<li><strong>@Model.Name</strong> – @Model.Surname</li>
<li><strong>@Model.Position</strong> - @Descriptions.Position</li>
</ul>
<p><i>@Descriptions.GeneralNote</i></p>
</div>
</div>
Lepším přístupem v takovém případě se mi jeví založení sdílené helper metody - do adresáře App_Code se přidá soubor s názvem SharedHelpers.cshtml (název není až tak důležitý, ale jméno - tedy SharedHelpers - bude používáno pro volání helperu, tak je dobré s ohledem na tento fakt zvolit příhodné jméno.
Souborů si můžete navíc založit více.
Helper metoda, kterou v souboru definujeme, bude tak dostupná ze všech view (cshtml) v projektu a bude využívat templated razor delegate - více informací lze nalézt například na stránce http://haacked.com/archive/2011/02/27/templated-razor-delegates.aspx/ . Vlastní kód metody bude vypadat takto:
@helper Tooltip(params Func<dynamic, HelperResult>[] templates)
{
<span class="hoverBox">
<img src="~/icon.png" height="14" width="14" title="" alt="" />
<div class="hoverTooltip">
<span class="arrowShape"></span>
<div>
@foreach (var template in templates)
{
@template(null)
}
</div>
</div>
</span>
}
Na ostatních - běžných - csthml stránkách bude použita jednoduše - stačí ji předat jenom razor kód těla konkrétního tooltipu a metoda jej "obalí" zbytkem kódu:
@SharedHelpers.Tooltip(
@<span>
<ul>
<li><strong>@Model.Name</strong> – @Model.Surname</li>
<li><strong>@Model.Position</strong> - @Descriptions.Position</li>
</ul>
<p><i>@Descriptions.GeneralNote</i></p>
</span>)
Předávaný razor kód musí mít html značky uzavřené, tj. první a poslední značka musí být z jednoho páru. Protože to někdy nechceme (viz <span> v příkladě), zvolil jsem params definici pro sdílenou helper metodu a je jí tedy možné předat více samostatných razor kódů (tohle vše zní asi trochu kostrbatě, ale nevím jak to správně popsat česky):
@SharedHelpers.Tooltip(
@<ul>
<li><strong>@Model.Name</strong> – @Model.Surname</li>
<li><strong>@Model.Position</strong> - @Descriptions.Position</li>
</ul>,
@<p><i>@Descriptions.GeneralNote</i></p>>)
Pokud vždy budete předávat jen jeden kus razor kódu, tak params pole vstupních parametrů sdílené helper metody není potřeba a můžete jej odstranit:
@helper Tooltip(Func<dynamic, HelperResult> template)
{
<span class="hoverBox">
<img src="~/icon.png" height="14" width="14" title="" alt="" />
<div class="hoverTooltip">
<span class="arrowShape"></span>
<div>
@template(null)
</div>
</div>
</span>
}
A málem bych zapomněl na css nahrazující javascript, to vypadá nějak takto:
...
.hoverBox div.hoverTooltip {
display: none;
...
}
.hoverBox:hover div.hoverTooltip {
display: block;
}
...
Programátor se pak nemusí při psaní razor kód zabývat správným kódem pro ikonku, vloží jen to, co chce mít zobrazené a o zbytek se postará sdílený helper.
středa 17. prosince 2014
Poznámky z code review 3
Další nedostatkem, se kterým se setkávám, je buď neznalost používaných technologiích a nebo nevyužívání jejich možností. To lze určitě pochopit, pokud kodér nepracuje s danou technologii určitý čas a nebo je jasné, že není expertem. Code review by ho v takovém případě mělo upozornit na jiné možnosti a tedy tak vlastně přispívá k jeho odbornému růstu - a nebo může sloužit k vyvolání diskuze, který přístup je lepší a proč.
Například mi byl předán takovýto Razor kód:
@model OurModel
@{
var mainClass = "main";
var hasProperty = SomePropertyProvider.HasProperty("Name");
if (Model.IsReadOnly)
{
mainClass += " readonly";
}
if(Model.Values.Any(v=> IsWrong(v))
{
mainClass += " error";
}
var alertId = Guid.NewGuid().ToString();
}
<div class="@mainClass">
<table class="Header">
<tr>
<td class="modelType">
.....
Je jasné, že se kodér snažil sestavit jména stylu - a dopustil se chyb - například v bloku kódu střídá několik věcí - jednak sestavuje jména stylů, mezitím ale provádí ale volání s touto akcí nijak nespjatých. Správně by měl blok vypadat asi takto:
@model OurModel
@{
var alertId = Guid.NewGuid().ToString();
var hasProperty = SomePropertyProvider.HasProperty("Name");
var mainClass = "main";
if (Model.IsReadOnly)
{
mainClass += " readonly";
}
if(Model.Values.Any(v=> IsWrong(v))
{
mainClass += " error";
}
}
<div class="@mainClass">
<table class="Header">
<tr>
<td class="modelType">
.....
To už vypadá lépe, ale stále to není ono. Kombinování řetězců výše uvedeným způsobem není příliš štastné a i když uvedený příklad by asi nevedl k velké paměťové zátěži, je lepší uvedené "sčítání" řetězců nepoužívat. Jinou možnost nabízí MVC helper:
@model OurModel
@helper GetMainClasses()
{
@:message
if(Model.IsReadOnly())
{
@:readonly
}
if(Model.Values.Any(v=> IsWrong(v))
{
@:error
}
}
<div class="@GetMainClasses()">
<table class="Header">
<tr>
<td class="modelType">
.....
a nebo při rozepsání na více helperů:
@model OurModel
@functions
{
string IsReadonly()
{
if (Model.IsReadOnly)
return "readonly";
return string.Empty;
}
string IsInError()
{
if(Model.Values.Any(v=> IsWrong(v))
return "error";
return string.Empty;
}
}
<div class="message @IsReadonly() @IsInError()">
<table class="Header">
<tr>
<td class="modelType">
.....
a nebo alternativně:
@model OurModel
@helper IsInError()
{
@(Model.Values.Any(v=> IsWrong(v) ? "error" : string.Empty)
}
@helper IsReadonly()
{
@(Model.IsReadOnly() ? "readonly" : string.Empty)
}
<div class="message @IsReadonly() @IsInError()">
<table class="Header">
<tr>
<td class="modelType">
.....
popřípadě in-line kódem:
@model OurModel
<div class="message @(Model.IsReadOnly() ? "" : string.Empty) @(Model.Values.Any(v=> IsWrong(v) ? "" : string.Empty)">
<table class="Header">
<tr>
<td class="modelType">
.....
a nebo pomocí funkce:
@model OurModel
@functions
{
string AddClass(bool add, string name)
{
return add ? name : null;
}
}
<div class="message @(AddClass(Model.IsReadOnly(), "readonly")) @(AddClass(Model.Values.Any(v=> IsWrong(v), "error"))">
<table class="Header">
<tr>
<td class="modelType">
.....
Chápu, že na daný příklad a řešení mohou být různé názory, kód nebyl vysloveně špatný a nefunkční, ale jak jsem již napsal, lze code review pojmout i jako možnost vzdělávání se či prostor k diskuzi. Helpery z uvedených příkladů lze přesunout na konec stránky a mít tak na začátku téměř čisté HTML. Čitelnost ukázek v VS je lepší, nějak mám problém vkládat ukázky, která mají odpovídající barevné zvýraznění.
pondělí 15. prosince 2014
Poznámky z code review 2
Při programování, ale vlastně asi při každé činnosti, je dobré se řídit pár zásadami. Jendou z nich je alespoň v případě programování něco, co se anglicky označuje jako DRY. Ne, neznamená to, že je sucho a je nutno zajít do hospody na jedno, ale jsou to první písmena Don't Repeat Yourself.
Je mnoho způsobů jak tento přístup využít a jedním z nich je i nepsat zbytečný kód. Při code review často ovšem narazím na to, že vývojář tento princip nedodrží a v kódu se objeví něco podobného následujícímu zjednodušenému příkladu:
public bool CanCreateNew()
{
if (this.allowedActions == null)
return false;
return this.allowedActions.Any(x => x.ActionCode == ActionCode.CreateNew);
}
public bool CanUpdate()
{
if (this.allowedActions == null)
return false;
return this.allowedActions.Any(x => x.ActionCode == ActionCode.Update);
}
public bool CanCancel()
{
if (this.allowedActions == null)
return false;
return this.allowedActions.Any(x => x.ActionCode == ActionCode.Cancel);
}
Když se podíváte na těla funkcí, tak se neustále opakuje ten samý postup a všechny metody lze vyjádřit pomocí jedné:
public bool CanDoAction(ActionCode actionCode)
{
if (this.allowedActions == null)
return false;
return this.allowedActions.Any(x => x.ActionCode == actionCode);
}
Uvedenému přístupu je vhodné se vyvarovat - výjimkou snad mohou být pouze případy, kdy jste placeni od řádku kódu :-). Je dobré si uvědomit, že čím méně kódu napíšete, tím méně v něm může být chyb a kód se bude snadněji udržovat.
sobota 13. prosince 2014
Poznámky z code review 1
Občas dělám i code review hotového kódu a některé věci se neustále opakují či se vyskytují v kódu poměrně často a tak mne napadlo, že by nebylo špatné na ně upozornit a nabídnout k zamyšlení či diskuzi. První příspěvek bude věnován kódu, který provádí rozhodnutí dle výčtové hodnoty, tedy enumu.
Například vznikne takovýto výčtový typ:
public enum Option
{
One = 1,
Two = 2
}
Výše uvedené hodnoty pak často vedou k programátora k rozhodování se pomocí následujícího kódu, kdy předpokládá, že buď má proměnná jednu určitou hodnotu a pokud ne, tak musí mít druhou.
if (userOption == Option.One)
Console.WriteLine("One");
else
Console.WriteLine("Two");
Což ovšem platí jen do okamžiku, než někdo přidá další hodnoty, například doplní definici enum takto:
public enum Option
{
Unknown = 0,
One = 1,
Two = 2,
Three = 3
}
Podobné chyby se ale my programátoři často dopouštíme, i pokud má výčtový typ více hodnot než dvě a my se z nějakého důvodu domníváme, že stejně může nabývat jen dvou námi vybraných hodnot. Takto napsaný kód není příliš odolný proti změnám ve výčtovém typu a ve výsledku se pak program často chová "podivně".
Jak to tedy napsat správně? Podle mne takto:
switch (userOption)
{
case Option.One: Console.WriteLine("One"); break;
case Option.Two: Console.WriteLine("Two"); break;
default: throw new NotImplementedException();
}
Pokud se objeví hodnota, s kterou kód nepočítal, vyhodí se výjimka. A nebo se může provést úprava hodnoty a zalogování chyby, aby šlo později daný kód upravit, tedy například napsat kód takto:
switch (userOption)
{
case Option.One: Console.WriteLine("One"); break;
case Option.Two: Console.WriteLine("Two"); break;
default:
Console.WriteLine("Not implemented value {0}, processing as {1}",
userOption,
Option.One);
goto case Option.One;
}
Upozornění: kód je napsán jen pro účely tohoto článku, má jen ilustrovat problém. K podobným chybám může dojít i při nepoužití výčtu, jen se to na něm dobře ilustruje.
středa 10. prosince 2014
Feraty u jezera Garda 3 - Cima Rocca
Výprava na horu Rocca byla krásným celodenním výletem - v této oblasti jsou hned čtyři feraty:
- F.Susati,
- M.Foletti
- Sent.dei Camminamenti
- Sent.delle Lasta
úterý 9. prosince 2014
Feraty u jezera Garda 2 - jezero Idro a Sasse
Další ferátkou byla cesta kolem horského jezera Idro. Má jméno Sasse a více informací lze zde. To je necelou hodinku jízdy od severního konce Gardského jezera a leží i výše - a je tam tedy i o poznání chladněji. A zatímco u Gardy to koncem října ještě žije, tady sezona už skončila. Velké parkoviště bylo zcela prázdné a na celé feratě jsme potkali jen čtyři další lidi. A panovalo zde ticho.
neděle 7. prosince 2014
Vybaveni na cesty autem - chlazení
Převoz potravin autem a v chladu byla vždy výzva. Na kratší vzdálenosti stačí polystyrénová krabice, zmražené potraviny a chladící vložky - tenhle systém používáme už roky a je to spolehlivý způsob přepravy většího množství potravin na kratší vzdálenosti. Ale pokud se jede někam na více dnů a není možné využívat lednice, nelze se spolehnout na box, respektive ten není moc použitelný.
pátek 5. prosince 2014
Razor a email
V přechozím příspěvku jsem ukázal, jak využít Razor i mimo web aplikace. Nyní popíši, jak tohoto přístupu využít v případě, že chceme v aplikaci vytvořit email a ten poslat uživateli. Email bude obsahovat i obrázky a přílohu.
emailLayout.cshtml
Vytvoření šablony pro email
Šablony jsou vlastně dvě. V první je vlastní obsah mailu a tato šablona využívá jinou jako svoji obálku - to je vhodné pro případy, kdy bude potřeba více emailů a všechny by měly mít například jednotný vzhled.
email.cshtml
@* Generator: Template *@ @functions{ public string Recipient { get; set; } public string Sender { get; set; } public string Content { get; set; } } @{ Layout = new emailLayout();} <img alt="Logo" src="cid:logo" /> <table width="550" class="headerText"> <tr><td>Hello Mr.@Recipient</td></tr> <tr><td>This is my message for you: @Content</td></tr> <tr><td>Regards, @Sender</td></tr> </table>
emailLayout.cshtml
@* Generator: Template *@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-type" content="text/html;charset=UTF-8" /> <style type="text/css"> body { font-family: "Lucida Grande","Helvetica Neue",sans-serif; font-size: 12px; color: #333333; background: White; } </style> </head> <body> @RenderBody() </body> </html>
Vygenerování emailu
Získání HTML mailu je jednoduché - jak ze šablony vyrobit text už bylo ukázáno v předchozím díle, nyní jen použijeme metodu třídy AlternateView k získání HTML obsahu:
Code:
MailMessage mail = new MailMessage(); var htmlContent = (new email { Sender = "Martin", Recipient = "John", Content = "You win!!!" }).TransformText().Trim(); var htmlView = AlternateView.CreateAlternateViewFromString(htmlContent, new ContentType("text/html")); mail.AlternateViews.Add(htmlView);
Vkládání obrázků
V kódu šablon si můžete všimnout i značek pro zobrazení obrázků. Pokud má email obsahovat obrázky, máte tyto tři možnosti:
- v tagu img odkázát na existující, veřejně dostupný obrázek na sítí
- vložit obrázek do emailu a odkázat na něj v tagu img pomoci cid:název
- vložit obrázek jako base64
Z textu šablon vyplývá, že použiji druhou možnost - jako hodnotu ContentId použijeme hodnotu zadanou za cid: v src atributu příslušeného img tagu:
var attachResource = new LinkedResource(new MemoryStream(<byte[]>)); attachResource.ContentId = <resourceID>; htmlView.LinkedResources.Add(attachResource);
Vkládání příloh
Pokud chceme k emailu přiložit i přílohu, jde to podobně jednoduše - nejprve si vytvoříme příslušný objekt a pak je přidáme do kolekce příloh mailu:Attachment att = new Attachment(new MemoryStream(<byte[]>), attachmentName); mail.Attachments.Add(att);
Tipy
Pro testování posílání emailů lze použít utilitku Smtp4Dev a do config souboru aplikace přidáme následující nastavení:
<system.net> <mailSettings> <smtp from="mail@test.com"> <network host="localhost" port="25"/> </smtp> </mailSettings> </system.net>
Výsledek
Po spuštění pak obdržíme email, který nás informuje o výhře:středa 3. prosince 2014
Využití Razor mimo web
Při programování aplikací je často nutné vygenerovat nějaký text - například email - který je částečně pozměněn - například se mění oslovení (pan-paní), jméno (Martin-Petr) apod. Obvyklé postupy jsou buď tvrdě kódové - tedy skládání textu přímo v kódu za využití například StringBuilderu a nebo se použije textový soubor sloužící jako předloha (šablona), kde se vyhledávájí značky a místo nich se vkládá určitá hodnota.
Pokud ale programujete web aplikace v ASP.NET, určitě jste se setkali s MVC a Razorem - a pokud se vám tento přístup líbí - tedy možnost vytvořit si šablonu, do které se v určitých místech vkládají hodnoty a která umožňuje provádět i složitější operace - je možné podobný přístup použít i v non-web aplikacích. Stručně ukáži jak na to.
Co je nutné?
Postupy jsou popsány mnohem stručněji v jiných příspěvcích dostupných na internetu, tak to vezmu stručně:
Nejprve nainstalujte si RazorGenerator za použití následujícícho postupu
- Ukončete všechny běžící instance Visual Studia - nechte běžet jen jednu.
- Běžte do Tools -> Extension and Updates a vyhledejte Razor Generator
- Naistalujte to
Poté pro každý projekt/solution, ve kterém chcete Razor používat, proveďte toto:
- Běžte do Tools –> NuGet Package Manager –> Package Manager Console
- Zadejte a spusťte" “PM>Install-Package RazorGenerator.Templating”
A to je vše, nyní již můžete Razor použít ve své aplikaci.
Vytváříme šablonu - příklad
Jak Razor použít popíši na příkladu jednoduché konzolové aplikace:
- Do projektu vložte novou položku - greetings.cshtml
- Ujistěte se, že tato položka má ve svých vlastnostech jako hodnotu Custom Tool nastaveno RazorGenerator
- Zadejte tento kód a ulože změny - na rozdíl od použítí v MVC nemá tato šablona vlastnost Model, ale díky bloku functions můžeme definovat jednotlivé vlastnosti a objekty - nejsme tedy vázání na jeden view model jako v případě MVC:
- Vraťte se do Program.cs a do Main metody vložte následující kód:
- Spusťte aplikaci a na obrazovce uvidíte:
Nezapomeňte:
Instalace Nuget balíčku začal váš projekt používat knihovnu RazorGenerator.Templating a tak ke scému běhu potřebuje další knihovny, takže nezapomeňte přidat tyo knihovny při šíření své aplikace:
- System.Web.Extensions.dll
- System.Web.Helpers.dll
Zjednodušený přehled výše uvedeného jsem shrnul na jednu stránku A4, kterou si je možné stáhnout zde.
úterý 18. listopadu 2014
Zábavní parky okolo ČR
Bohužel nám v ČR chybí pořádný zábavní park a tak se musí vždy do zahraničí. Tady je přehledná mapka zábavních parků v Evropě - ty lepší jsou zvýrazněny hvězdičkou. Snažil jsem se zahrnout všechny parky, které mají něco, co alespoň trochu připomíná horskou dráhu.
Jak je vidět, naprostá většina parků leží v Německu - zejména asi jeden z nejlepších parků v Evropě Europa Park.
Ne všechny parky v přehledu stojí za návštěvu, - například bych varoval před polským parkem Wesole Miasteczko - je to asi tak nejhorší park, co jsme kdy navštívil, díky neschopnosti obsluhy neskutečné čekací doby i když byl park prázdný. U jiných parků je třeba zvážit i vhodný věk na návštěvu. Rakouský Family Park se hodí spíše pro menší děti, německý Legoland asi nadchne děti do 10ti let a pak už je to horší a tak podobně.
pondělí 17. listopadu 2014
Feraty u jezera Lago di Garda 1 - Colodri a Drena
čtvrtek 6. listopadu 2014
Stránkování v MVC
Tenhle článeček je o stránkování záznamů v MVC trochu jinak - tak, aby jednotlivé části (controller's actions, models, views) zůstaly co nejvíce jednoduché.
Zdrojový kód je dostupný zde.
Pro zobrazení seznamu bude sloužit generická třída - takže ani tento příklad nebude pevně svázán s třídou UserViewModel, ale generickou třídu budeme moci použít s jakoukoliv třídou, z níž chceme zobrazovat data.
a ještě musíme mít třídu, která bude držet "stránkovácí" informace:
Objekt této třídy bude vlastně jen držet číslo aktuální stránky, velikost stránky a celkový počet stránek.
a view:
Nejprve se metoda pokouší najít hodnotu parametru předávaného metodě kontroleru - tedy PagingViewModel. Pokud takový parametr nenajde, tak jej vytvoří a nastaví velikost stránky a první stránku jako aktuální. Zároveň tyto hodnoty vloží i do kolekce RouteData. Tato kolekce je MVC používáná při bindování a pokud tedy budeme později volat metodu Paging, tak MVC použije data z této kolekce pro vytvoření parametru (instance třídy PagingViewModel) předávaného do této metody.
Po spuštění metody se naplní kolekce a zároveň se zjistí, kolik záznamů je celkem k dispozici. Což je ten správný okamžik pro spuštění metody OnActionExecuted, která vypočte celkový počet stránek a získá tak poslední hodnotu, kterou je nutno uložit do kolekce RouteData, aby MVC mohlo později, při volání metody Paging, vytvořit plnohodnotnou instanci třídy PagingViewModel.
Můžete si také všimnout, že je kontrolován typ příchozího volání a pokud se jedná o Ajax volání, je výsledek změněn na PartialView - takže se rendreju pouze view odpovídající danému kontroleru a master view je ignorováno - vrací se tak pouze zrendrovaná tabulka a nikoliv celá stránka.
Tento atribut je pak použit pro dekorování akce a zároveň nastavuje velikost stránky:
Může se použít na libovolnou akci, která vyžaduje pro své výsledky stránkování. Vývojář se o stránkování nemusí starat a v kontroleru se tedy může soustředit pouze na získání dat.
Zdrojový kód je dostupný zde.
Models
Aby to nebylo moc složité, bude se zobrazovat seznam jmen, tedy jako základní třída nám poslouží toto:public class UserViewModel { public string FirstName { get; set; } public string LastName { get; set; } }
Pro zobrazení seznamu bude sloužit generická třída - takže ani tento příklad nebude pevně svázán s třídou UserViewModel, ale generickou třídu budeme moci použít s jakoukoliv třídou, z níž chceme zobrazovat data.
public interface IItemListInfo { int TotalItems { get; } } public class ItemListViewModel<T> : IItemListInfo { private IEnumerable<T> items; private int totalItems; public ItemListViewModel(IEnumerable<T> items, int totalItems) { this.items = items; this.totalItems = totalItems; } public IEnumerable<T> Items { get {return this.items;} } public int TotalItems { get { return this.totalItems; } } }
a ještě musíme mít třídu, která bude držet "stránkovácí" informace:
public class PagingViewModel { public int PageNumber { get; set; } public int PageSize { get; set; } public int Pages {get;set;} }
Controller - poprvé
Akce v kontroleru bude jednoduchá - hlavním úkolem je načíst daný počet dat (=počet záznamů na stránku) pro danou stránku (počet stránek)
public ActionResult Index(PagingViewModel paging) { ItemListViewModel<UserViewModel> model = this.GetUsers(paging.PageSize, paging.PageNumber); return View(model); }
Views
Ve view dojde k zrendrování informací a i k vykreslení navigační části. První view je docela jednoduché a vykreslí informace o uživateli - pro vykreslení navigační části pak předá řízení zpět do kontroleru a zavolá akci "Paging"
@using MvcPagingExample.Models; @model ItemListViewModel<UserViewModel> @{ ViewBag.Title = "Home Page"; } <div class="list"> <h2>List of Users</h2> <table> <thead> <th width="120px">First name</th> <th width="120px">Last name</th> </thead> @foreach (UserViewModel user in Model.Items) { <tr> <td>@user.FirstName </td> <td>@user.LastName</td> </tr> } </table> <br/> @{Html.RenderAction("Paging");} </div>
Controller - podruhé
Tato metoda může být použita pro vykreslení stránkování libovolného seznamu - odpovídající view jen využívá javascript pro ajax volání zpět na server a vrácenou hodnotou nahrádí stávající obsah:[ChildActionOnly] public ActionResult Paging(PagingViewModel paging) { return PartialView(paging); }
a view:
@using MvcPagingExample.Models; @model PagingViewModel <script type="text/javascript"> $(document).ready(function () { jQuery("a").click(function () { var $anchor = jQuery(this); var url = $anchor.closest('.ajaxPaging').attr("data-source"); jQuery.get( url, { PageNumber: $anchor.attr("rel")}, function (data) { jQuery(".list").replaceWith(data); }); }); }); </script> <div class="ajaxPaging" data-source="@Request.Url.AbsolutePath"> @NaviLink(active: Model.PageNumber > 1, targetPageNumber: 1, label: "first") @NaviLink(active: Model.PageNumber > 1, targetPageNumber: (Model.PageNumber - 1), label: "previous") <b>page @Model.PageNumber of @Model.Pages</b> @NaviLink(active: Model.PageNumber < Model.Pages, targetPageNumber: (Model.PageNumber + 1), label: "next") @NaviLink(active: Model.PageNumber < Model.Pages, targetPageNumber: Model.Pages, label: "last") </div> @helper NaviLink(bool active, int targetPageNumber, string label) { if (active) { <a href="#" rel="@targetPageNumber">@label</a> } else { @label } }
Prvotní nastavení
Zbývá ještě ošetřit případ, kdy při prvním volání akce není žádné stránkování nastaveno. K tomu slouží atribut třída, která dědí z třídy ActionFilterAttribute a která nabízí dvě metody - jednu spouštěnou před provedením vlastní akce kontroleru (OnActionExecuting) a druhou, která se provadí po vykonání akce v kontroleru (OnActionExecuted).public override void OnActionExecuting(ActionExecutingContext filterContext) { var paging = filterContext.ActionParameters.Select(p => p.Value as PagingViewModel).Where(f=> f !=null).FirstOrDefault(); if (paging != null) { paging.PageSize = PageSize; filterContext.RouteData.Values.Add("PageSize", this.PageSize); if (paging.PageNumber == 0) { filterContext.RouteData.Values.Add("PageNumber", 1); paging.PageNumber = 1; } } base.OnActionExecuting(filterContext); }
Nejprve se metoda pokouší najít hodnotu parametru předávaného metodě kontroleru - tedy PagingViewModel. Pokud takový parametr nenajde, tak jej vytvoří a nastaví velikost stránky a první stránku jako aktuální. Zároveň tyto hodnoty vloží i do kolekce RouteData. Tato kolekce je MVC používáná při bindování a pokud tedy budeme později volat metodu Paging, tak MVC použije data z této kolekce pro vytvoření parametru (instance třídy PagingViewModel) předávaného do této metody.
Po spuštění metody se naplní kolekce a zároveň se zjistí, kolik záznamů je celkem k dispozici. Což je ten správný okamžik pro spuštění metody OnActionExecuted, která vypočte celkový počet stránek a získá tak poslední hodnotu, kterou je nutno uložit do kolekce RouteData, aby MVC mohlo později, při volání metody Paging, vytvořit plnohodnotnou instanci třídy PagingViewModel.
public override void OnActionExecuted(ActionExecutedContext filterContext) { var model = filterContext.Controller.ViewData.Model as IItemListInfo; if (filterContext.HttpContext.Request.IsAjaxRequest()) { ViewResult result = filterContext.Result as ViewResult; filterContext.Result = new PartialViewResult() { TempData = result.TempData, View=result.View, ViewData=result.ViewData, ViewEngineCollection=result.ViewEngineCollection}; } if (model != null) { filterContext.RouteData.Values.Add("Pages", (int) Math.Ceiling( (double) model.TotalItems/this.PageSize)); } base.OnActionExecuted(filterContext); }
Můžete si také všimnout, že je kontrolován typ příchozího volání a pokud se jedná o Ajax volání, je výsledek změněn na PartialView - takže se rendreju pouze view odpovídající danému kontroleru a master view je ignorováno - vrací se tak pouze zrendrovaná tabulka a nikoliv celá stránka.
Tento atribut je pak použit pro dekorování akce a zároveň nastavuje velikost stránky:
[Paging(PageSize = 5)] public ActionResult Index(PagingViewModel paging) { ItemListViewModel<UserViewModel> model = this.GetUsers(paging.PageSize, paging.PageNumber); return View(model); }
Může se použít na libovolnou akci, která vyžaduje pro své výsledky stránkování. Vývojář se o stránkování nemusí starat a v kontroleru se tedy může soustředit pouze na získání dat.
pondělí 3. listopadu 2014
Proč si nekupovat nosič od Atery (Atera Strada 3 DL)
Minulý rok jsem si koupil nosič na kola Atera Strada 3 DL s rozšířením na čtvrté kolo. S nosičem jsem byl celkem spokojen, dokud nebyl při couvání poškozen a vyvstala nutnost opravy - jednalo se o výměnu jednoho zadního dílu, který zajišťuje blokování pohyblivé části.
A s překvapením jsem zjistil, že vlastně žádný servis těchto poměrně drahých výrobků u nás neexistuje. Našel jsem sice nějaký soukromý servis, který se značkou Atera zabývá, ale tam mne jen politovali s tím, že jim to mohu poslat a oni budou čekat, až nějaký jiný neš´tasník hodí svůj nosič do šrotu a oni tak získají potřebnou součástku - Atera totiž náhradní díly nerada dodává. Tomu se mi nechtělo věřit, přeci jen je to výrobek v cenové relaci kolem €500 a tak jsem napsal přímo výrobci, popsal svůj problém, přiložil fotografie a už za pár dnů jsem měl odpověď:
Hello, pleace contact your dealer.....
Takže jsem nelenil a požádal o seznam dealerů a během dne obdržel kontakt na firmu Neumann a Elit CZ.
Z výše dvou uvedených Elit CZ vůbec nereagoval, u Neumanna se ozval jejich pracovník a začala zdlouhavá výměna mailů - když už se zdálo, že je díl úspěšně objednán, ozval se jejich pracovník s tím, že firma Atera není schopna díl dodat......
Naštěstí měl i řešení - měli skladem použitý poškozený nosič, který jim tam zřejmě nějaký méně trpělivý zákazník nechal a u kterého byl daný díl v pořádku a tak mi nabídl jeho zaslání jen za cenu poštovného - a tak jsem měl po třech měsících konečně nosič opět funkční - díky nadstandardní ochotě pracovníka firmy Neumann.
A poučení?
Výrobek od firmy Atera si už nikdy nekoupím, jakýkoliv - firma, která není schopna dodat náhradní díl (asi chce zákazníka, aby si koupil nový výrobek) je špatná firma.
čtvrtek 30. října 2014
Ferraty u Lago di Garda s dětmi na vlastní pěst
Při zařizování letošní krátké dovolené v Itálii jsem si uvědomil, že jsem vlastně nikdy nepoužil služeb cestovních kanceláří - tedy mimo prolistování katalogů, kde se občas dá najít pěkný tip na místo stojící za návštěvu. Vlastně ani nevím, co by mi nabídla a vyhovuje mi i volnost, které mi cestování "na vlastní pěst" poskytuje i to, že takto naplánována cesta vyjde levněji
Například na letošní podzimní prázdniny jsem si naplánoval výlet k jezeru Lago di Garda s tím, že si prolezeme zajímavé ferraty v okolí.
středa 22. října 2014
Jak navštívit zábavní parky se slevou 85%
Název je trochu nadnesený, ale v létě roku 2013 se prodávala ve vídeňském Prátru roční předplacená rodinná vstupenka 3+1 (pro čtyři osoby) za pouhých €185. Jen pro porovnání, v letošním ceníku je již tato vstupenka za €297 - na druhou stranu má ale navíc bezplatné parkování v Legolandu a Heide parku a ještě Express pass do těchto dvou parků (přednostní vstupenku na atrakce).
Nově se také nabízí i levnější varianta, která nemá parkování, express pasy, vstup do Gardalandu a už ani do dánského Legolandu za cca €240.
Po roce platnost vstupenky vypršela a nastal čas trochu počítat a najít, kde jsme všude byli a kolik by to stálo, pokud bychom si kupovali jednotlivé vstupy - přičemž jsem počítal s tím, že by se lístek kupoval s předstihem on-line. Přímo na pokladnách jsou pak ceny vyšší o minimálně 20%.
Výsledek vypadal takto:
- €90 za 3 * Heide Park
- €80 za 2 * Legoland Billund
- €75 za 3 * Legoland Gunzburg
- €30 za 1 * Gardaland
- €30 za 1 * Legoland Discovery Center Berlin a Oberhausen
- €5 za 1 * Adventure Park Oberhausen
- €45 za 1 * Sea Life Oberhausen, Berlin,Timmendorfen Strand
- €30 za 1 * Madam Tussauds Viden a Berlin
- €15 za 1* Hamburg Dungeon 15
Ne všechny karty byly používány stejně, takže jsme celkově uskutečnili návštěvy za €1285 - a za karty zaplatili původně €185 - takže je to sleva ve výši 85%. Navíc jsme využili i slevy na kemp v německém Legolandu - ten je zážitkem zejména pro menší děti. Ale ani ty starší se nenudili, jak je vidět na tomto obrázku:
Ceny karet jsou na stránkách Merlin Pass a vyplatí se sledovat, kde jsou jaké slevy - karty také bývají při objednávání v únoru a březnu levnější. Jsem sám zvědavý, jak upraví ceník na rok 2015 - ceník na rok 2014 už rozhodně nebyl tak výhodný jako na rok 2013. Vstupenka platí následujících 365 dnů po koupi včetně všech výhod a to i při změně ceníku.
Přihlásit se k odběru:
Příspěvky (Atom)