Trabajar con archivos .resx mediante programación

Nota

Este artículo se aplica a .NET Framework. Para obtener la información que se aplica a .NET 5+ (incluido .NET Core), consulte Recursos en archivos .resx.

Como los archivos de recursos (.resx) XML deben constar de código XML bien definido e incluir un encabezado según un esquema concreto, seguido de datos en pares nombre/valor, es posible que la creación manual de estos archivos sea propensa a errores. Como alternativa, puede crear archivos .resx mediante programación con tipos y miembros de la biblioteca de clases .NET Framework. También puede usar la biblioteca de clases .NET Framework para recuperar los recursos almacenados en archivos .resx. En este artículo se explica cómo se pueden usar los tipos y miembros del espacio de nombres System.Resources para trabajar con archivos .resx.

Se explica cómo trabajar con archivos XML (.resx) que contienen recursos. Para obtener información sobre cómo trabajar con archivos de recursos binarios que se han insertado en ensamblados, vea ResourceManager.

Advertencia

También hay formas distintas para trabajar con archivos .resx que no son mediante programación. Cuando se agrega un archivo de recursos a un proyecto de Visual Studio, Visual Studio proporciona una interfaz para crear y mantener un archivo .resx y lo convierte automáticamente en un archivo .resources en tiempo de compilación. También puede utilizar un editor de texto para manipular un archivo .resx directamente. Sin embargo, para evitar que se dañe el archivo, tenga cuidado de no modificar la información binaria almacenada en el archivo.

Creación de un archivo .resx

Puede utilizar la clase System.Resources.ResXResourceWriter para crear un archivo .resx mediante programación, siga estos pasos:

  1. Cree una instancia de un objeto ResXResourceWriter mediante una llamada al método ResXResourceWriter(String) y facilite el nombre del archivo .resx. El nombre de archivo debe incluir la extensión .resx. Si crea una instancia del objeto ResXResourceWriter en un bloque using , no es necesario que llame explícitamente al método ResXResourceWriter.Close en el paso 3.

  2. Llame al método ResXResourceWriter.AddResource para cada recurso que quiera agregar al archivo. Utilice las sobrecargas de este método para agregar datos binarios (matriz de bytes), de objeto y de cadena. Si el recurso es un objeto, debe ser serializable.

  3. Llame al método ResXResourceWriter.Close para generar el archivo de recursos y liberar todos los recursos. Si el objeto ResXResourceWriter se creó dentro de un bloque using , los recursos se escriben en el archivo .resx y los que utiliza el objeto ResXResourceWriter se liberan al final del bloque using .

El archivo .resx resultante tiene el encabezado adecuado y una etiqueta data por cada recurso que agregó el método ResXResourceWriter.AddResource .

Advertencia

No utilice archivos de recursos para almacenar contraseñas, información relativa a la seguridad o datos privados.

En el ejemplo siguiente se crea un archivo .resx denominado CarResources.resx que almacena seis cadenas, un icono y dos objetos definidos por la aplicación (dos objetos Automobile ). La clase Automobile, que se define y de la que se crea una instancia en el ejemplo, se etiqueta con el atributo SerializableAttribute.

using System;
using System.Drawing;
using System.Resources;

[Serializable()] public class Automobile
{
   private string carMake;
   private string carModel;
   private int carYear;
   private int carDoors;
   private int carCylinders;

   public Automobile(string make, string model, int year) :
                     this(make, model, year, 0, 0)
   { }

   public Automobile(string make, string model, int year,
                     int doors, int cylinders)
   {
      this.carMake = make;
      this.carModel = model;
      this.carYear = year;
      this.carDoors = doors;
      this.carCylinders = cylinders;
   }

   public string Make {
      get { return this.carMake; }
   }

   public string Model {
      get {return this.carModel; }
   }

   public int Year {
      get { return this.carYear; }
   }

   public int Doors {
      get { return this.carDoors; }
   }

   public int Cylinders {
      get { return this.carCylinders; }
   }
}

