Resources v .NET aplikacích

Jan Holan       29. 3. 2011       I/O operace       6919 zobrazení

Resource se v tomto případě myslí nějaký obecně binární soubor, který můžeme přidat do Visual Studio projektu a je pak v .NET aplikaci načítán a dále používán. Resourců máme ale ve VS více typů, něco si o jednotlivých povíme a ukážeme si jak je použít. Ještě upozornění, tento článek se nebude zabývat používáním resource pro lokalizaci aplikace.

Nejjednodušší způsob jak používat resource je pomoci silně typově generovaných resx souborů (které jsou již od FW 2.0 a VS 2005). Ty mám navíc umožňují kromě string hodnot nadefinovat i jiný typy jako jsou obrázky (Images), ikony (Icons) a jiné binární soubory (Files). Na základě typu vloženého resource se pak vygeneruje kód vlastnosti, kterým se resource načte (např. image se vrací automaticky typem Drawing.Bitmap atd.). V tom je někdy problém, protože typ je navíc sám rovnou odvozen podle přípony souboru, pokud tedy potřebujeme např. image v souboru png vrátit jako binární resource tj. typem byte[], musíme soubor přejmenovat např. na soubor.bin. Build Action je u souborů takto použitých v resx souboru nastaven na None.

Dále se budeme věnovat jiných typům resource. Pokud nastavíme u souboru ve VS projektu Build Action na Embedded Resource bude soubor zahrnut do kompilované assembly (jako manifest resource). Z té lze pak načíst voláním metody GetManifestResourceStream na assembly. Ukázka viz. následující jednoduchý kód:

private static Stream LoadResourceImage(string imageName)
{
    var assembly = Assembly.GetExecutingAssembly();

    return assembly.GetManifestResourceStream(string.Format("Blog.SampleApplication.Images.{0}.png", imageName));
}

Metodě GetManifestResourceStream se při volání předává jméno resource, které se složeno z výchozího namespace assembly (Blog.SampleApplication), extended namespace = jméno složky v assembly (Images) a jména vlastního souboru ve složce (image.png), vše je oddělováno tečkami, rozlišuje se velikost písmen. Resource je vrácen jako Stream (přesněji řečeno jako UnmanagedMemoryStream). Pokud resource daného jména v assembly neexistuje, je vrácen null. Výhodou tohoto přístupu může být to, že máme plnou kontrolu nad získaných streamem, můžeme lehce načíst takovýto resource z jiné než spouštěné assemby a také je možné z assemby získat seznam takovýchto resource a procházet je. Více o těchto resource můžu odkázat např. na článek zde.

