Skripty emitované do stránky pomocí odkazů do assembly, pomocí WebResource.axd, se v browseru cachují pouze pokud máme debug-kompilaci vypnutou:
<compilation debug="false" />
Skripty emitované do stránky pomocí odkazů do assembly, pomocí WebResource.axd, se v browseru cachují pouze pokud máme debug-kompilaci vypnutou:
<compilation debug="false" />
Vytváříme klasickou webovou službu publikovanou pomocí ASMX. Při pokusu o přístup k této webové službě přes internetový prohlížet obdržíme výjimku NullReferenceException – a to ještě žádnou metodu nevoláme, jen prohlížíme dostupné služby.
Debugger se nám zastavuje v souboru DefaultWsdlHelpGenerator.aspx na řádku 1335:
OperationBinding FindHttpBinding(string verb) {
foreach (ServiceDescription description in serviceDescriptions) {
foreach (Binding binding in description.Bindings) {
HttpBinding httpBinding = (HttpBinding)binding.Extensions.Find(typeof(HttpBinding));
Vskutku pozoruhodné je pak řešení problému: Ve web.configu máme v sekci <pages> nastavení autoEventWireUp=“false“. Po odstranění tohoto nastavení přístup k dokumentaci webových služeb funguje.
DefaultWsdlHelpGenerator.aspx je generátor dokumentace pro zobrazení v prohlížeči. Ten se evidentně kompiluje s použitím nastavení aplikace a v kódu spoléhá na zavolání metody Page_Load pomocí automatického navázání vybraných událostí (AutoEventWireUp). K dispozici máme i zdrojový kód tohoto souboru, standardně se nachází v C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG.
(Runtime .NET Framework 2.0)
Na vývojářské mašině se dá taky rovnou zeditovat soubor C:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\DefaultWsdlHelpGenerator.aspx a na první řádek přidat:
<%@ Page AutoEventWireup="true" %>
Generika jsou dobrá nejenom pro kolekce. Že se dají velmi dobře využít i v jiných situacích, dnes předvedu na třídě Money (diskuze, kdy má být Money třída a kdy je lepší struct není předmětem tohoto článku). Uvidíte, že jdou dělat i takové věci jako generické operátory.
Chceme řešit imlementaci třídy Money, třídy reprezentující peněžní datový typ skládající se z částky (Amount) a její měny (Currency). Naším cílem je dosáhnout následujího:
Money<Currency> cena = new Money(150.20M, Currency.Czk);
cena = cena + new Money(20M, Currency.Czk);
cena = cena * 10;
MyLabel.Text = cena.Amount.ToString("n2") + " " + cena.Currency.Zkratka;
...
Money cena = new Money(10M, Currency.Czk);
cena = cena.ToCurrency(Currency.Usd) + new Money(159.3M, Currency.Usd);
MyLabel.Text = cena.ToString("n2");
...
public static Money<TCurrency> operator +(Money<TCurrency> money1, Money<TCurrency> money2)
{
// implementace
}
…v potomkovi Money chceme přeci součet dvou Money a hlavně návratový typ Money, nikoliv Money<Currency>
public static Money operator +(Money money1, Money money2)
{
// znovu implementace??? Tomu jsme se chtěli vyhnout! Implementaci chceme mít v class-library.
}
Čeho chceme dosáhnout – mít centralizovanou implementaci funkčnosti operátorů tak, abychom ji mohli snadno udržovat pro všechny naše projekty. Kouzlo první spočívá v extrahování implementace operací do generických metod třídy Money<TCurrency>, kteréžto metody sama třída Money<TCurrency> ve svých operátorech využije, stejnětak jako je může využít libovolný potomek při definici svých typovaných operátorů.
Money<TCurrency>:
public static Money<TCurrency> operator +(Money<TCurrency> money1, Money<TCurrency> money2)
{
return SectiMoney<Money<TCurrency>>(money1, money2);
}
public static TResult SectiMoney<TResult>(Money<TCurrency> money1, Money<TCurrency> money2)
where TResult : Money<TCurrency>, new()
{
if (money1.Currency != money2.Currency)
{
throw ...
}
TResult result = new TResult();
result.Amount = money1.Amount + money2.Amount;
result.Currency = money1.Currency;
return result;
}
Obdobně jako u BusinessObjectCollection využíváme kouzla generika s „cílovým typem“. Daní za tento způsob je nám nutnost vyžadovat constructor bez parametrů, abychom v generiku byly schopni vytvořit instanci cílového typu (možnost generika s constructorem předepsaných parametrů se asi v dohledné době nedočkáme, v .NET 3.5 pokud vím, nic takového stále není a po diskuzi s částí CLR teamu na letošním MVP Summitu v Redmondu už i chápu proč).
Project-specific potomek Money už pak může využívat této centrální implementace snadno:
public static Money operator +(Money money1, Money money2)
{
return SectiMoney<Money>(money1, money2);
}
Řekněme, že se chceme v projektech, v project-specific třídách Money, úplně zbavit nutnosti implementovat operátory, byť už máme cestu jak v těchto operátorech využít centrální implementace funkčnosti. Projektový kód chceme mít co nejčistší, prostý takovýchto infrastrukturních redefinic, tak, abychom se v něm mohli přehledně věnovat jen nové funkčnosti Money oproti předkovi Money<TCurrency> – např. doplnit ony omílané kurzové přepočty, atp.
Jak na to?
Do řetězce dědičnosti mezi Money a Money<TCurrency> vložíme kouzelnou třídu MoneyImplementationBase, která nás dalším fíglem s generiky a operátory posune ke kýženému výsledku…
public abstract class MoneyImplementationBase<TCurrency, TResult>: Money<TCurrency>
where TCurrency: class
where TResult : MoneyImplementationBase<TCurrency, TResult>, new()
{
public static TResult operator +(MoneyImplementationBase<TCurrency, TResult> money1, MoneyImplementationBase<TCurrency, TResult> money2)
{
return SectiMoney<TResult>(money1, money2);
}
}
Tato třída samozřejmě patří do naší centrální class-library odkud ji budou využívat jednotlivé projekty.
A výsledek? Plně funkční projektové Money pak dostaneme takto:
public class Money : MoneyImplementationBase<Currency, Money>
{
// toť vše!
// sem už můžeme doplnit jen project-specific rozšíření Money, veškerou základní funkčnost nám poskytují předci
}
Hezké, že? Stejně jako u kouzlení s BusinessObjectCollection předhazujeme generickému předkovi sami sebe (pokud to bude potřeba, můžeme předhodit i svého potomka).
Po opakovaných přáních poodkrýt zákoutí našeho frameworku a generované business-vrstvy jsem se rozhodl zveřejnit vybraná témata a perličky. Dnes přicházím s BusinessObjectCollection, resp. s možným způsobem implementace kolekcí v business-vrstvě pomocí generik tak, abychom dosáhli maximální kompatibility typů a nemuseli bojovat s neustálou typovou neshodou kolekcí.
Čeho chceme u kolekcí business-objektů dosáhnout:
Jak tedy na to? Nejprve si zavedeme základní třídy, které budu v dalších příkladech používat:
public abstract class BusinessObjectBase
{
public abstract void Save();
}
public class Order : BusinessObjectBase
{
public decimal Cena { get; set; }
public override void Save()
{
// implementace
}
}
BusinessObjectBase je bázová třída pro všechny business-objekty, Order je příklad konkrétní implementace.
Nejprve si ukážeme, jak vypadá běžná implementace kolekcí:
public abstract class BusinessObjectCollection<T> : Collection<T>
where T : BusinessObjectBase
{
public BusinessObjectCollection()
: base(new List<T>())
{
}
public void SaveAll()
{
foreach (BusinessObjectBase item in this.Items)
{
item.Save();
}
}
public virtual List<T> FindAll(Predicate<T> match)
{
List<T> innerList = (List<T>)Items;
List<T> found = innerList.FindAll(match);
return found;
}
}
public class OrderCollection : BusinessObjectCollection<Order>
{
public decimal GetCelkovaCena()
{
decimal result = 0;
// implementace
return result;
}
}
Co tato implementace ukazuje:
OrderCollection orders = Order.GetAll();
List<Order> zeroOrders = orders.FindAll(delegate(Order item)
{
return (item.Cena == 0);
});
zeroOrders.SaveAll(); //nejde
decimal d = zeroOrder.GetCelkovaCena(); //nejde
OrderCollection zeroOrders2 = (OrderCollection)zeroOrders; // nejde
Návratovým typem FindAll() je List<Order>, s kterým přicházíme o veškerou logiku, kterou jsme business-kolekce naučili, jak v bázové třídě BusinessObjectCollection, tak v konkrétní tříděOrderCollection.
Zlepšení na poli typů dosáhneme, pokud implementaci naší BusinessObjectCollection<T> upravíme takto:
public abstract class BusinessObjectCollection<T> : Collection<T>
where T : BusinessObjectBase
{
public BusinessObjectCollection()
: base(new List<T>())
{
}
public void AddRange(IEnumerable<T> source)
{
List<T> innerList = (List<T>)Items;
innerList.AddRange(source);
}
public virtual BusinessObjectCollection<T> FindAll(Predicate<T> match)
{
List<T> innerList = (List<T>)Items;
List<T> found = innerList.FindAll(match);
BusinessObjectCollection<T> result = new BusinessObjectCollection<T>();
result.AddRange(found);
return result;
}
public void SaveAll()
{
foreach (BusinessObjectBase item in this.Items)
{
item.Save();
}
}
}
OrderCollection orders = Order.GetAll();
BusinessObjectCollection<Order> zeroOrders = orders.FindAll(delegate(Order item)
{
return (item.Cena == 0);
});
zeroOrders.SaveAll(); //HURÁ! Funguje!
decimal d = zeroOrders.GetCelkovaCena(); // stále nefunguje
OrderCollection zeroOrders2 = (OrderCollection)zeroOrders; // nejde
Stále nedostáváme od metody FindAll() kolekci typu OrderCollection. Sice jsme získali možnost využít operací implementovaných v BusinessObjectCollection, jako např. SaveAll(), stále však nedosáhneme na operace implementované v OrderCollection, nemáme GetCelkovaCena().
Generika v .NET Frameworku jsou udělána dobře a snesou všechno. Opakovaně jsem sám u generik předpokládal, že „tohle už přeci nemůže jít“. Generika opravdu unesou hodně:
public abstract class BusinessObjectCollection<TItem, TCollection> : Collection<TItem>
where TItem : BusinessObjectBase
where TCollection : BusinessObjectCollection<TItem, TCollection>, new()
{
public BusinessObjectCollection()
: base(new List<TItem>())
{
}
public void AddRange(IEnumerable<TItem> source)
{
List<TItem> innerList = (List<TItem>)Items;
innerList.AddRange(source);
}
public virtual TCollection FindAll(Predicate<TItem> match)
{
List<TItem> innerList = (List<TItem>)Items;
List<TItem> found = innerList.FindAll(match);
TCollection result = new TCollection();
result.AddRange(found);
return result;
}
public void SaveAll()
{
foreach (BusinessObjectBase item in this.Items)
{
item.Save();
}
}
}
public class OrderCollection : BusinessObjectCollection<Order, OrderCollection>
{
public decimal GetCelkovaCena()
{
decimal result = 0;
// implementace
return result;
}
}
Co jsme to udělali?
OrderCollection orders = Order.GetAll();
OrderCollection zeroOrders = orders.FindAll(delegate(Order item)
{
return (item.Cena == 0);
});
zeroOrders.SaveAll(); //HURÁ! Funguje!
decimal d = zeroOrders.GetCelkovaCena(); //HURÁ! Funguje!
// přetypovávat už ani nepotřebujeme
Generika snesou hodně, opravdu hodně. U nás je to takto:
Nejprve business-objekt Order:
public abstract class OrderBase : BusinessObjectBase
{
// toto je generovaná třída, generátor ji vždy přepíše, nic se sem nesmí ručně dopisovat
public decimal Cena { get; set; }
public override void Save()
{
// implementace
}
}
public class Order : OrderBase
{
// toto je používaná třída, sem mohu zapsat vlastní kód
public decimal GetCenaPoSleve()
{
// implementace
}
}
Dále business-kolekce OrderCollection:
public abstract class OrderCollectionBase : BusinessObjectCollection<Order, OrderCollection>
{
// toto je generovaná třída, generátor ji vždy přepíše, nic se sem nesmí ručně dopisovat
}
public class OrderCollection : OrderCollectionBase
{
// toto je používaná třída, sem mohu zapsat vlastní kód
public decimal GetCelkovaCena()
{
decimal result = 0;
// implementace
return result;
}
}
Ano! Generika umožňují dokonce v předkovi OrderCollectionBase používat jako TCollection odkaz na vlastního potomka OrderCollection.
Windows Vista Vás při pokusu o instalaci síťové tiskárny hostované na stroji Windows XP mohou obdařit elegantní chybovou hláškou o nemožnosti instalace s chybovým kódem 0x00000035. Obecně se jedná o chybu nenalezení síťové cesty, a to je i většinou problém Windows Vista při instalaci tiskárny. Windows Vista se totiž snaží tiskárnu nainstalovat nikoliv pomocí UNC share názvu, např. \\HAVIT\Printer1, ale pomocí názvu tiskárny, něco jako \\HAVIT\HP LaserJet P2015 PCL6, což se nepodaří.
Dá se postupovat jednou z následujících cest:
Avšak pozor, ani tento postup mně nepomohl, při pokusu o přidání portu jsem dostával hlášku Přístup odepřen, přestože jsem byl k hostitelskému počítači přihlášen s právy administrátora. Pomohlo na dobu instalace dát na tiskárně všechna práva skupině Everyone. Po úspěšné instalaci ve Vistách jsem práva zase odebral a nechal jen Tisk a vše v pohodě chodí.
Načítání webových stránek je pod Firefoxem v kombinaci s Windows Vista a ASP.NET Development serverem velmi pomalé. První podezření padlo na ASP.NET AJAX pod Firefoxem, nicméně pravda se ukázala být jinde.
Za všechno může automatické ladění TCP stacku ve Windows Vista. Na internetu jsem nalezl dvě řešení:
… lze provést spuštěním „netsh interface tcp set global autotuninglevel=disabled“ z příkazové řádky.
Toto řešení jsem nezkoušel.
Dle návodu pomůže ve Firefoxu:
Toto řešení bylo jednoduché a účinné.
Jaká je Vaše zkušenost s kombinací Firefoxu, Windows Vista a ASP.NET Development serveru? Nebo jste na stejný problém narazili i při jiné kombinaci produktů?
Jaký je rozdíl v prapagaci výjimky z bloku catch:
try
{
...
}
catch (Exception e)
{
throw;
}
versus
try
{
...
}
catch (Exception e)
{
throw e;
}
Odpověď je tentokrát jednoduchá a jako obvykle ji najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):
>>> Při vyhození výjimkt přes „throw;“ se nezmění stack trace – výjimka se stále tváří, že vzešla z původní metody. Při vyhození přes „throw e;“ je změněn stack trace, výjimka se tváří, že vzešla z naší metody obsluhující výjimku. <<<
Instalace IIS7 ve Windows Vista obsahuje více možností než tomu bylo dříve. V dobré víře jsem si zvolil nástroje pro správu a podporu ASP.NET v minimalistické podobě.
Díky podpoře nainstalovené podpoře ASP.NET fungovaly webové aplikace, ovšem statický obsah (jako jsou právě obrázky nebo soubory kaskádových stylů) prohlížeč neobdržel. Při zadání libovolné URL na statický obsah (například i adresa k obrázku, který ani na disku není) prohlížeč nezobrazí nic (i zdrojový kód stránky je prázdný).
Aby IIS7 poskytoval statický obsah, je nutné mezi instalovanými komponentami IIS zvolit i „Statický obsah“.
Jaký je rozdíl v zachytávání výjimek při použití typu výjimky Exception
try
{
...
}
catch (Exception e)
{
...
}
a bez použití tohoto typu, tedy
try
{
...
}
catch
{
...
}
Zdůrazňuji, že tento rozdíl existuje jen v .NET Frameworku 1.x, ve verzi 2.0 jsou způsoby funkčně rovnocenné.
Odpověď najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):
>>> Konstrukce catch (Exception e) zachytává jen CLS-compliant výjimky, catch zachytává všechny chyby. Praktický rozdíl je při zachytávání chyb z COM objektů, jejichž chyby nejsou CLS-compliant výjimkami. .NET Framework 2.0 tyto chyby z COM objektů zabalí do RuntimeWrappedException, které jsou CLS-compliant, takže je chyba zachycena i při použití catch (Exception e). <<<
…a jako posledně: teď se přiznejte, kdo jste to znal!