public class Example
{
   public static void Main()
   {
      // Instantiate an Automobile object.
      Automobile car1 = new Automobile("Ford", "Model N", 1906, 0, 4);
      Automobile car2 = new Automobile("Ford", "Model T", 1909, 2, 4);
      // Define a resource file named CarResources.resx.
      using (ResXResourceWriter resx = new ResXResourceWriter(@".\CarResources.resx"))
      {
         resx.AddResource("Title", "Classic American Cars");
         resx.AddResource("HeaderString1", "Make");
         resx.AddResource("HeaderString2", "Model");
         resx.AddResource("HeaderString3", "Year");
         resx.AddResource("HeaderString4", "Doors");
         resx.AddResource("HeaderString5", "Cylinders");
         resx.AddResource("Information", SystemIcons.Information);
         resx.AddResource("EarlyAuto1", car1);
         resx.AddResource("EarlyAuto2", car2);
      }
   }
}
Imports System.Drawing
Imports System.Resources

<Serializable()> Public Class Automobile
    Private carMake As String
    Private carModel As String
    Private carYear As Integer
    Private carDoors AS Integer
    Private carCylinders As Integer

    Public Sub New(make As String, model As String, year As Integer)
        Me.New(make, model, year, 0, 0)
    End Sub

    Public Sub New(make As String, model As String, year As Integer,
                   doors As Integer, cylinders As Integer)
        Me.carMake = make
        Me.carModel = model
        Me.carYear = year
        Me.carDoors = doors
        Me.carCylinders = cylinders
    End Sub

    Public ReadOnly Property Make As String
        Get
            Return Me.carMake
        End Get
    End Property

    Public ReadOnly Property Model As String
        Get
            Return Me.carModel
        End Get
    End Property

    Public ReadOnly Property Year As Integer
        Get
            Return Me.carYear
        End Get
    End Property

    Public ReadOnly Property Doors As Integer
        Get
            Return Me.carDoors
        End Get
    End Property

    Public ReadOnly Property Cylinders As Integer
        Get
            Return Me.carCylinders
        End Get
    End Property
End Class

Module Example
    Public Sub Main()
        ' Instantiate an Automobile object.
        Dim car1 As New Automobile("Ford", "Model N", 1906, 0, 4)
        Dim car2 As New Automobile("Ford", "Model T", 1909, 2, 4)
        ' Define a resource file named CarResources.resx.
        Using resx As New ResXResourceWriter(".\CarResources.resx")
            resx.AddResource("Title", "Classic American Cars")
            resx.AddResource("HeaderString1", "Make")
            resx.AddResource("HeaderString2", "Model")
            resx.AddResource("HeaderString3", "Year")
            resx.AddResource("HeaderString4", "Doors")
            resx.AddResource("HeaderString5", "Cylinders")
            resx.AddResource("Information", SystemIcons.Information)
            resx.AddResource("EarlyAuto1", car1)
            resx.AddResource("EarlyAuto2", car2)
        End Using
    End Sub
End Module

Sugerencia

También puede usar Visual Studio para crear archivos .resx. En tiempo de compilación, Visual Studio utiliza el generador de archivos de recursos (Resgen.exe) para convertir el archivo .resx en un archivo de recursos binario (.resources) y también lo inserta en un ensamblado de aplicación o un ensamblado satélite.

No se puede insertar un archivo .resx en un archivo ejecutable en tiempo de ejecución ni compilarlo en un ensamblado satélite. Debe convertir el archivo .resx en un archivo de recursos binario (.resources) con el generador de archivos de recursos (Resgen.exe). El archivo .resources resultante puede entonces insertarse en un ensamblado de aplicación o un ensamblado satélite. Para obtener más información, consulte Creación de archivos de recursos.

Enumeración de los recursos

En algunos casos, quizás quiera recuperar todos los recursos de un archivo .resx, en lugar de un recurso concreto. Para ello, puede utilizar la clase System.Resources.ResXResourceReader , que ofrece un enumerador para todos los recursos del archivo .resx. La clase System.Resources.ResXResourceReader implementa IDictionaryEnumerator, que devuelve un objeto DictionaryEntry que representa un recurso concreto para cada iteración del bucle. Su propiedad DictionaryEntry.Key devuelve la clave del recurso y su propiedad DictionaryEntry.Value devuelve el valor del recurso.

