Tag Archives: Tipy a Triky

AjaxControlToolkit HoverMenuExtender – dočítání vyskakovacího obsahu AJAXem

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.

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>

Zjištění volné paměti počítače na webhostingu

Pokud se chceme z naší hostované ASP.NET aplikace dozvědět něco o stroji, na kterém běží, nemůžeme tak zpravidla udělat přes WMI dotazy, či performance-countery, protože nemáme k dispozici účet s dostatečnými právy a dostáváme výjimku Access Denied.

I nám C#-istům je však k dispozici to, co mají ve VisualBasicu ukryto pod featurou My, konkrétně nás zde zajímá My.Computer.Info, které není nic jiného než instancí třídyMicrosoft.VisualBasic.Devices.ComputerInfo s propertama AvailablePhysicalMemory,AvailableVirtualMemory, TotalPhysicalMemory, TotalVirtualMemory a několika dalšími s informacemi o verzi OS atp.

Stačí si tedy do našeho C# projektu nareferencovat assembly Microsoft.VisualBasic, vytvořit si instanci třídy ComputerInfo a zeptat se jí na přísušné hodnoty:

using Microsoft.VisualBasic.Devices;

...

ComputerInfo computerInfo = new ComputerInfo();
InfoLb.Text = computerInfo.AvailablePhysicalMemory.ToString("n0");

Třída ComputerInfo používá volání podpůrných nativních metod .NET a informace o paměti stroje dostaneme i pod běžným hostingovým účtem.

Změna výchozí verze ASP.NET pro nové weby IIS

Pokud Vám IIS pro nově zakládané web-site přednastavuje jinou verzi ASP.NET, než si představujete, můžete toto výchozí nastavení ovlivnit spuštěním příkazu aspnet_regiis.exe od nově požadované verze ASP.NET s níže uvedenými parametry:

aspnet_regiis -sn W3SVC/

Utilitu aspnet_regiis najdete v C:\WINDOWS\Microsoft.NET\Framework\<číslo verze>\.

Firefox velmi pomalý při použití Windows Vista a ASP.NET Development serveru

Načítání webových stránek je pod Firefoxem v kombinaci s Windows Vista a ASP.NET Development serverem velmi pomalé. První podezření padlo na ASP.NET AJAX pod Firefoxem, nicméně pravda se ukázala být jinde.

Za všechno může automatické ladění TCP stacku ve Windows Vista. Na internetu jsem nalezl dvě řešení:

Zákaz automatického ladění TCP stacku…

… lze provést spuštěním „netsh interface tcp set global autotuninglevel=disabled“ z příkazové řádky.

Toto řešení jsem nezkoušel.

Zákaz IPv6 ve Firefoxu

Dle návodu pomůže ve Firefoxu:

  1. přejít na stránku about:config,
  2. vyhledat položku network.dns.disableIPv6,
  3. nastavit hodnotu na true.

Toto řešení bylo jednoduché a účinné.

Jaká je Vaše zkušenost s kombinací Firefoxu, Windows Vista a ASP.NET Development serveru? Nebo jste na stejný problém narazili i při jiné kombinaci produktů?

Lokalizace skinů (Themes)

Nevím jak vás, ale mně už delší dobu rozčiluje, že skiny nejsou lokalizovatelné. Pánové z Microsoftu na to buď zapomněli, nebo nevím, každopádně se tak skiny staly do značné míry nepoužitelnými pro lokalizované aplikace.

Štvalo mě to tak dlouho, až jsem napsal vlastní lokalizaci skinů. Není to nic překrásného, protože jen máloco je v ASP.NET tak zapouzdřeno jako témata a skiny a tak se kód hemží reflexí – nicméně to výborně funguje k tomu účelu, na který to sám potřebuji – do lokalizovaných webových aplikací s běžným provozem bez masové zátěže.

Optimalizace opět ponechám na každém z vás, stejnětak případný příhodnější způsob integrace do ASP.NET. Níže popisuji jednoduché řešení, jehož účelem je pochopení použitého způsobu lokalizace skinů. Taktéž předesílám, že se jedná o explicitní metodu lokalizace, protože implicitní lokalizaci nepovažuji za obecně vhodnou.

