Author Archives: Robert Haken

avatar Neznámé

About Robert Haken

Software Architect, Founder at HAVIT, Microsoft MVP - ASP.NET/IIS

Validátory považují white-space za empty hodnotu

Leckoho možná překvapí chování validátorů (RegularExpressionValidatoru, CompareValidatoru a dalších) v jednoduchém případě:

<asp:TextBox ID="MyTB" runat="server" />
<asp:RegularExpressionValidator ValidationExpression="\d+" ControlToValidate="MyTB" Text="x" runat="server" />
<asp:Button Text="OK" runat="server" />

Co se děje:

  • hodnota „123“ validátor aktivuje, vstup je validní
  • hodnoty „123 “ nebo “ 123″ validátor aktivují, vstup není validní
  • hodnotu “ “ (white-space) validátor propustí, vstup je podle něj validní
    • nepomáhá ani ValidationExpression=“^\d+$“, validátor se prostě neaktivuje
  • hodnota „“ (String.Empty) validátor neaktivuje, validátory nevalidují Empty vstupy
    • white-space samotný je považován za Empty a validátory se tak vyjma RequiredFieldValidatoru ignorují (a CustomValidatoru, pokud má nastaveno ValidateEmptyText=“true“)

Konvence, zásady a postupy z vývojářské praxe – Slides, materiály a záznam [WUG Praha 07/2011]

Slides a materiály z přednášky pro WUG Praha z července 2011:

Záznam z přednášky je k dispozici na našem YouTube Channelu:

Health Monitoring a sledování chyb v ASP.NET webových službách (.asmx)

Jak jsem již popisoval v předchozím články, nevýhodou jinak šikovného mechanizmu Health Monitoringu je, že nesbírá chyby z aynchronních requestů a z webových služeb ASP.NET. Jak se vypořádat s prvním problémem u AJAX requestů bylo již naznačeno, podívejme se teď na problematiku webových služeb ASP.NET (.asmx). V zásadě jde opět o zachycení problémové výjimky a předání WebRequestErrorEvent.

SoapExceptionHealthMonitoringHandler (SoapExtension)

Elegantní metodou jak se dostat k odběru výjimek během zpracování webových služeb a jak je předávat mechanizmu Health Monitoringu je požití SoapExtension. Uvádím opět kód kolegy Jiřího Kandy (pomocná třída WebRequestErrorEventExt byla již definována v předchozím článku):

/// <summary>
/// V případě chyby ve zpracování web metody (webové služby) zajistí oznámení chyby health monitoringem.
/// Pozor, toto nefunguje (a chyby healthmonitoringu tak nejsou oznamovány),
/// pokud se webové služby testují v browseru!!! Pro testování nutno použít skutečného klienta webové služby (třeba service reference v konzolovce).
/// </summary>
public class SoapExceptionHealthMonitoringHandler : System.Web.Services.Protocols.SoapExtension
{
    #region ProcessMessage
    public override void ProcessMessage(System.Web.Services.Protocols.SoapMessage message)
    {
        try
        {
            if ((message != null) && (message.Stage == SoapMessageStage.AfterSerialize))
            {
                if (message.Exception != null)
                {
                    Exception exception = message.Exception;
                    if ((exception is SoapException) && (exception.InnerException != null))
                    {
                        exception = exception.InnerException;
                    }
                    if ((exception is HttpUnhandledException) && (exception.InnerException != null))
                    {
                        exception = exception.InnerException;
                    }
                    new WebRequestErrorEventExt(exception.Message, message, exception).Raise();
                }
            }
        }
        catch // pokud by zde nedejbože došlo k nějaké další výjimce, tak ji zamaskujeme
        {
            // NOOP
        }
    }
    #endregion

    #region GetInitializer
    public override object GetInitializer(Type serviceType)
    {
        return null;
    }

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }
    #endregion

    #region Initialize
    public override void Initialize(object initializer)
    {
    }
    #endregion
}