En el ejemplo siguiente, se crea un objeto ResXResourceReader para el archivo CarResources.resx creado en el ejemplo anterior y se recorre iterativamente el archivo de recursos. Se agregan los dos objetos Automobile que están definidos en el archivo de recursos para un objeto System.Collections.Generic.List<T> y se agregan cinco de las seis cadenas a un objeto SortedList . Los valores del objeto SortedList se convierten en una matriz de parámetros, que se utiliza para mostrar los encabezados de columna en la consola. Los valores de propiedad Automobile también se muestran en la consola.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Resources;

public class Example
{
   public static void Main()
   {
      string resxFile = @".\CarResources.resx";
      List<Automobile> autos = new List<Automobile>();
      SortedList headers = new SortedList();

      using (ResXResourceReader resxReader = new ResXResourceReader(resxFile))
      {
         foreach (DictionaryEntry entry in resxReader) {
            if (((string) entry.Key).StartsWith("EarlyAuto"))
               autos.Add((Automobile) entry.Value);
            else if (((string) entry.Key).StartsWith("Header"))
               headers.Add((string) entry.Key, (string) entry.Value);
         }
      }
      string[] headerColumns = new string[headers.Count];
      headers.GetValueList().CopyTo(headerColumns, 0);
      Console.WriteLine("{0,-8} {1,-10} {2,-4}   {3,-5}   {4,-9}\n",
                        headerColumns);
      foreach (var auto in autos)
         Console.WriteLine("{0,-8} {1,-10} {2,4}   {3,5}   {4,9}",
                           auto.Make, auto.Model, auto.Year,
                           auto.Doors, auto.Cylinders);
   }
}
// The example displays the following output:
//       Make     Model      Year   Doors   Cylinders
//
//       Ford     Model N    1906       0           4
//       Ford     Model T    1909       2           4
Imports System.Collections
Imports System.Collections.Generic
Imports System.Resources

Module Example
    Public Sub Main()
        Dim resxFile As String = ".\CarResources.resx"
        Dim autos As New List(Of Automobile)
        Dim headers As New SortedList()

        Using resxReader As New ResXResourceReader(resxFile)
            For Each entry As DictionaryEntry In resxReader
                If CType(entry.Key, String).StartsWith("EarlyAuto") Then
                    autos.Add(CType(entry.Value, Automobile))
                Else If CType(entry.Key, String).StartsWith("Header") Then
                    headers.Add(CType(entry.Key, String), CType(entry.Value, String))
                End If
            Next
        End Using
        Dim headerColumns(headers.Count - 1) As String
        headers.GetValueList().CopyTo(headerColumns, 0)
        Console.WriteLine("{0,-8} {1,-10} {2,-4}   {3,-5}   {4,-9}",
                          headerColumns)
        Console.WriteLine()
        For Each auto In autos
            Console.WriteLine("{0,-8} {1,-10} {2,4}   {3,5}   {4,9}",
                              auto.Make, auto.Model, auto.Year,
                              auto.Doors, auto.Cylinders)
        Next
    End Sub
End Module
' The example displays the following output:
'       Make     Model      Year   Doors   Cylinders
'       
'       Ford     Model N    1906       0           4
'       Ford     Model T    1909       2           4

Recuperación de un recurso concreto

Además de enumerar los elementos de un archivo .resx, puede recuperar un recurso concreto por su nombre con la clase System.Resources.ResXResourceSet . El método ResourceSet.GetString(String) recupera el valor de un recurso de cadena con nombre. El método ResourceSet.GetObject(String) recupera el valor de datos binarios o un objeto con nombre. El método devuelve un objeto que se debe convertir en un objeto del tipo adecuado.

En el ejemplo siguiente se recupera la cadena de título de un formulario y el icono por sus nombres de recursos. También se recuperan los objetos Automobile definidos por la aplicación que se usan en el ejemplo anterior y se muestran en un control DataGridView.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Resources;
using System.Windows.Forms;

public class CarDisplayApp : Form
{
   private const string resxFile = @".\CarResources.resx";
   Automobile[] cars;

   public static void Main()
   {
      CarDisplayApp app = new CarDisplayApp();
      Application.Run(app);
   }