Možná jste si při práci ve WPF nebo Silverlight všimly, že zde používané soubory např. obrázků mají nastaveno Build Action na Resource nebo Content. V těchto technologiích se používají buď přímo v XAML souborech, nebo v kódu voláním např. Application.GetResourceStream (pro Resource) nebo XElement.Load (pro Content). V obou případech se zde tyto resource určují pomoci relativního URI (např. "/SilverlightApplication;component/EmbeddedInApplicationAssembly.png” v Silverlight).

Rozdíl Resource a Content je v tom, že Resource je zakompilován do assembly, zatímco na Content je v assembly pouze odkaz (metadata) a vlastní soubor je externě uložen ve stejném umístění jako assembly. Pokud chceme, aby nám Visual Studio rovnou tyto soubory uložilo do cesty, kam je aplikace buildovaná, můžeme u souborů nastavit volbu Copy to Output na hodnoty Copy if newer nebo Copy always. Pak je možné Content resource měnit i bez rekompilace assembly, naopak u Resource je zas výhoda v jednodušší distribuci, protože má aplikace méně souborů. Více se o těchto dvou typů resource můžeme dozvědět zde nebo zde.

Nyní se ale budeme věnovat jak Resouce soubory používat trochu jinak než to dělá WPF a Silverlight, a to jak je procházet nebo přímo načíst kdekoliv, ne jen ve WPF / Silverlight aplikacích. Pokud máme v projektu soubor s Build Action na Resource, vytvoří se při kompilaci v assembly manifest resource s názvem <assembly>.g.resources. (Pozor, v názvu je jméno assembly a ne výchozí namespace). Tento resource obsahuje všechny soubory s build action Resource, jméno pro jejich určení je dáno složkou v projektu a jménem souboru,  oddělovačem je znak / a jméno je uvedeno malými písmeny. Obrázek ukazuje takový resource v .NET Reflectoru.

Reflector

Pokud tedy víme jak je resource v assembly umístěn, můžeme vytvořit metodu, která umožňuje takový resource načíst podle jeho jména:

private static Stream GetResourcesStream(Assembly assembly, string name)
{
    //Get application g.resources stream
    using (var gResources = assembly.GetManifestResourceStream(assembly.GetName().Name + ".g.resources"))
    {
        if (gResources == null)
        {
            return null;
        }

        using (var resourceReader = new System.Resources.ResourceReader(gResources))
        {
            try
            {
                string resourceType;
                byte[] resourceData;
                resourceReader.GetResourceData(name.ToLowerInvariant(), out resourceType, out resourceData);

                if (resourceType.Equals("ResourceTypeCode.Stream", StringComparison.OrdinalIgnoreCase))
                {
                    using (var reader = new BinaryReader(new MemoryStream(resourceData, false)))
                    {
                        //Streams are encoded as their count followed by the bytes
                        int length = reader.ReadInt32();
                        return new MemoryStream(reader.ReadBytes(length), false);
                    }
                }
            }
            catch (ArgumentException)
            {
                //Resource does not exist.
                return null;
            }
        }
    }

Nejprve se již známou metodou GetManifestResourceStream načte “<assemby>.g.resource”. Na jeho další čtení použijeme třídu ResourceReader. Ta umožňuje metodou GetResourceData načíst data uložená pod daným jménem (také se na toto jméno volá ToLower). Metoda kromě dat ve formě pole bajtů vrací také typ resource. Zde se pro binární resource očekává typ ResourceTypeCode.Stream, seznam ostatních typů je uveden např. v tomto článku. Pokud není resource v “<assemby>.g.resource“ obsažen, je zde vyvolána vyjimka ArgumentException.

Posledním problémem, který naše metoda řeší je získání streamu. Data typu ResourceTypeCode.Stream, které jsou zde načteny, totiž nejsou přímo data souboru, ale jsou kódovány jako jejich velikost (4 bajty) následovány teprve vlastními daty. Proto se zde nejprve přes BinaryReader vyčte int obsahující velikost a poté voláním ReadBytes zbytek streamu. (V dokumentaci tato skutečnost popsána navíc není.)

Ještě si ukážeme příklad jak je možné pomoci třídy ResourceReader resource procházet.

public static Dictionary<string, object> GetResources()
{
    var asm = Assembly.GetExecutingAssembly();
    string resName = asm.GetName().Name + ".g.resources";

    using (var stream = asm.GetManifestResourceStream(resName))
    {
        if (stream != null)
        {
            using (var reader = new System.Resources.ResourceReader(stream))
            {
                return reader.Cast<DictionaryEntry>().ToDictionary(i=>(string)i.Key, i=>i.Value);
            }
        }
    }

    return new Dictionary<string, object>();
}

A použití může být následující:

foreach (var item in GetResources())
{
    var stream = item.Value as Stream;
    if (stream != null)
    {
        string fileName = Path.Combine(Environment.CurrentDirectory, item.Key.Replace("/", "\\"));

        Directory.CreateDirectory(Path.GetDirectoryName(fileName));

        using (var fs = new FileStream(fileName, FileMode.Create))
        {
            //Zapis souboru
            byte[] buffer = new byte[4096];
            int bytesRead = 0;
            while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                fs.Write(buffer, 0, bytesRead);
            }
        }
    }
}

Kód tohoto příkladu provede uložení vrácených resources na disk do souborů, jako jméno je použito jméno jednotlivých resources. Hodnota item.Value je v případě binárních souborů vrácena jako UnmanagedMemoryStream a obsahuje už přímo obsah souboru, není zde kódování s délkou jako v minulém příkladu s voláním GetResourceData.

 

hodnocení článku

0 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback