Category Archives: Development

TransformXml – MSBuild task pro transformaci XML souborů ála web.config transformace (XDT)

Uff, dalo mi hodně práce vymyslet titulek tohoto článku, aby alespoň trochu vyjadřoval, co článek popisuje – tedy jak pomocí nové techniky Visual Studia 2010 pro web.config transformace, tedy pomocí transformačních XDT souborů (XML Document Transformation Engine), transformovat libovolný jiný XML soubor jako součást běžného buildu.

Pokud nevíte, jak fungují ve Visual Studiu 2010 transformace web.config souborů při deploymentu, podívejte se na můj screencast na MSTV (12:48 minut) nebo na materiály z prezentací (vč. demokódu). Pokud Vás zajímá, jak tímto způsobem transformovat libovolný jiný XML soubor (ať už .config, .sitemap, nebo jakýkoliv jiný .xml), potom čtěte dále.

TransformXml MSBuild task

Celé kouzlo spočívá ve využití připraveného TransformXml MSBuild tasku, který je použit i v připraveném targetu TransformWebConfig používaném při deploymentu webových aplikací (vytváření Package). Task TransformXml je prostý, má tři parametry a vypadá takto:

<TransformXml Source="path/original.xml" Transform="path/transformation.xml" Destination="path/transformed.xml" />

Abyste však tento task mohli použít v libovolném projektovém (.csproj, .vbproj, …) souboru (nejenom tedy webu, ale i ConsoleApplication, ClassLibrary, či čehokoliv jiného mimo WebSite), musíte jej importovat instrukcí using z assembly webového deploymentu:

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>

No použít task můžete samozřejmě v rámci libovolného targetu, celé to pak může v .csproj souboru vypadat takto:

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<Target Name="AfterBuild">
    <TransformXml Source="Menu\default.xml" Transform="Menu\MyTransform.xml" Destination="Menu\MyMenu.xml" />
</Target>

Target AfterBuild je připravený a automaticky spouštěný po základním buildu projektu. Znáte-li fungování projektových souborů a MSBuildu, dále číst nemusíte.

Editace projektových souborů (.csproj/.vbproj/.*proj)

Projektové soubory, jak je znáte z Visual Studia, nejsou ve skutečnosti nic jiného, než MSBuild instrukční soubory, které nejenom drží pohromadě konfiguraci Vašeho projektu, ale představují především zadání pro MSBuild, jak má Vás projekt v různých situacích buildovat.

Nebojte se tedy s těmito soubory pracovat a editovat je! Nejedná se o žádné hackování Visual Studia, ale o zamýšlený způsob ovládání Microsoftího build-procesu. Visual Studio pro Vás tyto soubory typicky založí, během práce s projektem je aktualizuje, nicméně je nepřegenerovává, ale pouze edituje a je opravdu na Vás, pokud chcete buildování Vašeho projektu nějak obohatit, abyste s těmito XML soubory pracovali.

Podrobnější popis fungování těchto souborů je mimo rozsah tohoto článku, pro začátek Vám však značí znát pojmy jako target a task.

MSBuild task je primitivní úkon, který má MSBuild během své práce vykonat. Např. něco zkompilovat, smazat, zkopírovat, založit složku, zkomprimovat do ZIP souboru, transformovat XML, atp. V projektovém souboru se zapisuje jako XML emelent a jeho nastavení se provádí pomocí atributů, viz např. výše uvedený TransformXml task. Základní sada tasků je v MSBuild integrována, další lze připojit jako doplňky (viz výše uvedený Using, který zavádí task TransformXml z .NET assembly).

MSBuild target je něco jako procedura, sada tasků nebo jiných targetů, které se mají vykonat. Pokud např. Visual Studio builduje Váš projekt, volá target „Build“, při publishingu webových aplikací se používají targety jako „CreatePackage“ nebo „TransformWebConfig“. Výchozí konfigurace zahrnuje speciální template-targety „BeforeBuild“, „AfterBuild“ a podobné, kam se snadno můžete zapojit se svými tasky při běžných buildech.

No a to co my ve skutečnosti při výše uvedených transformacích XML souborů děláme, je ve skutečnosti to, že do targetu AfterBuild přidáme task TransformXml (před tím zavedený pomocí Using), který v patřičné fází buildu transformaci provede.

Jak na editaci projektových souborů z Visual Studia:

  1. Projekt, do jehož projektového souboru budete zasahovat, je zpravidla potřeba „unloadovat“ (jen málokterý projektový soubor lze editovat, pokud je projekt zaveden, např. u .wdproj Web Deployment Projectu to lze). V Solution Exploreru tedy pravým tlačítkem klikneme na projekt a dáme „Unload project“ (unloadovaný project se sbalí a zašedne).
  2. Nyní můžeme v Solution Exploreru pravým tlačítkem na projekt a dát „Edit project file“
  3. V typickém projektovém souboru najdete targety AfterBuild a BeforeBuild jako připravené zakomentované XML elementy (odkomentujte!), obvykle někde ke konci souboru (na začátku je zpravidla nastavení projektu, uprostřed používané soubory, ke konci buildové věci):
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->

4.  Po potřebných úpravách souboru a jeho uložení stačí opět dát pravým na projekt v Solution Exploreru a „Reload Project“.

5.  Nyní můžete buildovat…

Pokud zapomenete udělat Reload Project, projekt se Vám bude při buildu ignorovat, což nemusí být hned zjevné.