…a zapojení ve web.configu potom v elementu configuration/system.web/webServices:

<webServices>
    <soapExtensionTypes>
        <add type="MyNamespace.SoapExceptionHealthMonitoringHandler, MyAssembly" priority="0" group="0" />
    </soapExtensionTypes>
</webServices>

A JE TO.

Health Monitoring a sledování chyb asynchronních postbacků (AJAX)

Health Monitoring je šikovný vestavěný mechanizmus ASP.NET pro sledování a hlášení problémových situací. Typicky je používán pro zápis výjimek aplikace do event-logu nebo jejich posílání mailem.

Slabinou Health Monitoringu je, že se neumí vypořádat s chybami vzniklými během asynchronních postbacků (AJAX) ani s chybami vzniklými v rámci webových služeb ASP.NET. Přesněji řečeno ASP.NET na tyto chyby nějak nepamatuje a neoznamuje je do web-events mechanizmu, na němž je Health Monitoring závislý.

Podívejme se nyní na cestu, jak doplnit do Health Monitoringu sledování chyb AJAXu, sledování chyb webových služeb ponechávám do samostatného článku. Jak již bylo naznačeno, jde o to, že potřebujeme doplnit chybějící oznamování chyb do web.events mechanizmu. Chyba vzniklá během asynchronního postbacku způsobí vyvolání události ScriptManager.AsyncPostBackError. Potřebujeme se tedy napojit na tuto událost s vlastní obsluhou a v ní chybu předat jako WebRequestErrorEvent.

AjaxHealthMonitoring control

Jedním z možných opakovaně použitelných elegantních řešení je vytvoření vlastního controlu, který se bude umisťovat do stránek stejně jako samotný ScriptManager, např. tedy v MasterPage. Uvádím zde kód kolegy Jirky Kandy, který se s tím vypořádal takto:

public class AjaxHealthMonitoring: Control
{
    #region OnInit
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);

        ScriptManager scriptManager = ScriptManager.GetCurrent(Page);
        if (scriptManager == null)
        {
            throw new InvalidOperationException("Ve stránce nebyl nalezen ScriptManager, který je controlem AjaxHealthMonitoring vyžadován.");
        }
        scriptManager.AsyncPostBackError += new EventHandler<AsyncPostBackErrorEventArgs>(ScriptManager_AsyncPostBackError);
    }
    #endregion

    #region ScriptManager_AsyncPostBackError
    /// <summary>
    /// Obsluha události AsyncPostBackError ScriptManageru. Zajistí vyvolání události health monitoringu.
    /// </summary>
    private void ScriptManager_AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e)
    {
        if (e.Exception != null)
        {
            new WebRequestErrorEventExt(e.Exception.Message, this, e.Exception).Raise();
        }
    }
    #endregion
}

Pro úplnost uvádím odvozenou podobou třídy WebRequestErrorEventExt:

public class WebRequestErrorEventExt : WebRequestErrorEvent
{
    public WebRequestErrorEventExt(string message, object eventSource, Exception exception)
        : base(message, eventSource, WebEventCodes.WebExtendedBase + 999, exception)
    {
    }
}

Control se pak používá ve stránce stejně jako ScriptManager (do výstupního HTML kódu nic nerenderuje):

<asp:ScriptManager ScriptMode="Release" AllowCustomErrorsRedirect="true" runat="server" />
<havit:AjaxHealthMonitoring runat="server" />

ResourceProvider – resources z databáze nebo odjinud

ASP.NET ve výchozí konfiguraci bere při lokalizaci resources z .resx souborů umístěch ve Vaší webové aplikaci. Globální resources ze složky ~/App_GlobalResources/, lokální resources z podsložek ./App_LocalResources/ u jednotlivých stránek/controlů. Na resources pak lze přistupovat použitím syntaxe <%$ Resources: … %> z markup kódu, popř. metodami GetGlobalResourceObject(), resp. GetLocalResourceObject(). Ke globálním resources pak ASP.NET ještě generuje do namespace Resources třídy pro jednotlivé .resx soubory s properties odpovídajících jednotlivým klíčům (záznamům v .resx).