Jak lokalizace skinů funguje

První, co každý asi zkusí, je použít ve skinu běžnou expression <%$ Resources: MyResources, MyValueName %>. ASP.NET Vás odmění krásnou chybovou hláčkou parseru: „Expressions are not allowed in skin files.“ Tudy cesta nevede…

Inspiroval jsem se tedy v syntaxi lokalizace, kterou používá XmlSiteMapProvider pro web.sitemap soubory, tedy např.:

&lt;siteMapNode url=&quot;~/default.aspx&quot; title=&quot;$resources:MyResources,MyResourceKey,My default value&quot; /&gt;

a řekl jsem si, že stejnou syntaxi bych mohl použít i pro lokalizaci skinů:

&lt;asp:Button Text=&quot;$resources: MyResource, MyValue, Default Value&quot; runat=&quot;server&quot; /&gt;
&lt;asp:Button SkinID=&quot;SkinWithDefault&quot; Text=&quot;$resources: MyResource, InvalidResource, Default Value&quot; runat=&quot;server&quot; /&gt;

…samozřejmě se tím odepře možnost použití textů začínajících znaky „$resource:“, ale to mi nevadí.

Teď ještě vymyslet, kde a jak v životním cyklu stránky zajistit odchytávání a vyhodnocování těchto lokalizačních výrazů. Po chvilce bádání s Reflectorem lze dospět k následujícím poznatkům:

  1. téma stránky je uložena v private fieldu Page._theme typu PageTheme
  2. stylesheet-téma stránky je uloženo v private fieldu Page._styleSheet typu PageTheme
  3. témata se inicializují (InitializeThemes) po ve fází PreInit životního cyklu stránky, avšak až po události PreInit (v obsluze události PreInit tedy zřejmě není možné témata modifikovat),
  4. témata se aplikují ve fázi Init životního cyklu stránky, avšak před událostí Init (v obsluze události Init je tedy pozdě je modifikovat),
  5. třída PageTheme má metodu ApplyControlSkin(Control control), která najde ve slovníku skinů skin (typ ControlSkin) příslušející danému controlu a aplikuje ho pomocí skin.ApplySkin(Control control)
  6. metoda ControlSkin.ApplySkin(Control control) nedělá nic jiného, než že zavolá vygenerovaného delegáta.
public void ApplySkin(Control control)
{
    this._controlSkinDelegate(control);
}

Zdánlivě nikde není rozumný prostor pro modifikaci skinu, nikde není seznam hodnot skinu, žádná property-value kolekce, kde by se dal resource-odkaz najít a modifikovat. Vygenerovaný delegát prostě natvrdo nastavuje vlastnosti příslušného controlu a hotovo.

…zde však přichází to kouzlo! Můžeme totiž delegáta, který je uložen v private fieldu _controlSkinDelegate vyměnit za delegáta vlastního. A to nejenomže ho můžeme vyměnit, ale můžeme si dokonce odkaz na původního delegáta uchovat a tuto původní metodu z našeho delegáta zavolat. Celý můj fígl tedy spočívá ve výměně tohoto delegáta, za jinou metodu, která však v sobě nejprve volá metodu původní (aplikuje skin) a následně prochází skinovatelné property controlu (pro jednoduchost pouze textové) a hledá v nich lokalizující resource-odkaz, který případně vyhodnotí. Abychom si měli původního delegáta jak uchovat, je nová metoda wrapována do instance třídy SkinLocalizationDelegateWrapper.

Poslední, co zbývá vyřešit, je jak se s vlastní obsluhou dostat do správného místa životního cyklu stránky, tedy za InitializeTheme(), které je po události PreInit a před ApplySkin(), které je na začátku Init fáze, před událostí Init. Nehledal jsem dlouho, a možná najdete vhodnější místo, ale já jsem pro tyto účely znásilnil virtuální metodu Control.ResolveAdapter(), která se volá na začátku InitRecursive(), tedy přesně v místě, kde se nám to hodí.

SkinLocalizationPageBase

