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.

Instalace Windows 7 pomalá nebo končí černou obrazovkou

Při instalaci Windows 7 jsem se potýkal se dvěma problémy:

Instalace velmi pomalá

Na zobrazení každého dialogu se čekaly minuty, ke kopírování souborů jsem se nedostal, tak trpělivý jsem nebyl. Problém byl vyřešen vypnutím floppy disku v BIOSu (setupu) počítače. Floppy disk nemám.

Instalace končí černou obrazovkou

Druhý problém byl kurióznější. Instalace probíhala naprosto pohodově a svižně. Vše se na počítač nakopírovalo, po restartu se provedly další kroky. V okamžiku, kdy jsem se již měl přihlásit, najednou instalace skončila černou obrazovkou. Po všech možných pokusech (a dalších instalacích) jsem si všiml, že Windows vyhodnotili divně prioritu monitorů a jako primární označili přes HDMI zapojenou televizi televizi, přestože byla vypnutá. Instalace tedy proběhla korektně, přihlašovací dialog byl na televizi a sekundární monitor nic nezobrazoval. Po přihlášení stačilo jen změnit hlavní monitor.

Propojení počítače s TV přes HDMI: Problémy a jejich řešení

Propojení počítače s TV přes HDMI: Problémy a jejich řešení

Před nedávnem jsem zakoupil TV Sony Bravia a pomocí HDMI kabelu ji připojil k počítači. Po shlédnutí obrazu z počítače jsem se zděsil: „Tohle je ta kvalita? To snad ne!“ Nebyla, ke štěstí bylo potřeba provést několik nastavení.
Byl jsem překvapený, jak mizerné to je. V dnešní době, kdy máme TV s HDMI, grafické karty s HDMI a vše již je nějakou dobu na trhu, jsem se domníval, že prostě zapojím kabel a pofrčím. Omyl. Lituju všechny běžné uživatele, kteří investují do techniky nemalé peníze a nedostanou odpovídající kvalitu. Přestože technika ten potenciál rozhodně má.

Problém první: Obraz je rozmazaný, kus chybí

Obraz byl opravdu podivný, po stranách dokonce chybělo (nebylo zobrazeno) zhruba 30 pixelů. Děs.
Nejprve je nezbytné nastavit rozlišení, které odpovídá rozlišení televize, tj. pro FullHD 1920×1080. Poté na televizi nastavíme pixel mapping 1:1. Tím se zajistí, aby nedocházelo k přepočtům za účelem „vylepšení“ obrazu. Na Sony Bravia jej najdete v menu Nastavení obrazovky, Rozsah zobrazení, Plný obraz / Pixel (Screen, Display Area, Full Pixel).
Ověřit, že nedochází k přepočtům by mělo být snadné pomocí např. Malování. Nakreslete na monitoru vodorovnou a svislou černou čáru širokou právě 1 pixel. Při zobrazení na televizi by čára měla zůstat kontrastní, ostrá. Neměla by se rozmazat do více pixelů.

Problém druhý: Přepaly, podpaly

Po předchozím nastavení se obraz extrémně zlepšil. Stále to ale nebylo ono, obraz působil divným kontrastem, chyběly detaily ve stínech, světla byla přepálená. Příčinou je skutečnost, že televize předpokládá rozsah složek RGB signálu 16-235, zatímco počítač posílá 0-255. Proto hodnoty 0-16 jsou beznadějně černé, hodnoty nad 235 způsobují přepaly.
Otestovat toto je opět jednoduché, zase stačí malování. Vyrobte (vedle sebe nebo přes sebe) několik obdélníků s barvami RGB například (0,0,0), (16,16,16), (24, 24, 24), (32, 32,32) a (235, 235, 235), (248, 248, 248), (255, 255, 255). Zobrazte je na televizi. Obdélníky by měly být viditelně různé, pokud jsou všechny tmavé černé a všechny světlé bílé, musíte provést korekci. Pokud máte nekvalitní LCD monitor nebo jej máte špatně nastaven, můžete mít problém se zobrazením černých a zejména pak světlých boxů už na LCD!
Možností provedení korekce se uvádí několik, nicméně jediný, který mně zafungoval, je nastavit v ovladačích grafické karty jas a kontrast (NVIDIA Control Panel, Adjust desktop color settings). Při nastavování sledujte obraz na TV a snažte se najít ideální nastavení.
Tento krok jsem musel provést ve Windows XP.
Windows 7 zajistí výstup v RGB 16-235 samy, obraz je brilantní hned po instalaci systému.

Problém třetí: Zvuk

Přes DHMI je možné do televize dostat i zvuk. Většina nových grafických karet je vybavena digitálním vstupem (dva piny z boku karty), do toho je nutné přivést výstup ze zvukové karty. Z Creative Audigy jsem jej vytáhl z konektoru pro front panel, na webu lze najít popis pinů. Použil jsem kablík dříve používaný pro propojení CDROM se zvukovou kartou. Grafická karta se zvukem nic nedělá, pouze jej přenese do HDMI výstupu.
Pokud máte reproduktory lepší než ty v TV, pak se tímto bodem nezdržujte. Sám nakonec zvukový výstup přes TV nepoužívám.

Problém čtvrtý: Přehrávání videa

Při přehrávání videa různé programy a různá nastavení způsobily nejrůznější problémy: Zasekávání přehrávání, pády přehrávačů, cukaný obraz, černo na sekundárním displeji (televizi), obraz neroztažený na celou obrazovku a hlavně brutální ztrátu synchronizace zvuku od obrazu (jednotky sekund).
Řešení pro mne bylo neuvěřitelné: Nainstalovat si z Windows Update .NET Framework 3.5 (ačkoliv vývojář, na domácím počítači jsem jej neměl), stáhnout Windows Media Player Classic Homecinema. V nastavení výstupu (View, Options, Playback\Output) zvolit „EVR (Vista/.Net3)“.

Problém pátý: Titulky k videu

Jako titulkovadlo používám DirectVobSub. Ten ale ve Windows 7 nenaběhne sám, je nejprve potřeba jej nejprve zadat jako externí filtr: View, Options, External Filters, Add, DirectVobSub (Auto-loading version), OK, vpravo vyberte radiobutton „Prefer“, OK.

Děkuji Marku Chovancovi za rady, které mi dal jak při výběru TV, tak při jejím nastavení.

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

Pozor na ImageUrl, resp. UrlPathEncode() a znaky % (procenta) nebo # (hash)

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