No files were found to look in. Find was stopped in progress. (Hledání v souborech, VS 2008)

Při pokusu o hledání v souborech se mi zobrazuje hláška: No files were found to look in. Find was stopped in progress.
Říkám si hmm, asi bug. Restartuju Visual Studio a bude vyřešeno.
Restart nepomohl.
Chvilku googluju a nacházím návod, který (stejně jako autor článku) zařazuji kategorie „Kdybych to neviděl, tak bych tomu nevěřil.“ Cože? Zmáčknout Ctrl+ScrollLock a začne to fungovat? Skutečně! Neuvěřitelné. Co to je? Jak na to někdo přišel? Smekám…

UPDATE: Ve VS 2010 (x64) mi se stejným problémem právě pomohl stisk samotné klávesy Break/Pause.

XmlDataSource – pozor na jeho cachování při nastavování property Data

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. :-)

ClickOnce – spouštění při startu Windows, dvojí konfigurace a podobné záludnosti

Trochu nedobrovolně jsem byl dnes donucen šťourat se podrobněji v ClickOnce útrobách, přestože to není moje oblast zájmu.

Symptomy problému byly zajímavé – aplikace instalovaná pomocí ClickOnce, která v sobě měla volbu „Automaticky spouštět při startu Windows“, se chovala divně. Zprvu se zdálo, že nastavení provedená v aplikaci (.NET Settings mechanizmus), se restartem počítače ztratí. Nakonec jsem diagnostikoval, že ve skutečnosti aplikace používá dvoje nastavení – jedna nastavení používaná po instalaci a při spuštění aplikace automaticky vytvořenou položkou z menu Start, druhá nastavení používaná při automatickém spuštění po startu Windows. Zároveň jsem v C:\Users\… našel v profilu uživatele dva různé user.config soubory, které se používaly pro tyto dva scénáře spuštění aplikace.

Chyba byla ve skutečnosti v nekoretním spouštění aplikace po startu Windows. ClickOnce totiž sám automatické spouštění po startu Windows nepodporuje a při vlastní implementaci nebyl na rozdíl od zástupce v menu Start totiž použit link typu .asppref-ms (link soubor pro spouštění ClickOnce apliace, který zajišťuje i aktualizace a další ClickOnce věci), ale nekorektně aplikace pro spuštění po startu Windows používala přímo odkaz na svůj .exe soubor. To způsobilo nejen použití jiného user.config souboru, ale i nefunkčnost automatických aktualizací a dalších vlastností ClickOnce.

Poučení tedy zní: Pokud chcete korektně spouštět ClickOnce aplikaci, je potřeba volat vytvořený soubor .appref-ms (vytvořený ClickOnce instalátorem do menu Start) a nikoliv se odkazovat na .exe soubor aplikace.

Chybný kód tak může vypadat:

Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true)
.SetValue("InformTrayReader", Application.ExecutablePath);

Zatímco „správně“ (při dalších omezeních) by to mohlo vypadat nějak takto:

Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true)
.SetValue("InformTrayReader", GetApplicationStartupLink());

private object GetApplicationStartupLink()
{
    const string productName = "název dle ClickOnce Publish nastavení";
    const string publisherName = "nastavení dle ClickOnce Publish nastavení";

    string allProgramsPath = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
    string shortcutPath = Path.Combine(allProgramsPath, publisherName);

    return Path.Combine(shortcutPath, productName) + ".appref-ms";
}

Viz též související info:

UPDATE pro Windows Vista

Pokud nedejbože potřebujete, aby to fungovalo pro nějakého nebožáka používajícího alfa-verzi od Windows7 zvanou Windows Vista (dodnes nechápu, jak mohl Microsoft něco takového vydat), potom je nutný ještě jeden workaround.

Visty totiž nepustí .appref-ms přímo, a to ani z registrů, ani kdybyste odkaz dopravili do Start/Startup. Funguje v nich ale spouštění .exe a kupodivu lze .appref-ms zavolat z našeho exe a spustí se. Stačí tedy našemu programu přidat přepínač příkazové řádky -LaunchClickOnce a při jeho zjištění v Main() místo aplikace samotné odstartovat jen .appref-ms a sám se ukončit.

Podrobnější popis s ukázkou konkrétního kódu najdete na stránce http://www.brokenwire.net/bw/Programming/116/run-clickonce-app-on-startup.

Optimalizace databázových aplikací – Slides, dema, záznam [SQL DevCon 2009, MS Fest 2009]

Slides a další materiály z přednášky na konferenci SQL DevCon 2009 a MS Fest 2009 (na obou konferencích byla prezentována velmi podobná verze):

Z přednášky byl pořizován záznam, který je k dispozici na našem YouTube Channelu:

Podobná přednáška v rozsáhlejší podobě se záznamem byla prezentována i pro WUG Praha v březnu 2013.

web.config transformace a Code Contracts – Dema [MS DevDays 2009]

Dema k mým ukázkám na konferenci MS DevDays 2009:

Záznam z vystoupení nebyl pořizován, na téma web.config transformací však doporučuji svůj screencast.

Pozor na IIS Application pool: Maximum Worker Processes (Web Garden)

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.

ASP.NET Routing – Když v IIS7 integrated-mode nechodí Session

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.

„Could not load file or assembly System.Web.Extensions“ při načítání controlů metodou LoadControl

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">

asp:Chart aneb grafy od Microsoftu

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.