Pokud namísto .resx souborů chceme resources brát z jiného datového zdroje, např. databáze, je to jednoduché. ASP.NET pro tuto situaci používá provider-model a celá akce se v zásadě odehrává v následujících třech až čtyřech krocích:

  1. vytvoření vlastního resource-providera, tj. třídy implementující rozhraní IResourceProvider a zejména jeho metodu GetObject(),
  2. vytvoření ResourceProviderFactory třídy, na kterou se deleguje rozhodování ASP.NET o tom, kterého resource-providera má pro jednotlivé situace použít, v našem případě našeho resource-providera z bodu 1,
  3. nasměrování konfigurace ASP.NET na používání připravené ResourceProviderFactory třídy (úprava web.configu),
  4. pokud chceme zachovat, nebo alespoň simulovat generovaný namespace Resources, musíme ho buď také generovat (nepravděpodobné), nebo si od NET4 výše můžeme krásně vypomoci dynamickým objektem.

Pojďme se podívat na jednotlivé kroky podrobněji:

1. Vytvoření vlastního resource-providera

Resource-provider, je odpovědný za vlastní implementaci vyhodnocování resource-výrazu, tj. v něm ztvárním náš kód, který bude hodnoty resources číst z databáze, XML, nebo jiného zdroje, dle našeho uvážení.

Resource-provider je třída, která implementuje rozhraní IResourceProvider. To má zejména metodu object GetObject(string resourceKey, CultureInfo culture). Vstupem zde není resourceClass, který jak později uvidíme naopak dostává již ResourceProviderFactory a pro každou resourceClass je tak typicky vytvářena samostatná instance resource-providera odbavující danou resourceClass. IResourceProvider předepisuje ještě implementaci property IResourceReader ResourceReader, nicméně v běžných scénářích se bez implementaci readeru obejdete.

Primitivní implementace resource-providera pak může vypadat například takto:

namespace MyResourceProviders
{
    /// <summary>
    /// Resource-provider pro lokální i globální resources z DB.
    /// </summary>
    public class MyResourceProvider: IResourceProvider
    {
        private string _classKey;

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the  class.
        /// </summary>
        /// identifikátor resources třídy (cesta a název souboru pro lokální, název třídy pro globální)
        public XeroxWebToolResourceProvider(string classKey)
        {
            _classKey = classKey;
        }
        #endregion

        #region GetObject
        /// <summary>
        /// Returns a resource object for the key and culture.
        /// </summary>
        /// The key identifying a particular resource.
        /// The culture identifying a localized value for the resource.
        /// 
        /// An  that contains the resource value for the  and .
        /// 
        public object GetObject(string resourceKey, CultureInfo culture)
        {
            Debug.Assert(!String.IsNullOrEmpty(resourceKey)); // contract zajišťuje již interface

            if (culture == null)
            {
                culture = CultureInfo.CurrentUICulture;
            }

            return ResourceHelper.GetString(_classKey, resourceKey, culture);
        }
        #endregion

        #region ResourceReader
        /// <summary>
        /// Gets an object to read resource values from a source.
        /// </summary>
        /// 
        /// 
        /// The  associated with the current resource provider.
        /// 
        public IResourceReader ResourceReader
        {
            get
            {
                throw new NotImplementedException();
            }
        }
        #endregion
    }
}

mplementace třídy ResourceHelper, resp. její metody GetString(resourceClass, resourceKey, culture) již je samozřejmě na Vaší fantazii. Nemusíte samozřejmě tuto funkčnost ani vytahovat do samostatné třídy, ale můžete ji implementovat in-line.

3. Vytvoření ResourceProviderFactory