   public CarDisplayApp()
   {
      // Instantiate controls.
      PictureBox pictureBox = new PictureBox();
      pictureBox.Location = new Point(10, 10);
      this.Controls.Add(pictureBox);
      DataGridView grid = new DataGridView();
      grid.Location = new Point(10, 60);
      this.Controls.Add(grid);

      // Get resources from .resx file.
      using (ResXResourceSet resxSet = new ResXResourceSet(resxFile))
      {
         // Retrieve the string resource for the title.
         this.Text = resxSet.GetString("Title");
         // Retrieve the image.
         Icon image = (Icon) resxSet.GetObject("Information", true);
         if (image != null)
            pictureBox.Image = image.ToBitmap();

         // Retrieve Automobile objects.
         List<Automobile> carList = new List<Automobile>();
         string resName = "EarlyAuto";
         Automobile auto;
         int ctr = 1;
         do {
            auto = (Automobile) resxSet.GetObject(resName + ctr.ToString());
            ctr++;
            if (auto != null)
               carList.Add(auto);
         } while (auto != null);
         cars = carList.ToArray();
         grid.DataSource = cars;
      }
   }
}
Imports System.Collections.Generic
Imports System.Drawing
Imports System.Resources
Imports System.Windows.Forms

Public Class CarDisplayApp : Inherits Form
    Private Const resxFile As String = ".\CarResources.resx"
    Dim cars() As Automobile

    Public Shared Sub Main()
        Dim app As New CarDisplayApp()
        Application.Run(app)
    End Sub

    Public Sub New()
        ' Instantiate controls.
        Dim pictureBox As New PictureBox()
        pictureBox.Location = New Point(10, 10)
        Me.Controls.Add(pictureBox)
        Dim grid As New DataGridView()
        grid.Location = New Point(10, 60)
        Me.Controls.Add(grid)

        ' Get resources from .resx file.
        Using resxSet As New ResXResourceSet(resxFile)
            ' Retrieve the string resource for the title.
            Me.Text = resxSet.GetString("Title")
            ' Retrieve the image.
            Dim image As Icon = CType(resxSet.GetObject("Information", True), Icon)
            If image IsNot Nothing Then
                pictureBox.Image = image.ToBitmap()
            End If

            ' Retrieve Automobile objects.  
            Dim carList As New List(Of Automobile)
            Dim resName As String = "EarlyAuto"
            Dim auto As Automobile
            Dim ctr As Integer = 1
            Do
                auto = CType(resxSet.GetObject(resName + ctr.ToString()), Automobile)
                ctr += 1
                If auto IsNot Nothing Then carList.Add(auto)
            Loop While auto IsNot Nothing
            cars = carList.ToArray()
            grid.DataSource = cars
        End Using
    End Sub
End Class

Conversión de archivos .resx en archivos .resources binarios

La conversión de archivos .resx en archivos de recursos binario ( .resources) insertados tiene ventajas importantes. Aunque los archivos .resx son fáciles de leer y mantener durante el desarrollo de aplicaciones, en rara ocasión se incluyen con las aplicaciones terminadas. Si se distribuyen con una aplicación, son archivos independientes separados del ejecutable de la aplicación y las bibliotecas que lo acompañan. En cambio, los archivos .resources se insertan en el archivo ejecutable de la aplicación o en los ensamblados que lo acompañan. Además, en las aplicaciones localizadas, al depender de archivos .resx en tiempo de ejecución, la responsabilidad de controlar la reserva de recursos es del desarrollador. En cambio, si se creó un conjunto de ensamblados satélite que contienen archivos .resources insertados, Common Language Runtime administra el proceso de reserva de recursos.

Para convertir un archivo .resx en un archivo .resources, utilice el Generador de archivos de recursos (resgen.exe), que tiene la sintaxis básica siguiente:

 resgen.exe .resxFilename

El resultado es un archivo de recursos binario que tiene el mismo nombre de archivo raíz que el archivo .resx y una extensión de archivo .resources. Este archivo puede compilarse en un archivo ejecutable o una biblioteca en tiempo de compilación. Si está utilizando el compilador de Visual Basic, use la siguiente sintaxis para insertar un archivo .resources en el archivo ejecutable de la aplicación:

vbc filename .vb -resource: .resourcesFilename

Si está utilizando C#, la sintaxis es la que se indica a continuación:

 csc filename .cs -resource: .resourcesFilename

También se puede insertar el archivo .resources en un ensamblado satélite con Assembly Linker (al.exe), que tiene la sintaxis básica siguiente:

al resourcesFilename -out: assemblyFilename

Vea también