Хранение ресурсов в базе данных

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 18.12.2019

В предыдущих статьях для хранения ресурсов, используемых при локализации, применялись файлы ресурсов resx. Однако мы также можем использовать для хранения ресурсов любое хранилище, например, базу данных. Рассмотрим как это сделать.

Вначале определим модели. Добавим в проект в папку Models два класса Culture и Resource:

Класс Resource:

public class Resource
{
	public int Id { get; set; }
    public string Key { get; set; }		// ключ
    public string Value { get; set; }	// значение
    public Culture Culture { get; set; }
}

Класс ресурса содержит свойства для хранения ключа, значения по ключу и ссылку на культуру, к которой принадлежит ресурс.

Класс Culture:

using System.Collections.Generic;

namespace EFLocalizationApp.Models
{
    public class Culture
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<Resource> Resources { get; set; }
        public Culture()
        {
            Resources = new List<Resource>();
        }
    }
}

Класс культуры хранит ее название и список связанных ресурсов.

Для взаимодействия с MS SQL Server через Entity Framework добавим в проект через Nuget пакет Microsoft.EntityFrameworkCore.SqlServer. А затем добавим в папку Models класс контекста данных LocalizationContext, через который мы будем работать с базой данных:

using Microsoft.EntityFrameworkCore;

namespace EFLocalizationApp.Models
{
    public class LocalizationContext : DbContext
    {
        public LocalizationContext(DbContextOptions<LocalizationContext> options) 
            : base(options)
        { 
			Database.EnsureCreated();
		}
        public DbSet<Culture> Cultures { get; set; }
        public DbSet<Resource> Resources { get; set; }
    }
}

Теперь нам надо создать инфраструктуру, которая бы извлекала из базы данных через контекст нужный ресурс. Для этого добавим в корень проекта новый класс EFStringLocalizer:

using EFLocalizationApp.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace EFLocalizationApp
{
    public class EFStringLocalizer : IStringLocalizer
    {
        private readonly LocalizationContext _db;

        public EFStringLocalizer(LocalizationContext db)
        {
            _db = db;
        }

        public LocalizedString this[string name]
        {
            get
            {
                var value = GetString(name);
                return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
            }
        }

        public LocalizedString this[string name, params object[] arguments]
        {
            get
            {
                var format = GetString(name);
                var value = string.Format(format ?? name, arguments);
                return new LocalizedString(name, value, resourceNotFound: format == null);
            }
        }

        public IStringLocalizer WithCulture(CultureInfo culture)
        {
            CultureInfo.DefaultThreadCurrentCulture = culture;
            return new EFStringLocalizer(_db);
        }

        public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
        {
            return _db.Resources
                .Include(r => r.Culture)
                .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
                .Select(r => new LocalizedString(r.Key, r.Value));
        }

        private string GetString(string name)
        {
            return _db.Resources
                .Include(r => r.Culture)
                .Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
                .FirstOrDefault(r => r.Key == name)?.Value;
        }
    }
}

С помощью индексаторов возвращаем из базы данных по ключу объект Resource, из которого формируем экземпляр класса LocalizedString. Для обращения к базе данных через встроенный механизм внедрения зависимостей получаем в конструкторе контекст данных.

Также добавим в корень проекта еще один класс, который назовем EFStringLocalizerFactory. Этот класс будет представлять фабрику объектов IStringLocalizer:

using EFLocalizationApp.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Linq;

namespace EFLocalizationApp
{
    public class EFStringLocalizerFactory : IStringLocalizerFactory
    {
        string _connectionString;
        public EFStringLocalizerFactory(string connection)
        {
            _connectionString = connection;
        }

        public IStringLocalizer Create(Type resourceSource)
        {
            return CreateStringLocalizer();
        }

        public IStringLocalizer Create(string baseName, string location)
        {
            return CreateStringLocalizer();
        }