Abychom našeho připraveného resource-providera dostali do hry, musíme připravit ještě jednoduchou třídu odvozenou od předka ResourceProviderFactory, která jak už název napovídá bude odpovědná za volbu a vytváření instancí příslušných tříd resource-providerů. Teoreticky můžeme mít totiž pro různé množiny resources mít úplně jiné resource-providery, část nechat v .resx souborech, část mít v DB, atp. Rozhodujícím faktorem pro volbu resource-providera je zde informace o tom, jestli se jedná o globální nebo lokální resources a dále v případě globálních resourceClass a v případě lokálních virtualPath odkazujícího prvku.

ResourceProviderFacotry má dvě abstraktní metody, které musíme implementovat – CreateGlobalResourceProvider() a CreateLocalResourceProvider(). Primitivní implementace odkazující se na našeho resource-providera z předchozího kroku může vypadat třeba takto:

namespace Havit.XeroxWebTool.WebBase.ResourceProviders
{
    /// <summary>
    /// Resource-factory vracející InformResourceProvider pro lokální i globální resources.
    /// </summary>
    public class MyResourceProviderFactory : ResourceProviderFactory
    {
        #region CreateGlobalResourceProvider
        /// <summary>
        /// When overridden in a derived class, creates a global resource provider.
        /// </summary>
        /// The name of the resource class.
        /// 
        /// An .
        /// 
        public override IResourceProvider CreateGlobalResourceProvider(string classKey)
        {
            return new MyResourceProvider(classKey);
        }
        #endregion

        #region CreateLocalResourceProvider
        /// <summary>
        /// When overridden in a derived class, creates a local resource provider.
        /// </summary>
        /// The path to a resource file.
        /// 
        /// An .
        /// 
        public override IResourceProvider CreateLocalResourceProvider(string virtualPath)
        {
            string classKey = virtualPath;
            if (!string.IsNullOrEmpty(virtualPath))
            {
                classKey = virtualPath.Remove(0, 1); // odstranění počátečního lomítka
            }
            return new MyResourceProvider(classKey);

        }
        #endregion
    }
}

3. Konfigurace použití ResourceProviderFactory (web.config)

Posledním nutným krokem ke zprovoznění vlastní logiky resources je sdělit ASP.NET prostřednictvím web.configu, že si přejem používat naší ResourceProviderFactory. Děje se tak prostřednictvím elementu globalization, atributu resourceProviderFatoryType:


Element globalization patří do configuration/system.web/globalization, pokud je Vaše aplikace lokalizována, již ho tam pravděpodobně budete mít.

4. Náhrada za namespace Resources (volitelně)

Pokud přesouváte resources projektu do DB dodatečně, je možné, že v kódu již na mnoha místech používáte strong-type odkaz na prvky namespace Resources, kterou ASP.NET generuje ke globálním resources. V takovém případě se budete potřebovat s tímto kódem nějak vypořádat, protože nebudete-li používat .resx soubory, namespace Resources se Vám nebude generovat.

Jednou z možných cest, jak toto vyřešit, je použití dynamického objektu NET4.

V zásadě jde o to, že místo namespace Resources vytvoříte třídu odvozenou od předka DynamicObject a v příslušných předcích (PageBase, UserControlBase) vytvoříte dynamic property Resources. Ve zkratce může implementace třídy vypadat takto:

namespace MyResources
{
    /// <summary>
    /// Pomocná třída pro přístup k Resources z kódu.
    /// </summary>
    public class Resources: DynamicObject
    {
        #region Constructors
        public Resources()
            : base()
        {
        }
        private Resources(string key):base()
        {
            this.ResourceClass = key;
        }
        #endregion

        #region ResourceClass
        /// <summary>
        /// ResourceClass pro načítaný resource
        /// </summary>
        private string ResourceClass { get; set; }
        #endregion

        #region TryGetMember (override)
        /// <summary>
        /// Vrátí požadovanou property
        /// </summary>
        /// 
        /// 
        /// 
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (this.ResourceClass == null)
            {
                result = new Resources(binder.Name);
            }
            else
            {
                result = ResourceHelper.GetString(this.ResourceClass, binder.Name, Thread.CurrentThread.CurrentUICulture);
            }
            return true;
        }
        #endregion
    }
}