Vytvořil jsem tedy nakonec třídu SkinLocalizationPageBase, která je potomkem System.Web.UI.Page, a která by měla být předkem všech stránek webu, kde lokalizované skiny používáme (nejlépe tedy úplně všech, pomocí <pages pageBaseType=“SkinLocalizationPageBase“ /> ve web.configu.

Výsledný kód vypadá takto:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.UI.Adapters;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.Resources;

namespace Havit.Web.UI
{
    /// &lt;summary&gt;
    /// Bázová třída pro stránky, které obsahují controly s lokalizovatelnými skiny.
    /// &lt;/summary&gt;
    public class SkinLocalizationPageBase : Page
    {
        #region ResolveAdapter (override)
        /// &lt;remarks&gt;
        /// Metoda ResolveAdapter je jediné místo, které jsem našel, že se pomocí něj dá vykonávat nějaký kód
        /// mezi InitializeTheme() (po OnPreInit) a ApplyControlSkin (před OnInit).
        /// &lt;/remarks&gt;
        protected override ControlAdapter ResolveAdapter()
        {
// Theme
            FieldInfo themeFieldInfo = typeof(Page).GetField(&quot;_theme&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            PageTheme theme = themeFieldInfo.GetValue(this) as PageTheme;

            if (theme != null)
            {
                PropertyInfo controlSkinsPropertyInfo = typeof(PageTheme).GetProperty(&quot;ControlSkins&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                IDictionary controlSkins = controlSkinsPropertyInfo.GetValue(theme, null) as IDictionary;

                foreach (DictionaryEntry entry in controlSkins)
                {
                    ControlSkin controlSkin = entry.Value as ControlSkin;

                    if (controlSkin != null)
                    {
                        FieldInfo controlSkinDelegateFieldInfo = typeof(ControlSkin).GetField(&quot;_controlSkinDelegate&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                        ControlSkinDelegate controlSkinDelegate = controlSkinDelegateFieldInfo.GetValue(controlSkin) as ControlSkinDelegate;

                        SkinLocalizationDelegateWrapper sldw = new SkinLocalizationDelegateWrapper(controlSkinDelegate);

                        controlSkinDelegateFieldInfo.SetValue(controlSkin, new ControlSkinDelegate(sldw.DoWork));
                    }

                }
            }

            // StylesheetTheme
            FieldInfo styleSheetFieldInfo = typeof(Page).GetField(&quot;_styleSheet&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            PageTheme styleSheet = styleSheetFieldInfo.GetValue(this) as PageTheme;

            if (styleSheet != null)
            {
                PropertyInfo controlSkinsPropertyInfo = typeof(PageTheme).GetProperty(&quot;ControlSkins&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                IDictionary controlSkins = controlSkinsPropertyInfo.GetValue(styleSheet, null) as IDictionary;

                foreach (DictionaryEntry entry in controlSkins)
                {
                    ControlSkin controlSkin = entry.Value as ControlSkin;

                    if (controlSkin != null)
                    {
                        FieldInfo controlSkinDelegateFieldInfo = typeof(ControlSkin).GetField(&quot;_controlSkinDelegate&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                        ControlSkinDelegate controlSkinDelegate = controlSkinDelegateFieldInfo.GetValue(controlSkin) as ControlSkinDelegate;

                        if (!(controlSkinDelegate.Target is SkinLocalizationDelegateWrapper))
                        {

                            SkinLocalizationDelegateWrapper sldw = new SkinLocalizationDelegateWrapper(controlSkinDelegate);

                            controlSkinDelegateFieldInfo.SetValue(controlSkin, new ControlSkinDelegate(sldw.DoWork));
                        }
                    }

                }
            }

            return base.ResolveAdapter();
        }
        #endregion

        #region SkinLocalizationDelegateWrapper
        /// &lt;summary&gt;
        /// SkinLocalizationDelegateWrapper obalí původní výkonný kód skinování a zajistí vyhodnocení resource-odkazů.
        /// &lt;/summary&gt;
        class SkinLocalizationDelegateWrapper
        {
            private ControlSkinDelegate _oldControlSkinDelegate;

            public SkinLocalizationDelegateWrapper(ControlSkinDelegate oldControlSkinDelegate)
            {
                this._oldControlSkinDelegate = oldControlSkinDelegate;
            }

            public Control DoWork(Control control)
            {
                Control result = null;

                if (this._oldControlSkinDelegate != null)
                {
                    result = this._oldControlSkinDelegate(control);
                }

                PropertyInfo[] properties = result.GetType().GetProperties();
                foreach (PropertyInfo property in properties)
                {
                    if (property.PropertyType == typeof(String))
                    {
                        // zajímají nás jen property typu string
                        ThemeableAttribute themableAttribute = Attribute.GetCustomAttribute(property, typeof(ThemeableAttribute)) as ThemeableAttribute;
                        if ((themableAttribute == null) || themableAttribute.Themeable)
                        {
                            // které nemají atribut themable vůbec, nebo ho nemají s volbou false (výchozí je true)
                            string propertyValue = property.GetValue(result, null) as string;
                            if (((propertyValue != null) &amp;&amp; (propertyValue.Length &gt; 10)) &amp;&amp; propertyValue.ToLower(CultureInfo.InvariantCulture).StartsWith(&quot;$resources:&quot;, StringComparison.Ordinal))
                            {
                                string resourceOdkaz = propertyValue.Substring(11);
                                if (resourceOdkaz.Length == 0)
                                {
                                    throw new ConfigurationErrorsException(&quot;Resource odkaz skinu nesmí být prázdný.&quot;);
                                }
                                string resourceClassKey = null;
                                string resourceKey = null;
                                int length = resourceOdkaz.IndexOf(',');
                                if (length == -1)
                                {
                                    throw new ConfigurationErrorsException(&quot;Resource odkaz skinu není platný&quot;);
                                }
                                resourceClassKey = resourceOdkaz.Substring(0, length);
                                resourceKey = resourceOdkaz.Substring(length + 1);
                                string defaultPropertyValue = null;
                                int index = resourceKey.IndexOf(',');
                                if (index != -1)
                                {
                                    defaultPropertyValue = resourceKey.Substring(index + 1); // default value
                                    resourceKey = resourceKey.Substring(0, index);
                                }
                                else
                                {
                                    propertyValue = null;
                                }

                                try
                                {
                                    propertyValue = (string)HttpContext.GetGlobalResourceObject(resourceClassKey.Trim(), resourceKey.Trim());
                                }
                                catch (MissingManifestResourceException)
                                {
                                    // NOOP
                                }

                                if (propertyValue == null)
                                {
                                    propertyValue = defaultPropertyValue;
                                }

                                property.SetValue(result, propertyValue, null);
                            }
                        }
                    }
                }

                return result;
            }
        }
        #endregion
    }
}

Úplné demo v podobě Web Site je v připojeném ZIP souboru.

Související články

Zobrazování podrobností výjimek a chyb

Jestli Vás taky rozčilují chybové hlášky SharePointu ve stylu „Unexpected error has occured.“ nebo „File not found.“ a marně hledáte detaily těchto chyb/výjimek, pak vězte, že stačí ve web.configu nastavit v elementu SharePoint/SafeMode atribut CallStack na true:

<SharePoint>
    <SafeMode MaxControls="200" CallStack="true" ...

Samozřejmě k tomu ještě <customErrors mode=“Off“ /> nebo mode=“RemoteOnly“:

<customErrors mode="Off" />

…a je to.

Samozřejmě na produkční mašině bychom měli mít CallStack=“false“.

$MyExpression: Argument, aneb jak na custom expressions (ExpressionBuilder)

Jistě jste si v ASP.NET 2.0 všimli nové vlastnosti, tedy používání expressions v markup-kódu. Vestavěny jsou tyto:

<%$ ConnectionStrings: Name %>
<%$ Resources: Glossary, Key %>
<%$ AppSettings: Key %>

…a my si ukážeme, jak lze jednoduše přidávat vlastní (uvádím pouze primitivní příklad, který má ukázat, že to jde, a kudy na to).

Vše je to v podstatě o tom, že vytvoříme třídu odvozenou od abstraktní báze ExpressionBuilder a implementujeme metodu GetCodeExpression(…) nebo metodu EvaluateExpression(…). Metda EvaluateExpression() vrací vyhodnocený výraz, zatímco metoda GetCodeExpression() vrací kód, který se má použít v přiřazení do property při kompilaci ASPX stránky.

Abychom vytvořený expression-builder použili v našem web, musíme ho ještě přihlásit ve web.configu.

Jak by tedy mohla taková nejjednoduší třída pro expression vypadat:

namespace MyNamespace
{
   [ExpressionPrefix( "MyExpression" )]
   public class MyExpressionBuilder : System.Web.Compilation.ExpressionBuilder
   {
      public override CodeExpression GetCodeExpression(
         BoundPropertyEntry entry,
         object parsedData,
         ExpressionBuilderContext context)
     {
         return new CodeSnippetExpression(entry.Expression);
     }
   }
}

ve web.config pak zavedeme takto:

<compilation debug="true">
      <expressionBuilders>
          <add expressionPrefix="MyExpression" type="Namespace.MyExpressionBuilder, MyAssembly"/>
      </expressionBuilders>
  </compilation>

a v kódu pak použijeme třeba takto:

<asp:Literal Text="<%$ MyExpression: MyClass.MyConst %>" runat="server" /> 
<asp:TextBox ID="PasswordTB" MaxLength="<%$ MyExpression: Uzivatel.Properties.Password.MaximumLength %>" runat="server" />
<asp:RegularExpressionValidator ValidationExpression=<%$ MyExpression: MyRegexPatterns.EmailStrict %>" ... />

Důležité je zdůrznit, že MyExpression v této podobě nedělá nic jiného, než že ASP.NET-compileru dosazuje do míst, kde máme MyExpression použit, příšlušný kód. Můžeme tedy postavit naprosto libovolný syntakticky korektní kus kódu, který je compileru předhozen za přiřazovací rovnítko obdobně jako by se jednalo o makro známé z jiných programovacích platforem, např.:

@__ctrl.MaxLength = ((int)(Uzivatel.Properties.Password.MaximumLength));

form defaultfocus=“..“ defaultbutton=“…“ runat=“server“ při použití MasterPage

Občas jsme donuceni umístit control <form runat=“server“> už do MasterPage, například proto, že máme v MasterPage navigační TreeView či jiný control vyžadující <form>. Pak nastává otázka, jak ve vlastní content-page nastavit formuláři vlastnosti DefaultButton a DefaultFocus na tam umístěné controly.

<%@ Master ... %>
...
<body>
    <form id="MainForm" runat="server">
        ...
        <asp:ContentPlaceHolder ID="BodyCPH" runat="server" />
        ...
    </form>
</body>
...

Content-page s controly:

<%@ Page MasterPageFile="..." ... %>
<asp:Content ContentPlaceHolderID="BodyCPH" runat="server">
    <asp:TextBox ID="UsernameTB" runat="server" />
    <asp:LinkButton ID="LoginLB" runat="server" />
</asp:Content>

Mnohé správně napadne, že Page má property Form, přes kterou to jistě půjde nastavit. Ano, nicméně properties DefaultFocus a DefaultButton jsou typu string a pokud zkusíme například

Page.Form.DefaultButton = "LoginLB";  // špatně !!!
Page.Form.DefaultButton = LoginLB.ID;  // špatně !!!

…pak budeme odměněni výjimkou InvalidOperationException: The DefaultButton of ‚MainForm‘ must be the ID of a control of type IButtonControl.

Správně je v případě DefaultButton potřeba předat UniqueID a v případě DefaultFocus ClientIDdaného controlu, protože je potřeba ho identifikovat včetně NamingContaineru (form a button jsou každý v jiném naming containeru)

Page.Form.DefaultButton = LoginLB.UniqueID;
Page.Form.DefaultFocus = UsernameTB.ClientID;

Stejným způsobem bychom postupovali nejenom v případě MasterPage, ale například i v případě, kdy bychom chtěli vlastnosti DefaultButton či DefaultFocus ovlivnit z nějakého controlu (což bych spíše nedoporučoval).