č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.

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;}
}
Objekt této třídy bude vlastně jen držet číslo aktuální stránky, velikost stránky a celkový počet stránek.

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átek 10. října 2014

Kde zaparkovat u Hohe Wand

Hohe Wand je masív, který se nachází kousek pod Vídní a poskytuje několik ferat - s bonusem ve formě vyhlídky z terasy Skywalk.

Místo jsme navštívíli začátkem října - bylo sice pošmourno, ale i přes tohle počasí jsme si to užili. Feráty jsou to opravdu lehké a dají se zvládat bez zajištění - ostatně většina lidí co jsme potkali ani žádné neměla :-)

neděle 14. září 2014

Časovače ve službách

Toto je jen zamyšlení, jak správně používat časovače pro opakované spouštění operací. Obvykle se to řeší takto:

private int interval = 3600000;
private Timer timer = new Timer(2000);

public void Start()
{
   this.timer.Elapsed += this.OnTimerElapsed;
   this.timer.Start();
}

private void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
   this.timer.Stop();
   .....
   this.timer.Interval = this.interval;
   this.timer.Start();
}


Drobným nedostatkem tohoto kódu je fakt, že kód vlastně neběží v nastaveném intervalu, ale tento interval je prodloužen i o vlastní běh metody - například pokud je interval nastaven na 10 sekund a metoda běží  také 10 sekund, uplyne mezi dvěma voláními 20 sekund.

Takže tady je lepší řešeni- použije se System.Threading.Timer - ten umožňuje nastavit jak čas prvotního spuštění, tak interval mezi následnými voláními.

private Timer timer = null;
private readonly TimerSemaphore semaphore = new TimerSemaphore();

public void Start()
{
   int interval = 3600000;
   this.timer = new Timer(new TimerCallback(this.OnTimerElapsed), null, 200, interval);
}

private void OnTimerElapsed(object state)
{
   if (this.semaphore.IsRunning) return;

   do
   {
       ...
    } while (this.semaphore.ShouldBeRunning);
}

Objekt semaphore zabraňuje opětovnému spuštění metody, pokud tato již běží a navíc v případě, že dojde k tomuto opětovnému zavolání spustí metodu ihned znovu - takže pokud metoda běží pomaleji a doba běhu překročí nastavený interval, je metoda volána ihned znovu - na rozdíl od prvního kódu, kde se opětovně čeká.

Takto vypadá vlastní kód třídy TimerSemaphore:

public class TimerSemaphore
{
 private bool isRunning = false;
 private bool shouldBeRunning = false;
 private bool isStopped = false;
 private object locker = new object();
    
 public bool IsRunning
 { 
  get 
  { 
   lock(this.locker)
   {
    if (this.isStopped)
     return false;
    if(!this.isRunning)
    {
     this.isRunning = true;
     return false;
    }
    else
    {
     this.shouldBeRunning = true;
     return true;
    }
    
   }
  } 
 }

 public bool ShouldBeRunning
 {
  get
  {
   lock(this.locker)
   {
    if this.isStopped)
     return false;
    if(this.shouldBeRunning)
    {
     this.isRunning = true;
     this.shouldBeRunning = false;
     return true;
    }
    else
    {
     this.isRunning = false;
     return false;
    }
   }
  }
 }

 public void Stop()
 {
  lock(this.locker)
  {
   this.isRunning = false;
   this.shouldBeRunning = false;
   this.isStopped = true;
  }
 }
}





sobota 6. září 2014

Datové připojení ve Francii

Při cestách mimo ČR mi vadí docela vysoké poplatky za data v zahraničí (i když jsou země EU zahraničí?). Obecně se snažím moc nevázat, navíc používám tablet se SIM kartou a raději si data v ČR kupuju na předplacence. Ale v zahraničí je problém, u Mobil.cz si lze koupit balíček na měsíc - za 100MB chtějí 185 Kč. A nebo přímo u T-Mobile platit balíček za den (měsíční balíčky pro předplacené karty zrušili). Vím, že existují další možnosti, třeba připojení od Vodafone - ale ani jedno není moc cenově výhodné.


Při letošní cestě do Francie jsem se tedy rozhodl koupit místní SIM od poskytovatele Lebara. SIM kartu si lze objednat buď předem poštou - což jsem nezkoušel -   a nebo koupit na místě v trafikách (Tabaco). Což jsem nakonec udělal. Stála €5 a musí se aktivovat.

Aktivace přes formulář na internetu mi nešla, takže jsem musel volat zákaznickou linku (čislo je v balíčku, který si koupíte a voláte bezplatně). Je to sice trochu zdlouhavé, většinu údajů musíte hláskovat (operátor mluví anglicky), ale tak během 10 minut je karta aktivována. Uvádíte jméno, bydliště a číslo pasu.

Nyní zbývá jen dobít kredit a vybrat si tarif. Dobil jsem €20 a aktivoval si tarif 3GB na měsíc za €15 - přehled všech nabídek na datové připojení je zde. Vše lze provést po internetu, jen musíte mít ještě jiné připojení, neboť SIM karta není před dobitím aktivní - vhodné je využít wifi v hotelu, u McDonalds apod.

Bohužel po dobití karta nezačne automaticky fungovat pro datové připojení. Je nutné nastavit ručně APN konfiguraci. Pokud si nevíte rady, můžete zavolat na podporu (tahle je ale již placená, na rozdíl od registrace) a tam vám slíbí poslat konfigurační data SMS. Ale alespoň mne nic neposlali, takže jsem musel googlit a najít toto nastavení (v oficiálních materiálech mají heslo prázdné, ale mne to bez něj nepracovalo):

APN: fr.lebara.mobi
User: wap
Password: 1234

(Nastavení -:Bezdrátovaá připojení a sítě -> Další-> Mobilní sítě->Názvy přístupových bodu (APN) - Přidat)

A pak to už normálně funguje a za celkových cca 700Kč jsou k dispozici 4GB dat (dostáváte nějaké bonusy za dobití). Český operátor vám za tuhle částku v roamingu dodá tak desetkrát méně dat.  Jsou možné i jiné kombinace, třeba 1.1GB za €15 (cca 420 Kč) a podobně. 

Karta by měla jít koupit na těchto místech: