HAVIT Knowledge Base

Vývoj webových aplikací, .NET, SQL, návrh
Welcome to HAVIT Knowledge Base Sign in | Join | Help
-
Home Články Forums Obrázky Soubory

.NET Framework

Microsoft .NET Framework, Base Class Library

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

     

  • X509Certificate2: CryptographicException: The system cannot find the file specified.

    Pokud vytváříte ve webové aplikaci X509Certificate2

    myCertificate = new X509Certificate2(rawData, password);
    //nebo
    myCertificate = new X509Certificate2(fileName, password);
    pak můžete být po nasazení své funkční aplikace na produkční server (např. hosting) obdařeni výjimkou

    System.Security.Cryptography.CryptographicException: The system cannot find the file specified.
    at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
    at System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[] rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, SafeCertContextHandle& pCertCtx)
    at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)
    at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData, String password)

    ...přestože soubor s certifikátem buď zaručeně existuje (fileName), nebo se ani nepoužívá (rawData).

     

    Problém souvisí s tím, že v produkčním prostředí Vaše aplikace beží pod uživatelským účtem, který nemá založen profil, který se má certifikát vytvořit (volně přeloženo, omlouvám se za případnou nepřesnost).

    Řešením je použití přetížení constructoru, kterým zvolíte cílové uložiště certifikátu:

    myCertificate = new X509Certificate2(rawData, password, X509KeyStorageFlags.MachineKeySet);
    //nebo
    myCertificate = new X509Certificate2(fileName, password, X509KeyStorageFlags.MachineKeySet);

  • Generátor klíčů GP WebPay (PayMuzo) hlásí chybu "Illegal key size"

    Při poslední z implementací GP WebPay (PayMuzo) se nám stalo, že nám generátor klíčů obchodníka zahlásil chybu

    exception encrypting data - java.security.InvalidKeyException: Illegal key size

    Po chvilce testování (na dvou různých PC) jsme zjistili, že to dělá při Heslu pro keystore delším 8 a více znaků. Pokud jsme heslo zkrátili na 7 a méně znaků, vygenerování klíčů proběhlo úspěšně.

    Verzi utility lze popsat jako "(c) 2004/2005 Muzo a.s." v UI a velikost souboru GenCert.jar 33567 bytů, víc mě nad tím bádat nebaví.

     

    Pokud byste někdo bojoval s implementací GP WebPay pro .NET, tak se mi klidně ozvěte, máme to hotový a určitě se nějak dohodneme... ;-)

  • MVP Summit 2009 1. - Code Contracts

    Zrovna jsem přišel z přednášky o "Code Contracts" a jsem relativně nadšen tím, co Microsoft chystá. Naštěstí již je to k veřejnému testování přes DevLabs, obecné věci nejsou pod NDA, a proto se s Vámi mohu bez detailů podělit o první dojmy.

    Code Contracts je aparát, kterým bude možné v kódu (nezávisle na jazyce) definovat contract, tedy pravidla, které daná metoda/property/třída/... musí splňovat, například:

    public class MyClass
    {
      public int DoSomething(int vstup, int vstup2)
      {
        Contract.Requires(vstup != 0);
        Contract.Requires(vstup2 > vstup1, "argument vstup2 musí být větší než vstup1");
        Contract.Ensures(Contract.Result<int>() > 0, "návratová hodnota bude větší než 0");

        ...implementace metody
      }
    }

    Co by to mělo umět:

    • pre-conditions - Contract.Requires() -  podmínky, které musí být splněny pro spuštení metody - na začátku
    • post-conditions - Contract.Ensures() - podmínky, které budou splněny na konci metody
    • object invariants - [ContractInvariant] - podmínky, které musí být splněny stále (na konci každé operace)
    • asserts - Contract.Assert() - podmínky, které je možné vyžadovat průběžně v kódu (ála Debug.Assert())
    • ...

    Co by to mělo znamenat:

    • run-time checking - v podstatě náhrada za if (..) then throw new ArgumentXyException(...)
    • static verification - kontrola (třeba jakou součást buildu), že budou podmínky splněny
    • generování dokumentace - pravidla contract se stanou součástí dokumentace (např. místo současných ArgumentExceptions)
    • náhrada Debug.Assert() a podobných

    Jak to funguje:

    • třída Contract v podstatě nemá žádnou implementaci a funguje jako metadata, která se díky ní dostanou do kódu
    • post-build stepem v msbuildu se tato metadata vyhodnotí a provedou se modifikace zkompilovaných assembly dle požadovaných nastavení (zda dělat testy i v Release, zda vygenerovat assembly s contracty, atp.); procesně to lze přirovnat k Obfuskaci, nebo mechanizmům používaným v AOP (Aspekt Oriented Programmingu) 

    Odkazy

  • Na lokále mi aplikace chodí, ale při spouštění ze síťové cesty padá na SecurityException

    Problém, který poprvé zaskočí snad každého .NET programátora. Ani já nebyl kdysi výjimkou a tuto otázku dostávám stále dokola.

    Symptom je jednoduchý - při spouštění .NET aplikace z lokálního počítače vše krásně funguje, při spouštění přes síť (např. po deploymentu do sdílené složky) aplikace padá na SecurityException.

    Jádrem celého problému je .NETí mechanizmus Code Access Security Policy, který má mj. chránit hostující počítač před neoprávněným spouštěním kódu, popř. před závadným kódem. Stručně řečeno tento mechanizmus používá pro řízení práv k provádění jednotlivých operací sadu faktů zvanou Evidence (důkazy), přičemž jeden z podstatných faktů je zóna, z které je kód spouštěn (jde o klasické zóny Můj počítač, Lokální intranet, Internet, Důveryhodné servery, ...). No a výchozí nastavení na hostujícím počítači po instalací .NET frameworku říká, že kód spouštěný ze zón Intranet/Internet má určitá omezení, např. nesmí přistupovat k souborovému systému, či nesmí pracovat se sítí (SQL serverem), atp.

    V podstatě jsou dvě cesty k řešení:

    1. Rozšířit sadu Evidence aplikace tak, aby ve výchozím nastavení .NET frameworku měla aplikace práva lepší. Základem je např. podepsat aplikaci (strong-name), atp.
    2. Změnit výchozí nastavení zabezpečení .NET frameworku na hostujícím počítači tak, aby i aplikace z méně bezpečných zón směly provádět zabezpečené operace. Což provedeme:
      1. Spustíme MMC konzoli pro konfiguraci .NET Frameworku (z Nástrojů pro správu, popř. z Run mmc.exe a pak Přidat modul snap-in...)
      2. Donavigujeme se ve stromu na My Computer ~ Runtime Security Policty
      3. dáme Adjust Zone Security
      4. Make changes to this computer / to the current user only (dle potřeby)
      5. Vybereme zónu a šoupátkem zvýšíme její "trust". Full Trust znamená "bez omezení".

    Pozor, neměňte bezhlavě tato nastavení. Týkají se totiž i kódu spouštěného z internetu, např. i přímo z webových stránek. Můžete tak nechtěně otevřít bránu do svého počítače i pro nežádoucí kód.

    Správná cesta k řešení vede přes bod 1), nicméně připouštím, že pro začínajícího programátora a jednoduché síťové utilitky je to cesta složitější.

     

  • "Všechny" hotfixy .NET Frameworků a Visual Studií uvolněny a ke stažení z jednoho místa

    Na Microsoft Connect jsou k dispozici "všechny" hotfixy pro .NET Frameworky a Visual Studia ke stažení hezky z jednoho místa. Jedná se o nový "Visual Studio and .NET Framework Hotfix Public Availability Program".
  • Jednoduchý exception logging pomocí Trace/TraceSource mechanizmů .NET

    Pokud se sháníte po jednoduchém mechanizmu, jak ve Vašich aplikacích logovat výjimky, pak zřejmě v .NET pro consolové/WinForm aplikace marně hledáte něco, jako je healthMonitoring pro ASP.NET, kde je připraven jednoduchý, ale mocný nástroj a lze pomocí pouhých několika řádků ve web.config souboru získat přehled o výjimkách ve Vaší aplikaci, např.:

    <configuration>
        <system.web>
            <healthMonitoring enabled="true">
                <rules>
                    <add name="Mail Notifications on All Errors" eventName="All Errors" provider="SimpleMailWebEventProvider" profile="Default"/>
                </rules>
                <providers>
                    <add name="SimpleMailWebEventProvider" type="System.Web.Management.SimpleMailWebEventProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" from="errors@havit.cz" to="errors@example.cz" subjectPrefix="MyApplication: " buffer="true" bufferMode="Notification" maxEventLength="4096" maxMessagesPerNotification="2"/>
                </providers>
            </healthMonitoring>
        </system.web>
    </configuration>
    ...výše uvedená konfigurace Vám zajistí zasílání mailových notifikacích o neobsloužených výjimkách a chyách Vaší aplikace, jednoduché, prosté.

    Jak na to u consolových a WinForm aplikací?

    Pro consolové či WinForm aplikace nic jako healthMonitoring připraveno není, nicméně s pár řádky kódu lze dosáhnout obdobného efektu a dokonce u toho využít široké možnosti pro tracing v .NET 2.0. Základní myšlenka je prostá:

    • .NET 2.0+ v sobě obsahuje široké možnosti pro tracing, kdy můžeme definovat různé zdroje zpráv (TraceSource), zprávy různé rozdělovat (Switch), filtrovat (TraceFilter) a směrovat na různé listenery (výstup do konzole, XML, textového souboru, event-logu, SQL, SMTP, atd. další si můžeme napsat) - to vše bez nutnosti jakýchkoliv doplňků - ať už Microsoftích Enterprise Library / Application Blocků, či produktů třetích stran typu Log4Net. Základní přehled o možnostech si můžeme udělat například z článku A Tracing Primer - Part I [Mike Rousos].
    • .NET sám nemá mechanizmus, jak výjimky zapisovat do trace
    • napsat pár řádků kódu, které zajistí zápis výjimek do trace je snadné!!!
    • do trace můžeme buď výjimky posílat explicitně (níže volání ExceptionTracer.TraceException(myException)),
    • nebo se můžeme přihlásit k odběru událostí, které neošetřené výjimky způsobují,
      • obecně jde o událost AppDomain.CurrentDomain.UnhandledException
      • ve WinForm aplikacích pak událost Application.ThreadException

    Interface pro nasazení?  ...prostý

    V nejjednodušší variantě si můžeme udělat například třídu ExceptionTracer, kterou přihlásíme k odběru příslušných událostí a v obsluze událostí už budeme jen výjimku posílat do trace.

    Pro konzolové aplikace:

    class Program
    {
        static void Main(string[] args)
        {
            ExceptionTracer.SubscribeToUnhandledExceptions();

            throw new InvalidOperationException("Chybka!");

            // ExceptionTracer.Default.TraceException(new ArgumentNullException("param", "Sakrapísek!"));
        }
    }
    Pro WinFormAplikace:

    static class Program
    {
        [STAThread]
        static void Main()
        {
            ExceptionTracer.SubscribeToWindowsFormsThreadExceptions();

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

    App.config pak může vypadat nějak takto:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <system.diagnostics>
            <sources>
                <source name="Exceptions" switchValue="Error">
                    <listeners>
                        <add name="LogFileListener"
                            type="System.Diagnostics.TextWriterTraceListener"
                             initializeData="Exceptions.log"
                        />
                        <add name="XmlListener"
                             initializeData="Exceptions.xml"
                             type="System.Diagnostics.XmlWriterTraceListener"
                        />
                    </listeners>
                </source>
            </sources>
        </system.diagnostics>
    </configuration>

    ExceptionTracer [Simplified Version]

    Jednoduchá, ale plně funkční verze ExceptionTracekru může vypadat třeba takto:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Diagnostics;
    using System.Windows.Forms;

    namespace Havit.Diagnostics
    {
        public class ExceptionTracer
        {
            /// <summary>
            /// Přihlásí ExceptionTracer k odběru všech neobsloužených výjimek (event AppDomain.CurrentDomain.UnhandledException).
            /// </summary>
            public static void SubscribeToUnhandledExceptions()
            {
                AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            }
            private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
            {
                if (e.ExceptionObject is Exception)
                {
                    TraceException((Exception)e.ExceptionObject);
                }
            }

            /// <summary>
            /// Přihlásí ExceptionTracer k odběru všech neobsloužených výjimek WinForm (event Application.ThreadException).
            /// </summary>
            public static void SubscribeToWindowsFormsThreadExceptions()
            {
                Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
            }
            private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
            {
                TraceException(e.Exception);

                // původní implementace obsluhy výjimky, WinForm dialog s chybou
                using (ThreadExceptionDialog excptDlg = new ThreadExceptionDialog(e.Exception))
                {
                    DialogResult result = excptDlg.ShowDialog();
                    if (result == DialogResult.Abort)
                    {
                        Application.Exit();
                    }
                }
            }

            /// <summary>
            /// Pošle do trace zadanou výjimku.
            /// </summary>
            /// <param name="exception">výjimka k zaznamenání</param>
            public static void TraceException(Exception exception)
            {
                TraceSource ts = new TraceSource("Exceptions");

                ts.TraceEvent(TraceEventType.Critical, 0, exception.ToString());

                ts.Flush();
                ts.Close();
            }
        }
    }

    Příklad výstupu [XML]

    <E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
    <System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
      <EventID>0</EventID>
      <Type>3</Type>
      <SubType Name="Critical">0</SubType>
      <Level>1</Level>
      <TimeCreated SystemTime="2008-01-31T21:08:35.0625000Z" />
      <Source Name="Exceptions" />
      <Correlation ActivityID="{00000000-0000-0000-0000-000000000000}" />
      <Execution ProcessName="WindowsFormsApplication1" ProcessID="1232" ThreadID="1" />
      <Channel />
      <Computer>OSKAR</Computer>
    </System>
    <ApplicationData>System.ApplicationException: Test !!! at WindowsFormsApplication1.Form1.button1_Click(Object sender, EventArgs e) in D:\Development\ExceptionLogging\WindowsFormsApplication1\Form1.cs:line 20 at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnClick(EventArgs e) at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent) at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ButtonBase.WndProc(Message& m) at System.Windows.Forms.Button.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)</ApplicationData>
    </E2ETraceEvent>

     

  • Použití C# 3.0 pro runtime .NET Framework 2.0

    Pokud vyvíjíte aplikaci ve Visual Studiu 2008 a aplikace je určena pro runtime .NET Framework 2.0, nic vám nebrání použít nové charakteristiky jazyka C# 3.0. Úžasné je, že pro to nemusíte udělat vůbec nic.

    Není možné pochopitelně použít ty rysy jazyka, které vyžadují knihovny instalované .NET Frameworkem 3.5. Můžete využít například eleganci lambda výrazů nebo zkráceného zápisu vlastností, zapomeňte ale na LINQ.
  • Načítání uživatelů z Active Directory

    Jen krátký kousek kódu, který možná někdy ušetří pár minut nad dokumentací:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.DirectoryServices;

    namespace ActiveDirectorySynchronizer
    {
        class Program
        {
            // doména test.branik.havit.cz, Organizational Unit: My Organizational Unit, pod tím OU: SubUnit
            public static string rootPath = "LDAP://OU=SubUnit,OU=My Organizational Unit,DC=test,DC=branik,DC=havit,DC=cz";
            public static string usernameOptional = @"test.branik.havit.cz\Administrator";
            public static string passwordOptional = "P@ssw0rd";

            static void Main(string[] args)
            {
                // node, od kterého se mají uživatelé hledat
                DirectoryEntry searchRoot = new DirectoryEntry(rootPath);
                if (!String.IsNullOrEmpty(usernameOptional))
                {
                    searchRoot.Username = usernameOptional;
                    searchRoot.Password = passwordOptional;
                }

                // vyhledání všech uživatelů
                DirectorySearcher ds = new DirectorySearcher(searchRoot, "(objectClass=user)");
                ds.PropertiesToLoad.Add("displayName");
                ds.PropertiesToLoad.Add("sAMAccountName");
                ds.PropertiesToLoad.Add("objectGUID");

                foreach (SearchResult sr in ds.FindAll())
                {
                    Guid userGuid = new Guid((byte[])sr.Properties["objectGUID"][0]);
                    string userDisplayName = String.Empty;

                    Console.Write(userGuid);

                    if (sr.Properties["displayName"].Count > 0)
                    {
                        userDisplayName = sr.Properties["displayName"][0].ToString();
                        Console.Write(",");
                        Console.Write(userDisplayName);
                    }


                    Console.WriteLine();
                }

                Console.WriteLine("DONE");
            }
        }
    }

  • Programátorská hádanka - catch & throw

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

     

  • Programátorská hádanka - Výjimka ve výjimkách

    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!


  • Programátorská hádanka - neprobádaná zákoutí C#

    Víte co znamená @ v následujícím bloku kódu?

    ICollection @is = dataSource as ICollection;
    if (@is != null)
    {
         this.Items.Capacity = @is.Count + this.Items.Count;
    }
    Odpověď najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):

    >>>  Zavináč je možné použít na začátku identifikátoru, pokud chceme, aby identifikátorem mohlo být i klíčové slovo (is, null, for, ...)  <<<

    ...a teď se přiznejte, kdo jste to znal?

  • Vlastní textová reprezentace výčtového typu enum - alternativa ToString()

    K výčtovému typu enum nelze konvenčními metodami udělat vlastní textovou reprezentaci, není jak overridovat metodu ToString(). Pokud chceme každé hodnotě přiřadit pouze jedinou "user-friendly" textovou reprezentaci, můžeme využít atributu DescriptionAttribute:

    public enum StavZakazky
    {
        [Description("Nedefinován")]
        Nedefinovan,

        [Description("Vytištěno")]
        TiskHotovo
    }

    public static class EnumExt
    {
        public static string GetDescription(Type enumType, object hodnota)
        {
            string strRet = "<na>";
            try
            {
                System.Reflection.FieldInfo objInfo = enumType.GetField(Enum.GetName(enumType, hodnota));

                System.ComponentModel.DescriptionAttribute objDescription =
                    (System.ComponentModel.DescriptionAttribute)objInfo.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), true)[0];

                strRet = objDescription.Description;
            }
            catch(Exception)
            {
                // chybí description
            }
            return strRet;
        }
    }
    Interní: Implementováno jako Havit.EnumExt.GetDescription().

  • GUI pro Sandcastle - Microsoftí generátor dokumentace ála nDoc

    Microsoftí projekt Sandcastle se začíná pomalu usazovat, nicméně jeho klíčovým nedostatkem zatím byla absence GUI, grafického uživatelského rozhraní. Naštěstí to někteří vzali do svých rukou, a tak už dnes existuje několik GUI pomůcek pro ovládání Sandcastle a pohodlné generování MSDN-style .NET 2.0 dokumentace.

    Vše zajímavé kolem Sandcastle se momentálně děje na webu http://www.sandcastledocs.com/.

    Visual Studio 2005 SDK je ke stažení z Microsoft Downloads, je v něm HtmlHelp 2.0.

  • System.Transactions - dobrý sluha, ale špatný pán

    .NET Framework 2.0 zavádí nový namespace System.Transactions, který umožňuje velmi programátorsky pohodlnou práci s transakcemi, a to jako transakcemi ADO.NET/SQL Serveru, tak i MSMQ (Message Queues) a MSDTC (Distributed Transaction Coordinator).

    Můžeme tak například celkem transparentně obalit kus kódu transakcí, aniž bychom museli do kódu zasahovat a transakce explicitně nastavovat. 

    using (TransactionScope scope = new TransactionScope())
    {
       using (SqlConnection connection = new SqlConnection(connectionString))
       {
          SqlCommand command = connection.CreateCommand();
          command.CommandText = "Insert....";
          command.Connection = connection;

          SqlCommand command2 = connection.CreateCommand();
          command2.CommandText = "Update....";
          command2.Connection = connection;

          connection.Open();
          command.ExecuteNonQuery();
          command2.ExecuteNonQuery();
          connection.Close();
       }
       scope.Complete();
    }
    ...vše vypadá krásně a opravdu to může i krásně fungovat, můžeme si ale i pěkně naběhnout.

    V první řadě, pokud výše uvedený kód běží vůči SQL2000 serveru, pak se namísto běžné SQL-transakce vytvoří distribuovaná transakce spravovaná MSDTC, Distributed Transaction Coordinatorem - což bude mít velmi nepříjemný dopad na výkon naší aplikace. Při použití s SQL2000 totiž nejsou podporovány tzv. "promotable transactions".

    Pokud používáme SQL2005 server, tento problém odpadá, transakce bude realizována prostřednictvím SqlTransaction.

    Dalším problémem však je, že explicitně neurčujeme, co vše je součástí transakce, takže veškeré transakční zdroje (resources), které v rámci transaction-scope používáme, se automaticky zaregistrují jako součást transakce a snadno tak opět skončíme na distribuované transakci spravované MSDTC.

    Závěr

    Osobně raději pro transakční zpracování SQL používám klasickou SqlTransaction, navíc pokud si vytvoříme malou pomůcku, pak můžeme i SqlTransaction řešit obdobně pohodlným způsobem:

    int myID = 5;
    object result;
     
    SqlDataAccess.ExecuteTransaction(
       delegate(SqlTransaction transaction)
       {
          // uvnitř lze používat i lokální proměnné (samozřejmě i parametry, statické fieldy atp.)
     
          SqlCommand cmd1 = new SqlCommand("command string");
          cmd1.Transaction = transaction;
          cmd1.Connection = transaction.Connection;
          cmd1.Parameters.AddWithValue("@MyID", myID);
          cmd1.ExecuteNonQuery();
     
          SqlCommand cmd2 = new SqlCommand("another command");
          cmd2.Transaction = transaction;
          cmd2.Connection = transaction.Connection;
          result = cmd2.ExecuteScalar();
       });

    Související články

More Posts Next page »