|
|
Vývoj webových aplikací ASP.NET
-
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".
|
-
Vlastnost Visible třídy Control používáme k zobrazení a skrytí controlu ve stránce. Dokumentace říká něco mírně komplikovanějšího: Gets or sets a value that indicates whether a server control is rendered as UI on the page. Tato zdánlivá komplikovanost má zásadní význam. Getter property totiž vrací false, pokud je skrytý control nebo některý z jeho rodičů ve stromu controlů. Z mého pohledu jde o dosti nezvyklé a vůči ostatním vlastnostem nekonzistentní chování. Neočekával bych, že po nastavení vlastnosti na true budu číst false. Doporučuji tedy s vlastností Visible pracovat náležitě opatrně. PříkladMějme dva panely (PrvniPanel a DruhyPanel), přičemž chceme, aby byl vidět právě jeden z nich. Oba panely jsou obaleny vnějším panelem. <asp:Panel ID="VnejsiPanel" Visible="false" runat="server"> <asp:Panel ID="PrvniPanel" runat="server"> ... </asp:Panel> <asp:Panel ID="DruhyPanel" runat="server"> ... </asp:Panel> </asp:Panel>1 2 3 | PrvniPanel.Visible = true; // nějaký výraz, který tentokrát vrací true DruhyPanel.Visible = !PrvniPanel.Visible VnejsiPanel.Visible = true; |
- Na řádku 1 nastavíme první panel k zobrazení.
- Na řádku 2 přečteme property PrvniPanel.Visible. Ačkoliv jsme ji na řádku 1 nastavili na true, vrací false, neboť rodič VnejsiPanel ve stromu controlů je skrytý. I druhému panelu tedy nastavíme Visible na true.
- Na řádku 3 nastavíme vnější panel k zobrazení.
- Postupně jsme všem panelům nastavili Visible na true a bude tedy vidět vše, ačkoliv to tak na první pohled nevypadá.
|
-
Objevil jsem jednu nepříliš dokumentovanou vlastnost HoverMenuExtenderu z AjaxControlToolkitu - že umí "vyskakovací" obsah dočítat pomocí AJAXového callbacku na server. Nastavení je snadné, použijí se property DynamicXyz a jen je potřeba vědět (což se ukázalo jako největší kámen úrazu), jak je to vlastně celé zamýšleno a jak má vypadat serverová metoda (WebService), která má dynamický content vracet:
<asp:Label ID="TargetLb" Text="Ukažte sem, já se dočtu a vyskočím!" runat="server" /> <asp:Panel ID="PopupPanel" Style="display: none;" runat="server"> <%-- display:none - aby se při načítání nepřesýpala obrazovka --%> Statický obsah pop-upu. <asp:Panel ID="DynamicPopupContent" runat="server" /> </asp:Panel> <ajaxToolkit:HoverMenuExtender TargetControlID="TargetLb" PopupControlID="PopupPanel" DynamicServicePath="~/AjaxServices/MyService.asmx" DynamicServiceMethod="GetPopupContent" DynamicContextKey="Kontext, např. ID záznamu" DynamicControlID="DynamicPopupContent" runat="server" />a služba musím mít signaturu "string DoSomething(string contextKey)":
[WebService] [ScriptService] public class Sluzby : System.Web.Services.WebService { [WebMethod] [ScriptMethod] public string GetPopupContext(string contextKey) { return "Hello World " + contextKey; } }
Tip 1: Pokud má být celý pop-up tvořen jen dynamickým obsahem, můžete DynamicControlID nastavit na stejný control jako PopupControlID a nemusíte pak vnořovat žádný další Panel (nebo jiný control).
Tip 2: DynamicControlID nemusí být uvnitř PopupControlID, dynamický obsah můžete dočítat i do jiného místa stránky, i když to asi není moc běžné.
Tip 3: Metodu vracející dynamický obsah můžete umístit i přímo do stránky jako PageMethod, musí být pak statická a HoverMenuExtenderu se pak nenastavuje vlastnost DynamicServicePath.
|
-
ClientScriptManagerTřída ClientScriptManager je součástí .NET Frameworku od jeho vzniku. Její instance je běžně přístupná přes Page.ClientScript. Třída slouží k registraci klientských skriptů, které mají být ve stránce renderovány, a k další práci s klienskými skripty. Tato třída neví nic o AJAXu a asynchronním postbacku, pokud zaregistrujeme do stránky nějaký klientský skript, bude vyrenderován pouze v případě prvního načtení stránky (GET) nebo v klasickém postbacku (POST). Pokud je skript registrován v asynchronním postbacku, do browseru se nedostane.
ScriptManagerTřída ScriptManager je součástí ASP.NET Ajax 1.0 rozšířující .NET Framework 2.0 nebo .NET Frameworku 3.5. Tato třída rovněž slouží k registaci klientských skriptů do stránky. Metody pro registraci klienských skriptů jsou statické a disponují rozhaním pro pohodlnější použití. Skripty registrované při prvním načítání stránky (GET) a v klasickém postbacku (POST) jsou stejně jako v předchozím případě renderovány do stránky, skripty registrované v asynchronním postbacku MOHOU být předány do browseru uživatele. Každá z registračních metod existuje ve dvou přetíženích, které se liší typem prvního parametru - Control vs. Page. Například:
- RegisterClientScriptBlock(Page, Type, String, String, Boolean)
Skripty registrované touto metodou jsou do browseru předány v každém asynchronním postbacku.
- RegisterClientScriptBlock(Control, Type, String, String, Boolean)
Skripty registrované touto metodou v asynchronním postbacku jsou renderovány do stránky jen tehdy, pokud je renderován předaný control. Pokud tedy při asnychronním postbacku není control v update panelu nebo je v update panelu, který není renderován, potom není renderován ani registrovaný skript. Další metody pro registraci klientských skriptů jsou:
- RegisterClientScriptBlock - registruje blok kódu
- RegisterClientScriptInclude - registruje externí soubor s klienských skriptem
- RegisterClientScriptResource - registruje soubor s klienských skriptem z resources
- RegisterOnSubmitStatement - registruje kód vykonaný před postbackem
- RegisterStartupScript - registruje kód vykonaný během načtení stránky
|
|
|
|