Ve třídě PageBase/UserControlBase potom zadefinujete property takto:

protected readonly dynamic Resources = new Resources();

(Předka všech stránek/usercontrolů, pokud to zatím nepoužíváte, dostanete do hry z web.configu pomocí atributů pageBaseType a userControlBaseType na elementu configuration/system.web/pages. Budete se na něj však muset odkazovat i z code-behind tříd.)

Viz též

  • Extending the ASP.NET 2.0 Resource-Provider Model [MSDN, Michèle Leroux Bustamante]
  • MSSQL: Neměňte „Maximum server memory (in MB)“ na 0

    Dneska jsem se nechal unést a chtěl jsem vypnout na MS SQL 2008 R2 horní mez využité paměti, kterou jsme měli na serveru staženou na 2.5 GB.

    Nu což, vlezl jsem do SQL Management Studia, dal jsem si vlastnosti serveru, koukám do nastavení Memory a změnil jsem nastavení vlastnosti „Maximum server memory (in MB)“ z 2500 na 0 (velmi naivně jsem předpokládal, že stejně jako jinde to bude interpretováno jako „no limit“).

    Omyl, to nezkoušejte. Poslední, co jsem ještě záhledl bylo, že si to SQL sám změnil na 16 (MB) a to bylo na dlouho všechno, co jsem z SQL serveru viděl.

    Následný pokus změny na vyšší hodnotu už SQL Management Studio neprovedlo, nové připojení na SQL Management Studio už neúspěšné (ani nevytvořil connection).

    Zůstal viset ve stavu, kdy si nechal asi 500 MB paměti a bral 25% CPU, což nekleslo ani po odpojení zátěže (kterou již stejně neodbavoval).

    Stop service nic, takže kill procesu z task manageru.

    Nový start service, zabraná paměť procesem 70 MB, bez šance na připojení z Management Studia.

    Zkouším OSQL, sp_reconfigure, nic, hlášky typu „There is insufficient system memory in resource pool ‚internal‘ to run this query.“, nebo se vůbec nevytvoří connection.

    Nakonec jsem to vybojoval na několikátý pokus (vždy těsně po startu service) přes „sqlcmd -A -E“ (Dedicated Administrator Connection) a volby

    sp_configure 'show advanced options', 1;
    GO
    RECONFIGURE;
    GO
    sp_configure 'max server memory', 2147483647;
    GO
    RECONFIGURE;
    GO
    

    …horká půlhodinka.

    VaDo said:

    Nepomohlo nastartovat SQL server z command line? http://msdn.microsoft.com/en-us/library/ms190737.aspx

    David Hlaváček said:

    Právě pro tyto stavy je k dispozici DAC (Dedicated Administrator Connection). Je to vyhrazené připojení pro systémového administrátora, které má přednostní právo v přidělení prostředků. V praxi se SQL Server z důvodu nedostatku paměti může dostat do stavu, který je ve článku posán.

    Management Studio dokáže DAC použít také, tam je to o změně connection stringu kdy v před název serveru se uvede ADMIN: tedy „ADMIN:SQLServer1“.

    DAC je standardně zapnuto pouze lokálně tzn. protokol Shared memory. Pokud by se tedy celý server dostal do problémů s pamětí, DAC není nic platné, když ho lokálně nevytvoříme. Proto je možné zapnout DAC s možností vzdáleného přístupu, což je vlastnost, která je po instalaci standardně zablokována a je nutné ji povolit.

    Návrh schématu databáze – Slides a ukázkové DB [TechEd Praha 2011]

    Slides a vzorové DB z přednášky pro TechEd Praha 2011:

    Záznam z přednášky nebyl pořizován, jiná verze téže přednášky však byla prezentována pro MS Fest Praha 2011 a k té záznam existuje.

    Konvence a zásady z vývojářské praxe [TechEd Praha 2011]

    Slides a dema z přednášky na konferenci TechEd Praha 2011:

    Z přednášky nebyl pořizován záznam, rozšířená podoba přednášky však byla prezentována pro WUG Praha a k té záznam existuje.

    Excel: Podmíněný součet SUMIF s odkazem na jinou buňku v podmínce

    Opět jsem chvíli bádal, tentokrát, jak zapsat do funkce SUMIF podmínku, která by se odkazovala na hodnotu jiné buňky. Např. chci sečíst hodnotu, pokud je větší než hodnota v jiné buňce (konkrétně jsem řešil jakési plovoucí součty umořování, ale to ten není podstatné). Důležité je, že pokud se potřebuji v podmínce (criteria), která je jinak textovým řetězcem, odkázat na hodnotu určité jiné buňky, tak si tam mohu hodnotu té jiné buňky prostě dostat textovým skládáním onoho řetězce:

    = SUMIF(C2:C10; ">" & A2; B2:B10)
    

    …jak prosté.

    Viz též Excel: Podmíněný součet SUMIF s podmínkou na (ne)prázdné buňky.

    Český stemmer do MSSQL 2008 R2 x64

    Dneska se budu s laskavým svolením chlubit cizím peřím, uvádím postup (mnou neověřený, ale z důvěryhodného zdroje – od Roberta Kindla z firmy EXEC), jak do MS SQL 2008 R2 x64 dostat český stemmer pro fultextové vyhledávání:

    ****

    musis nainstalovat Microsoft Search Server 2010 Express – to by melo byt zdarma a cesky stemmer to obsahuje

    bohuzel je ale ten stemmer dostupny jen v tom Sharepointu (neregistruje se to do systemu)

    takze jsem vlasnimi silami vyhackoval toto:
    Windows Registry Editor Version 5.00
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.SQL2008R2\MSSearch\Language\ces]
    „TsaurusFile“=“tsces.xml“
    „Locale“=dword:00000405
    „WBreakerClass“=“{468bfc77-3876-4a47-a6ff-f5f6e8ea7968}“
    „StemmerClass“=“{f51b7203-9bf9-4c39-b655-18fad8fa8a9a}“
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.SQL2008R2\MSSearch\CLSID\{f51b7203-9bf9-4c39-b655-18fad8fa8a9a}]
    @=“c:\\Program Files\\Microsoft Office Servers\\14.0\\Bin\\mswb7.dll“
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10_50.SQL2008R2\MSSearch\CLSID\{468bfc77-3876-4a47-a6ff-f5f6e8ea7968}]
    @=“c:\\Program Files\\Microsoft Office Servers\\14.0\\Bin\\mswb7.dll“
    toto konkretne zaregistruje ten cesky stemmer z Microsoft Search Server 2010 Express do MSSQL 2008 R2 (pro MSSQL 2008 a MSSQL 2005 by to zrejme slo podobne)

    pak uz staci jen
    exec sp_fulltext_service ‚update_languages‘;
    exec sp_fulltext_service ‚restart_all_fdhosts‘;

    overeni instalace (vypise se stemmer) s lcid 1029
    exec sp_help_fulltext_system_components wordbreaker
    SELECT * FROM sys.fulltext_languages order by lcid

    sklonovat to umi:
    select * from sys.dm_fts_parser(‚FORMSOF(INFLECTIONAL, květina)‘, 1029, 0, 0)

    ++++

    jeste je asi treba tohle:
    copy „c:\Program Files\Microsoft Office Servers\14.0\Data\Config\tsces.xml“ „c:\MSSQL2008R2\FTData“ (do slozky k ostatnim ts???.xml)
    Noise Words, ktere jsou v Sharepointu 2010 ulozeny v c:\Program Files\Microsoft Office Servers\14.0\Data\Config\noiseces.txt by se na MSSQL 2008 R2 mely konfigurovat pres CREATE FULLTEXT STOPLIST