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]
  • Napsat komentář

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Změnit )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Změnit )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Změnit )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Změnit )

    Connecting to %s