        private IStringLocalizer CreateStringLocalizer()
        {
            LocalizationContext _db = new LocalizationContext(
                new DbContextOptionsBuilder<LocalizationContext>()
                    .UseSqlServer(_connectionString)
                    .Options);
			// инициализация базы данных
            if (!_db.Cultures.Any())
            {
                _db.AddRange(
                    new Culture
                    {
                        Name = "en",
                        Resources = new List<Resource>()
                        {
                            new Resource { Key = "Header", Value = "Hello" },
                            new Resource { Key = "Message", Value = "Welcome" }
                        }
                    },
                    new Culture
                    {
                        Name = "ru",
                        Resources = new List<Resource>()
                        {
                            new Resource { Key = "Header", Value = "Привет" },
                            new Resource { Key = "Message", Value = "Добро пожаловать" }
                        }
                    },
                    new Culture
                    {
                        Name = "de",
                        Resources = new List<Resource>()
                        {
                            new Resource { Key = "Header", Value = "Hallo" },
                            new Resource { Key = "Message", Value = "Willkommen" }
                        }
                    }
                );
                _db.SaveChanges();
            }
            return new EFStringLocalizer(_db);
        }
    }
}

Фабрика EFStringLocalizerFactory будет определена как синглтон (так как нет смысла создавать для каждого запроса отдельный объект фабрики), поэтому контекст данных, через который фабрика взаимодействует с БД, создается непосредственно внутри нее. В частности, в методе CreateStringLocalizer определяется контекст данных, происходи инициализация БД и возвращается объект EFStringLocalizer.

При создании объекта IStringLocalizer инфраструктурой MVC у фабрики будут вызываться методы Create, которые собственно и будут возвращает объект EFStringLocalizer. И этот объект потом будет использоваться для локализации.

Итоге получится следующий проект:

Localization in ASP.NET Core and Entity Framework Core

Теперь все это используем в классе Startup:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using EFLocalizationApp.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using System.Globalization;
using Microsoft.AspNetCore.Localization;
namespace EFLocalizationApp
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            string connection = "Server=(localdb)\\mssqllocaldb;Database=localizationdb;Trusted_Connection=True;";
            services.AddDbContext<LocalizationContext>(options => options.UseSqlServer(connection));

            services.AddTransient<IStringLocalizer, EFStringLocalizer>();
            services.AddSingleton<IStringLocalizerFactory>(new EFStringLocalizerFactory(connection));
            services.AddControllersWithViews().AddDataAnnotationsLocalization(options => {
                options.DataAnnotationLocalizerProvider = (type, factory) =>
                factory.Create(null);
            })
            .AddViewLocalization(); ;
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseDeveloperExceptionPage();

            var supportedCultures = new[]
            {
                new CultureInfo("en"),
                new CultureInfo("ru"),
                new CultureInfo("de")
            };
            app.UseRequestLocalization(new RequestLocalizationOptions
            {
                DefaultRequestCulture = new RequestCulture("ru"),
                SupportedCultures = supportedCultures,
                SupportedUICultures = supportedCultures
            });

            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

С локализацией контроллеров и представлений все относительно просто. Нам просто надо установить зависимость для IStringLocalizer:

services.AddTransient<IStringLocalizer, EFStringLocalizer>();

С аннотациями данных все сложнее, так как для аннотаций данных необходимо создать через фабрику объект IStringLocalizer. Но чтобы использовать нужную фабрику, надо установить зависимость для IStringLocalizerFactory:

services.AddTransient<IStringLocalizerFactory, EFStringLocalizerFactory>();

services.AddControllersWithViews
        .AddDataAnnotationsLocalization(options => {
			options.DataAnnotationLocalizerProvider = (type, factory) =>
				factory.Create(null);
        })

Для применения EFStringLocalizer в представлениях, необходимо инжектировать сервис IViewLocalizer:

@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

<h1>@Localizer["Header"]</h1>
<h3>@Localizer["Message"]</h3>

Для использования для аннотаций данных ничего дополнительно не надо делать.

А в контроллере также через конструктор передается сервис IStringLocalizer:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

namespace EFLocalizationApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly IStringLocalizer _localizer;

        public HomeController(IStringLocalizer localizer)
        {
            _localizer = localizer;
        }
        public string Test()
        {
            string message = _localizer["Message"];
            return message;
        }
        public IActionResult Index()
        {
            return View();
        }
    }
}

К примеру, обращение к действию Test при установке различных культур:

Локализация из базы данных в ASP.NET Core Локализация с помощью Entity Framework Core в ASP.NET Core MVC
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850