|
|
Vývoj webových aplikací ASP.NET
-
Nedávno se mi připomněla jedna starší lahůdka (čti obskurnost, za kterou může ten zpropadený svět heterogenních browserů) - Device Filters. Dovolím si tedy připomenout:
Jde o víceméně elegantní deklaratorní zápis, jak nastavovat různé hodnoty jednotlivých properties controlů a direktiv v ASP.NET v závislosti na browseru. Syntaxe je jednoduchá:
<asp:Label IE:Text="Používáte Internet Explorer" Mozilla:Text="Používáte Firefox" PIE:Text="Používáte Pocket IE" Text="Používáte Buchvíco" runat="server" />Není to jen o properties controlů, dá se to použít i pro direktivy, takže například pro <%@ Page %> lze nastavit MasterPageFile či Theme. Rozpoznávané browsery se řídí browser-definition-files (App_Browsers, browserCaps).
|
-
Že Microsoft ASP.NET team si už delší dobu hraje s věcmi kolem CSS Sprites se vědělo už dlouho. Nakonec nečekají na další release ASP.NET a uvolnili svůj ASP.NET Sprite and Image Optimization Framework už nyní (na Codeplex). Pokud tedy chcete na svých webech optimalizovat rychlost načítání a zobrazování obrázků, určitě doporučuji Vaší pozornosti. Kdo netušíte, o co jde, potom viz instruktážní video (ve zkratce jde o slučování několika obrázků do jednoho a zobrazování výřezů z něj na Vaší webové stránce pomocí CSS stylování s background-image).
|
-
Měl jsem příležitost zúčastnit se beta programu nových certifikačních zkoušek Microsoftu pro platformu .NET 4, nedá mi to tedy, abych se s Vámi nepodělil o první dojmy (výstup je naštěstí již značně cenzurován, za ty dva dny už jsem trochu vychladl a nepublikovatelný proud mého hodnocení dopadl jen na prvních pár lidí, které jsem potkal).
První zkouška, kterou jsem testoval, byla zkouška na datové přístupy z kategorie Technical Specialist. Dle očekávání a zaměření se jednalo o "kodérskou" zkoušku, kde nešlo o nějaké koncepční myšlení, ale otázky směřovaly většinou na konkrétní code-snippety, nebo jiné coding techniky pro přístup k datům. V podstatě se vše točilo kolem následujících témat: - Entity Framework - odhadem 40%
- LINQ, LINQ to SQL - odhadem 20%
- pure DB Access (DbConnection, DbCommand, ...) - odhadem 10%
- "old fashion" (DataSet, DataTable, TableAdapter, ...) - odhadem 10%
- ostatní všechno možné (XML, ADO.NET Data Services, Sync Services, konfigurace, ...) - odhadem 20%
V zásadě se jedná o standardní zkoušku tohoto "kodérského" typu, kde se opět objevilo pro mě nepochopitelné množství otázek směřujících čistě na názvy/signatury jednotlivých metod. Něco, co v reálné praxi zcela řeší IntelliSense. Nesnáším otázky typu "Abyste udělali XY, použijete metodu: ExecuteQuery(), ExecuteQuery(true), ExecuteQuery(false) nebo ExecuteQuery<Order>()?". Naopak tam úplně chyběla sebemenší otázka na SqlTransactions, T-SQL, SQL-injection, apod., kteréžto znalosti bych od člověka certifikovaného na Data Access očekával (narozdíl o detailů o předávání Table Parameters nebo Spatial Data z kódu a podobných nuancí, které si každý snadno najde, když je potřebuje). Celkové zkoušku hodnotím jako standardního následovníka dosavadních MCP kodérských zkoušek, kde Microsoft/Prometric nedokázali překonat svůj stín a místo, aby se soustředili na skutečné ověření dovedností vývojáře potřebných v každodenní praxi, tak šlo opět o koncentraci na novinky a okrajová témata, které jsou zrovna "in". Člověk, který takovou zkoušku úspěšně absolvuje, není vůbec ověřen na základní koncepty přístupu k datům, spíše se dokázal připravit na požadavky zkoušky způsobem, který mu umožnil ji absolvovat (ať už pomocí zkušebních testů, uniklých otázek, nebo tvrdým biflováním faktů), nicméně stejně nelze úspěšně předpokládat, že by si běžný vývojář detaily, které jsou předmětem zkoušky zapamatoval déle než potřebný půlden.
Druhá zkouška, kterou jsem testoval, byla z kategorie Professional Developer pro Web Development. Zkouška by se měla zaměřovat na koncepty a znalost technologie, prakticky bez kódování. V hodnocení této zkoušky jsem bohužel mnohem radikálnější, točila se totiž celá kolem následujících témat: - MVC - odhadem 50%
- ASP.NET Core Infrastructure - odhadem 25%
- WebForms - odhadem 10%
- Ostatní (Data Access, WCF, Silverlight, jQuery, ...) - odhadem 15%
Totální zahlcení problematikou MVC mě velmi nemile překvapilo. Nikdy jsem neměl nic proti lidem, kteří se rozhodli věnovat této technologické masturbaci (dovoluji si vypůjčit trefné označení kolegy Michal Altaira Valáška), ale kdo v Microsoftu/Prometric došel k závěru, že pro testování dovedností samostatného webového vývojáře (dovedností relevantních pro běžnou praxi!), je nutné tak silně zdůraznit nové MVC a prakticky zcela opustit WebForms přístup, to mě hlava nebere. Toto mě znechutilo natolik (a tento feedback, že by se měly oddělit zkoušky na MVC a Webforms, jsem dával do Microsoftu už dávno v dobách, kdy ještě bylo co měnit), že odsuzuji tuto zkoušku jako absolutně irelevantní vzhledem k hodnocení reálného webového vývojáře. Pokud bych měl posuzovat jeho dispozice a dovednosti pro reálnou praxi vývoje webových aplikací, tato zkouška by mi toho mnoho neřekla. Bohužel i těch několik otázek, které by tématicky patřily do podobné zkoušky, bylo položených tak, že často vůbec nesměřovaly k jádru věci, ale jednalo se např. o vyloučení zcestných odpovědí, aby zůstaly ty méně špatné. Když se Vás někdo zeptá, které dva druhy ovoce byste doporučili někomu, kdo má rád sladkosti, tak Vám z nabídky "okurky", "igelit", "brambory" a "mramor", nezbývá než zaškrtnout... (ne, bohužel tlačítko WTF testovací aplikace nenabízí a co považují autoři za správnou odpověď se můžeme jenom dohadovat).
|
-
XmlDataSource má poměrně předurčený scénář použití a dokáže velmi nemile překvapit, pokud zapojíte vlastní fantazii a v dobré víře ho použijete jinak (ač zdravý člověk by očekával, že to fungovat bude). Tak především:
- vlastnost EnableCaching je defaultně nastavena na true (což není úplně očekávané, ale budiž, dokumentované),
- Cache je však poměrně zákeřná a ne vždy reflektuje vaše změny, vše se odvíjí od implementace metod GetXmlDocument() a CreateCacheKey(), z nichž lze vyčíst, co všechno považuje XmlDataSource za změnu situace vyžadující reload dat a co ne.
- tak například změna hodnoty property Data (zápis nového XML stringu) změnou pro Cache není a XmlDataSource vesele vrací stále původní XML až do vypršení Cache!
- za změnu se považuje zejména změna v hodnotě DataFile nebo TransformFile, dále pak změna souboru, na který se DataFile nebo TransformFile odkazují,
- XmlDataSource je tedy předurčeno pro použití právě přímo s XML soubory uloženými na disku, na které se odkazujete pomocí DataFile a TransformFile, zatímco přímé nastavování property Data je poněkud nedotažený scénář (i když přípustný).
...z čehož se pro mě jen potvrzuje, že datasources (CokolivDataSource) jsou cesta do pekel. :-)
|
-
Na Microsoft DevDays 2009 v Praze a Ostravě jsem mimo účasti na MVP stánku Ask the Experts prezentoval dvě dema svých nejoblíbenějších novinek v Dev10 platfromě. Jejich zdrojové soubory Vám nyní nabízím ke stažení:
Na webu www.devdays.cz jsou ke stažení prezentace, webcasty a screencasty k dalším přednáškám této akce.
|
-
V nastavení application poolu na IIS je zastrčeno jedno nastavení s výchozí hodnotou 1, které je poměrně zrádné, pokud ho někdo změní a neví, co to přesně znamená.
Jde o volbu "Maximum Worker Processes" a neznalé svádí tuto hodnotu z jedničky zvětšit na vyšší číslo.
Co to však ve skutečnosti znamená - jde o to, že IIS pustí pro Application Pool více instancí Worker Processu (w3wp.exe), které jsou navzájem izolované. Vznikne tak více nezávislých instancí aplikací běžících na daném Application Poolu a celé se to začne chovat jako Web Garden (= lokální virtuální Web Farm na jednom serveru). Následky jsou pak prakticky stejné, jako běh aplikace na webové famě - instance aplikace mezi sebou nezdílí kontext (Application, Cache, in-proc Session, atp.).
Protože každý Váš request pak může vyřdit jiná instance aplikace, projevy mohou být např. následující:
- ztrácí se Vám Session, pokud používáte výchozí InProc session (hodnoty Session jsou uloženy na jiném "serveru", než který vyřizuje aktuální request),
- nefunguje dobře cachování (každý "server" si udržuje vlastní cache a musí se tedy naplnit tolik cachí, kolik "serverů" Vám běží),
- ztrácí se Vám hodnoty Application contextu (opět stejný důvod),
- nefungují Vám grafy DevXpress (protože si obrázek ukládají do lokálního contextu v okamžiku renderování stránky s grafem a požadavek browseru na obrázek grafu se dostane na jiný "server"),
- atp. atp.
Osobně v běžném provozu nenacházím scénář, kde by Maximum Worker Processes mělo být nastaveno jinak než na 1. Dokážu si představit jedině, že někdo chce záměrně testovat chování své aplikace na webové farmě, nebo nějaké obskurní důvody s unmanaged componentami, nebo řešením nedostatečné thread-safety aplikace).
|
-
Pokud Vám po nastavení ASP.NET routingu na IIS7 v integrated-mode nechodí na stránkách Session, pak je potřeba elementu system.webServer/modules nastavit atribut runAllManagedModulesForAllRequests na true:
<system.webServer> <modules runAllManagedModulesForAllRequests="true"><!-- bez tohoto s ASP.NET Routingem nechodí Session v integrated-mode --> <remove name="UrlRoutingModule" /> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> </modules> ......ono to není jenom o Session, pro ASP.NET Routing se v integrated-mode nespouští moduly, které mají pre-condition managedHandler.
|
-
|
Na zajímavý problém jsem narazil při implementaci použití CMS a našeho prezentačního frameworku. Obojí je kompilováno pro .NET 2.0, používáme je s .NET Frameworkem 3.5. CMS načítá *.ascx standardní metodou:
Page.LoadControl("~/Test.ascx"); Bohužel se objevil problém s načítáním assembly, které jsou ve web.configu redirectovány na nové verze:
Could not load file or assembly 'System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. Systém nemůže nalézt uvedený soubor.
<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35"/> <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35"/> <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0"/> </dependentAssembly> </assemblyBinding> </runtime>
Příčina a řešení problému byla taková, že jsem v konfiguračním souboru musel odstranit namespace (atribut xmlns) z elementu configuration.
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
|
-
.NET Framework 3.5 SP1 přináší alternativní přístup k URL Rewritingu - tzv. ASP.NET Routing, ve zkratce:
- jde o modul System.Web.Routing.UrlRoutingModule a související třídy, vše z assembly System.Web.Routing (novinka v SP1 k .NET 3.5),
- modul pracuje nad globální routovací tabulkou (RouteTable), v které je uložena uspořádaná kolekce Routes (v podstatě seznam postupně vyhodnocovaných pravidel - instancí třídy Route, resp. potomků RouteBase)
- pravidlo v kolekci Routes může mít název, na který se pak dá odkazovat při zpětném sestavování URL,
- pro každé pravidlo (pattern cesty) je v kolekci uložen odkaz na IRouteHandler, který má daný request obsloužit (IRouteHandler předepisuje jedinou metodu GetHttpHandler(), která vrací příslušný IHttpHandler pro odbavení requestu)
- ASP.NET routing nedělá "rewriting", tedy nevymění surově za běhu requestu ASP.NETu cestu, ale jde o čisté řešení používající přímo nasměrování na odpovídající HttpHandler
- routovací tabulku lze využít (a je to zásadní výhoda ASP.NET routingu) i ke zpětné rekonstrukci URL a tedy lze udělat čistě routing obousměrný (adresu veškerých odkazů a redirectů v aplikaci zjišťuji z routingu metodou GetVirtualPath).
Jak tedy implementovat ASP.NET Routing pro WebForms (původně byl zamýšlen především pro MVC, používá ho i Dynamic Data):
- Nareferencujeme assembly System.Web.Routing (ve VS přes References, popř. ve web.configu do system.web/compilation/assemblies.
- V příslušné částí web.configu (dle verze IIS system.web/httpModules, nebo system.webServer/modules) zapojíme do hry modul UrlRoutingModule:
<add name="RoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
- Vytvoříme routovací tabulku (typicky z Application_Start v global.asax)
private void Application_Start() { RegisterRoutes(RouteTable.Routes); }
public static void RegisterRoutes(RouteCollection routeCollection) { routeCollection.Add("Test", new Route("test/{MyParam}", new MyRouteHandler("~/MyFolder/Page.aspx"))); }
- v patternu pro routu dáváme parametry do složených závorek {parametr} - parametry od sebe musí být oddělený textovou konstatnou, typicky lomítkem /, ale třeba i {language}-{country}/{something} pro cs-CZ/test - na konci patternu můžeme použít hvězdičkový parametr {*parametr}, který požere veškerý zbývající text do konce řetězce (vstupního URL) - případné další parsování tohoto parametru je už na nás - připomínám, že pravidla se vyhodnocují v pořadí v jakém jsou v kolekci a uplatní se první vyhovující pravidlo (není tedy dobré začínat {*myParam}, dál už se nedostanete)
- Vytvoříme třídu implementující IRouteHandler, která bude requesty obsluhovat, v případě WebForms vracíme regulérní IHttpHandler (potomek Page) odpovídající potřebné připravené stránce, která má request obsloužit
public class MyRouteHandler : IRouteHandler { private string _targetVirtualPath;
#region Constructor public MyRouteHandler(string targetVirtualPath) { _targetVirtualPath = targetVirtualPath; } #endregion
#region GetHttpHandler /// <summary> /// Provides the object that processes the request. /// </summary> /// <param name="requestContext">An object that encapsulates information about the request.</param> /// <returns>An object that processes the request.</returns> public IHttpHandler GetHttpHandler(RequestContext requestContext) { // zkopírování parametrů z route do HttpContext.Items foreach (var urlParm in requestContext.RouteData.Values) { requestContext.HttpContext.Items[urlParm.Key] = urlParm.Value; } return BuildManager.CreateInstanceFromVirtualPath(_targetVirtualPath, typeof(Page)) as IHttpHandler; } #endregion }
- Část kódu v metodě GetHttpHandler ukazuje vyzvednutí parametrů zjištěných z routy a možný způsob jejich předání do stránky (přes kolekci Items HttpContextu).
- Ve stránce pak můžeme na takto předané parametry přistupovat
TestLb.Text = HttpContext.Current.Items["MyParam"].ToString();
- A pokud bychom chtěli zkonstruovat URL stránky na základě pravidel v RouteTable použijeme např.
Response.Redirect(RouteTable.Routes.GetVirtualPath(null, "Test", new RouteValueDictionary() { {"MyParam", "hodnota"} }).VirtualPath);...chceme URL od routy Test při použití parametrů MyParam=hodnota. Pokud bychom název cesty nezadali, najde routing sám první routu, která předané parametry používá.
Routingu se ještě dá nastavit spousta dalších věcí:
- defaultní hodnoty parametrů pro každou routu,
- constrainty na jednotlivé parametry routy (regulárním výrazem, nebo i vlastním kódem vracejícím boolean),
- jestli má reflektovat requesty na fyzicky existující soubory (třeba /Default.aspx), nebo jestli má požírat vše a řídit se výhradně definovanými routami,
- ...a pár dalších věcí.
Je potřeba si dát pozor zejména na:
- autorizaci - běžná autorizace (web.config, element authorization) nastavená na fyzické soubory se při routingu neuplatní, cesta je totiž jiná, museli bychom nastavit elementy location na nové cesty, ale ztratíme tím výhodu jediného místa s definicí URL (routovací tabulky) - doporučuje se autorizaci řešit z kódu,
- relativní cesty (např. k obrázkům, stylům, atp.)
Viz též:
Doporučené zdroje:
|
-
Microsoft přišel se svým "vlastním" controlem na renderování grafů do webových stránek (ve skutečnosti je to zřejmě Dundas "Lite"). <asp:Chart runat="server"/>Každopádně je to zajímavá alternativa ke komerčním komponentám třetích stran a přestože podstatně chybí design-time podpora, určitě se na nové grafy podívám. Control, dokumentace i rozsáhlá dema jsou ke stažení. Krátké intro-video můžete zkouknout i na MSTV.cz.
|
-
Při bezstarostném používání Image.ImageUrl jsem narazil na zajímavý problém - pokud se Vám v názvu souboru nebo v cestě k němu vyskytne % (procenta) nebo # (hash), pak máte problém - metoda HttpUtility.UrlPathEncode(), kterou Image.ImageUrl a mnohé další controly pro encodování URL odkazů používají, Vám tyto znaky neencoduje:
<asp:Image ImageUrl="~/Folder #3/File.jpg" runat="server" />
udělá
<img src="/Folder%20#3/File.jpg" />
protože
HttpUtility.UrlPathEncode("/Folder #3/File.jpg") == "/Folder%20#3/File.jpg"Metoda UrlPathEncode() totiž encoduje jen mezerník a non-ASCII znaky.
Správné samozřejmě je, abyste se na webových serverech pokusili těmto znakům vyhnout. Nicméně uživatelé jsou tvořiví a tak se můžete dočkat překvapení stejně jako já.
Osobně si myslím, že se jedná o bug v metodě UrlPathEncode(), resp. o nenaplnění očekávaného contractu, nicméně Microsoft to samozřejmě interpretuje jako by-design a radí, ať si uděláte Replace() těchto znaků sami.
Nezbývá tedy než:
myImage.ImageUrl = path.Replace("%", "%25").Replace("#", "%23");Mimochodem obdobných vychytávek se můžete dočkat i se znakem + (plus).
|
-
Natočil jsem screencast o transformacích web.config souborů při nasazování webových aplikací ve Visual Studiu 2010. Video si můžete prohlédnout na MSTV.cz.
Ke screencastu si můžete stáhnout prezentaci a demo.
|
-
AJAXový UpdatePanel se chová zvláštně vůči logice naming-containerů, pokud se tedy potkáte s názvy (ID) controlů, nepůjde Vaše stránka zkompilovat. Stačí zkusit následujících jednoduchý snippet:
<asp:TextBox ID="SomethingTB" runat="server" /> <asp:Repeater ID="MyRepeater" runat="server"> <ItemTemplate> <asp:UpdatePanel runat="server"> <ContentTemplate> <asp:TextBox ID="SomethingTB" runat="server" /> </ContentTemplate> </asp:UpdatePanel> </ItemTemplate> </asp:Repeater>...při kompilaci budete obšťastněni chybovými hláškami:
D:\Development\UpdatePanelCompiler\Default.aspx(18,57): error CS0102: The type '_Default' already contains a definition for 'SomethingTB' c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\updatepanelcompiler\1c44388f\ad38e94\App_Web_r7xfjqxf.0.cs(231,59): error CS0111: Type 'ASP.default_aspx' already defines a member called '__BuildControlSomethingTB' with the same parameter types
|
-
Po prázdninové odmlce jsem tu zpět s další čerstvou zkušeností, kterou jsme udělali na jednom z našich projektů.
Story
Společné uživatelské rozhraní pro vytváření nových a editaci stávajících záznamů mělo části, které byly použitelné jen pro existující objekty, mj. i možnost připojení souborů pres control FileUpload. Části použitelné jen u existujících objektů byly skryty pomocí Visible="fase" a zobrazovány pomocí UpdatePanelu, který je zobrazil po uložení nového objektu.
Problém byl v tom, že u nově vytvořených objektů nebylo možné připojit soubory, zatímco u existujících objektů to bez problémů šlo. Ukázalo se, ze control FileUpload v postbacku žádný soubor nedostane (HasFile bylo false), přestože uživatel soubor do formuláře zadal. Stejně se to chovalo ve všech prohlížečích a Fiddler potvrdil, že se z klienta žádný soubor nepřenesl.
Primitivní testovací kód by mohl vypadat třeba takto:
<form id="form1" runat="server"> <asp:ScriptManager EnablePartialRendering="false" runat="server" /> <asp:UpdatePanel runat="server"> <ContentTemplate> <asp:FileUpload ID="FileFU" Visible="false" runat="server" /> <asp:Label ID="HasFileLb" runat="server" /> <asp:Button ID="SaveBt" Text="Save" runat="server" /> </ContentTemplate> </asp:UpdatePanel> </form>
void SaveBt_Click(object sender, EventArgs e) { FileFU.Visible = true; HasFileLb.Text = FileFU.HasFile.ToString(); }
Po chvilce ladění se ukázala příčina problému. V režimu editace existujícího objektu formulář již od prvního requestu renderoval control FileUpload, který si sám do elementu form doplňuje potřebný atribut enctype="multipart/form-data", zatímco v režimu založení nového objektu se control FileUpload renderoval až AJAXem z UpdatePanelu po uložení nového objektu. UpdatePanel však v DOM stránky vymění jen svoji část a element form zůstává nedotčen, bez atributu enctype.
Summary
Pozor na to, ze AJAXovy partial rendering pomoci UpdatePaneliu vymění pouze určitou část DOM stránky a nesmi se zapomínat na vztahy teto části se zbytkem stránky. Většinou jsou tyto vztahy zřejmé, záludnost s FileUpload a atributem enctype vsak může pozlobit.
Možným řešením je například nastavování hodnoty atributu z kódu už při prvním requestu, přestože control FileUpload ještě na stránce není:
this.Page.Form.Enctype = "multipart/form-data"
|
-
Nedávno se nám v IIS7 na serveru (Windows Server 2008 Standard x64 EN) začaly množit výjimky:
Exception information: Exception type: System.Runtime.InteropServices.COMException Exception message: The I/O operation has been aborted because of either a thread exit or an application request. (Exception from HRESULT: 0x800703E3)
Nejdřív k výjimkám docházelo jen tak občas, ale vzestup počtu byl doslova raketový a naše aplikace se staly nepoužitelnými. Proč chyby vznikly nevíme, máme malé podezření na aktualizaci kb947562, ale situace se po jejím odinstalování nezlepšila. Zdá se, že vyřešit popsaný problém pomohla změna nastavení Managed pipeline mode aplikačního poolu z "Integrated" na "Classic".
|
|
|
|