ASP.NET CORE 1.0, AppSettings из другой сборки

У меня есть приложение, разделенное на два проекта: веб-приложение и библиотеку классов. Startup есть только в веб-приложении:

var builder = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")

Я хочу, чтобы мой appsettings.json был в этой библиотеке классов и загружался оттуда. Это возможно? Как я могу это сделать?


person Dani    schedule 08.02.2016    source источник
comment
Вы хотели прочитать appsettings.json целиком или просто использовать части appsettings.json веб-приложения для некоторых значений конфигурации в библиотеке? Для последнего случая я просто создал класс конфигурации в библиотеке и статическое свойство этого класса. В Startup.Configure я привязываю это свойство к соответствующему разделу в appsertings.json: Configuration.GetSection("MyLibrarySettings").Bind(MyLibrary.Settings.Default);. Не знаю, хорошее ли это решение, но оно простое.   -  person noox    schedule 02.05.2016
comment
Пожалуйста, смотрите мой ответ на этот вопрос. Вам не нужно писать какой-либо код, чтобы ваша конфигурация была доступна в другой сборке. stackoverflow.com/ вопросы/43080695/   -  person Razor    schedule 30.09.2017


Ответы (4)


Лучшее решение, которое я нашел, требует создания новых IFileProvider и IFileInfo, а затем встраивания файлов настроек JSON в вашу сборку.

Решение повторно использует существующую логику AddJsonFile. Таким образом, вам нужно только сообщить системе конфигурации, как и где найти файл JSON, а не как его анализировать.

Решение совместимо с .NET Core 1.0.

Использование:

public class Startup
{
    private readonly AppSettings _appSettings;

    public Startup(IHostingEnvironment env)
    {
        Assembly assembly = GetType().GetTypeInfo().Assembly;

        new ConfigurationBuilder()
          .AddEmbeddedJsonFile(assembly, "appsettings.json")
          .AddEmbeddedJsonFile(assembly, $"appsettings.{env.EnvironmentName.ToLower()}.json")
          .Build();
    }

    ...
}

Вставьте файлы, обновив файл project.json для библиотеки классов:

...
"buildOptions": {
  "embed": [
    "appsettings.json",
    "appsettings.development.json",
    "appsettings.production.json"
  ]
}
...

Реализация IEmbeddedFileInfo:

public class EmbeddedFileInfo : IFileInfo
{
    private readonly Stream _fileStream;

    public EmbeddedFileInfo(string name, Stream fileStream)
    {
        if (name == null) throw new ArgumentNullException(nameof(name));
        if (fileStream == null) throw new ArgumentNullException(nameof(fileStream));

        _fileStream = fileStream;

        Exists = true;
        IsDirectory = false;
        Length = fileStream.Length;
        Name = name;
        PhysicalPath = name;
        LastModified = DateTimeOffset.Now;
    }

    public Stream CreateReadStream()
    {
        return _fileStream;
    }

    public bool Exists { get; }
    public bool IsDirectory { get; }
    public long Length { get; }
    public string Name { get; }
    public string PhysicalPath { get; }
    public DateTimeOffset LastModified { get; }
}

Реализация IFileInfo:

public class EmbeddedFileProvider : IFileProvider
{
    private readonly Assembly _assembly;

    public EmbeddedFileProvider(Assembly assembly)
    {
        if (assembly == null) throw new ArgumentNullException(nameof(assembly));

        _assembly = assembly;
    }

    public IFileInfo GetFileInfo(string subpath)
    {
        string fullFileName = $"{_assembly.GetName().Name}.{subpath}";

        bool isFileEmbedded = _assembly.GetManifestResourceNames().Contains(fullFileName);

        return isFileEmbedded
          ? new EmbeddedFileInfo(subpath, _assembly.GetManifestResourceStream(fullFileName))
          : (IFileInfo) new NotFoundFileInfo(subpath);
    }

    public IDirectoryContents GetDirectoryContents(string subpath)
    {
        throw new NotImplementedException();
    }

    public IChangeToken Watch(string filter)
    {
        throw new NotImplementedException();
    }
}

Создайте простой в использовании метод расширения AddEmbeddedJsonFile.

public static class ConfigurationBuilderExtensions
{
    public static IConfigurationBuilder AddEmbeddedJsonFile(this IConfigurationBuilder cb,
        Assembly assembly, string name, bool optional = false)
    {
        // reload on change is not supported, always pass in false
        return cb.AddJsonFile(new EmbeddedFileProvider(assembly), name, optional, false);
    }
}
person cebru    schedule 02.07.2016
comment
Себру, это фантастика. Именно то, что я искал. - person ThisGuy; 20.07.2017
comment

Да, вы можете реализовать IConfigurationProvider.

Существует базовый класс ConfigurationProvider, который вы можете наследовать, а затем переопределить все виртуальные методы.

Вы также можете увидеть, как JsonConfigurationProvider реализовано.

Поэтому я предполагаю, что ваша реализация может использовать внутренний код поставщика Json для встроенных файлов json.

Затем вы также захотите реализовать расширение ConfigurationBuilder для регистрации вашего провайдера, аналогичное коду для использования конфигурации json.

person Joe Audette    schedule 08.02.2016
comment
Как я могу вставить файл json? - person Dani; 08.02.2016
comment
это другой вопрос, чем это возможно. Я предлагаю сначала погуглить этот новый вопрос, а затем, если вы не найдете то, что вам нужно, задайте новый, более конкретный вопрос. - person Joe Audette; 08.02.2016
comment
Я говорю это только потому, что погуглил, и теперь вы можете найти ответ на этот вопрос. - person Joe Audette; 08.02.2016
comment
@Dani Смотрите мой ответ. На мой взгляд, лучшим решением является встраивание файлов JSON. - person cebru; 02.07.2016

Кто-то другой может меня поправить, но я не думаю, что то, что вы ищете, существует. Файлы App Configs и AppSettings считываются во время выполнения работающим приложением.

Библиотека классов не может видеть никаких специфичных для себя AppSettings, поскольку при запуске во время выполнения она находится в папке работающего приложения.

Единственный потенциальный способ, который я вижу для вас, чтобы ваша библиотека классов содержала файл json, - это иметь файл json в качестве встроенного ресурса. Например: в решении выберите файл json и установите для него встроенный ресурс вместо «контент».

Проблема заключается в том, чтобы извлечь встроенный файл конфигурации из вашей сборки, а затем загрузить его.

AddJsonFile принимает путь к json-файлу.

Однако вы можете извлечь файл Json во временный каталог, а затем загрузить оттуда.

static byte[] StreamToBytes(Stream input)
            {

                int capacity = input.CanSeek ? (int)input.Length : 0; 
                using (MemoryStream output = new MemoryStream(capacity))
                {
                    int readLength;
                    byte[] buffer = new byte[capacity/*4096*/];  //An array of bytes
                    do
                    {
                        readLength = input.Read(buffer, 0, buffer.Length);   //Read the memory data, into the buffer
                        output.Write(buffer, 0, readLength);
                    }
                    while (readLength != 0); //Do all this while the readLength is not 0
                    return output.ToArray();  //When finished, return the finished MemoryStream object as an array.
                }

            }



Assembly yourAssembly = Assembly.GetAssembly(typeof(MyTypeWithinAssembly));
using (Stream input = yourAssembly.GetManifestResourceStream("NameSpace.Resources.Config.json")) // Acquire the dll from local memory/resources.
                {
                    byte[] byteData  = StreamToBytes(input);
                    System.IO.File.WriteAllBytes(Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)+"//Config.json",new byte[]{});
                }



var builder = new ConfigurationBuilder()
    .AddJsonFile(Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)+"//Config.json");

Теоретически вы должны иметь возможность указать тип из библиотеки классов, чтобы помочь коду С# конкретно ориентироваться на эту библиотеку классов. Затем вам просто нужно указать пространство имен и путь к встроенному файлу json.

person Baaleos    schedule 08.02.2016

Вот мое решение, спасибо Baaleos и Joe за ваши советы.

project.json

"resource": [
    "appsettings.json"
  ]

startup.cs

var builder = new ConfigurationBuilder()
    .Add(new SettingsConfigurationProvider("appsettings.json"))
    .AddEnvironmentVariables();

this.Configuration = builder.Build();

namespace ClassLibrary
{
    using Microsoft.Extensions.Configuration;
    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// A JSON file based <see cref="ConfigurationProvider"/> for embedded resources.
    /// </summary>
    public class SettingsConfigurationProvider : ConfigurationProvider
    {
        /// <summary>
        /// Initializes a new instance of <see cref="SettingsConfigurationProvider"/>.
        /// </summary>
        /// <param name="name">Name of the JSON configuration file.</param>
        /// <param name="optional">Determines if the configuration is optional.</param>
        public SettingsConfigurationProvider(string name)
            : this(name, false)
        {
        }

        /// <summary>
        /// Initializes a new instance of <see cref="SettingsConfigurationProvider"/>.
        /// </summary>
        /// <param name="name">Name of the JSON configuration file.</param>
        /// <param name="optional">Determines if the configuration is optional.</param>
        public SettingsConfigurationProvider(string name, bool optional)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("Name must be a non-empty string.", nameof(name));
            }

            this.Optional = optional;
            this.Name = name;
        }

        /// <summary>
        /// Gets a value that determines if this instance of <see cref="SettingsConfigurationProvider"/> is optional.
        /// </summary>
        public bool Optional { get; }

        /// <summary>
        /// The name of the file backing this instance of <see cref="SettingsConfigurationProvider"/>.
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// Loads the contents of the embedded resource with name <see cref="Path"/>.
        /// </summary>
        /// <exception cref="FileNotFoundException">If <see cref="Optional"/> is <c>false</c> and a
        /// resource does not exist with name <see cref="Path"/>.</exception>
        public override void Load()
        {
            Assembly assembly = Assembly.GetAssembly(typeof(SettingsConfigurationProvider));
            var resourceName = $"{assembly.GetName().Name}.{this.Name}";
            var resources = assembly.GetManifestResourceNames();

            if (!resources.Contains(resourceName))
            {
                if (Optional)
                {
                    Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                }
                else
                {
                    throw new FileNotFoundException($"The configuration file with name '{this.Name}' was not found and is not optional.");
                }
            }
            else
            {
                using (Stream settingsStream = assembly.GetManifestResourceStream(resourceName))
                {
                    Load(settingsStream);
                }
            }
        }

        internal void Load(Stream stream)
        {
            JsonConfigurationFileParser parser = new JsonConfigurationFileParser();
            try
            {
                Data = parser.Parse(stream);
            }
            catch (JsonReaderException e)
            {
                string errorLine = string.Empty;
                if (stream.CanSeek)
                {
                    stream.Seek(0, SeekOrigin.Begin);

                    IEnumerable<string> fileContent;
                    using (var streamReader = new StreamReader(stream))
                    {
                        fileContent = ReadLines(streamReader);
                        errorLine = RetrieveErrorContext(e, fileContent);
                    }
                }

                throw new FormatException($"Could not parse the JSON file. Error on line number '{e.LineNumber}': '{e}'.");
            }
        }

        private static string RetrieveErrorContext(JsonReaderException e, IEnumerable<string> fileContent)
        {
            string errorLine;
            if (e.LineNumber >= 2)
            {
                var errorContext = fileContent.Skip(e.LineNumber - 2).Take(2).ToList();
                errorLine = errorContext[0].Trim() + Environment.NewLine + errorContext[1].Trim();
            }
            else
            {
                var possibleLineContent = fileContent.Skip(e.LineNumber - 1).FirstOrDefault();
                errorLine = possibleLineContent ?? string.Empty;
            }

            return errorLine;
        }

        private static IEnumerable<string> ReadLines(StreamReader streamReader)
        {
            string line;
            do
            {
                line = streamReader.ReadLine();
                yield return line;
            } while (line != null);
        }
    }
}

Вам также потребуется JsonConfigurationFileParser. :

namespace ClassLibrary
{
    using Microsoft.Extensions.Configuration;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;

    internal class JsonConfigurationFileParser
    {
        private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        private readonly Stack<string> _context = new Stack<string>();
        private string _currentPath;

        private JsonTextReader _reader;

        public IDictionary<string, string> Parse(Stream input)
        {
            _data.Clear();
            _reader = new JsonTextReader(new StreamReader(input));
            _reader.DateParseHandling = DateParseHandling.None;

            var jsonConfig = JObject.Load(_reader);

            VisitJObject(jsonConfig);

            return _data;
        }

        private void VisitJObject(JObject jObject)
        {
            foreach (var property in jObject.Properties())
            {
                EnterContext(property.Name);
                VisitProperty(property);
                ExitContext();
            }
        }

        private void VisitProperty(JProperty property)
        {
            VisitToken(property.Value);
        }

        private void VisitToken(JToken token)
        {
            switch (token.Type)
            {
                case JTokenType.Object:
                    VisitJObject(token.Value<JObject>());
                    break;

                case JTokenType.Array:
                    VisitArray(token.Value<JArray>());
                    break;

                case JTokenType.Integer:
                case JTokenType.Float:
                case JTokenType.String:
                case JTokenType.Boolean:
                case JTokenType.Bytes:
                case JTokenType.Raw:
                case JTokenType.Null:
                    VisitPrimitive(token);
                    break;

                default:
                    throw new FormatException($@"
                        Unsupported JSON token '{_reader.TokenType}' was found. 
                        Path '{_reader.Path}', 
                        line {_reader.LineNumber} 
                        position {_reader.LinePosition}.");
            }
        }

        private void VisitArray(JArray array)
        {
            for (int index = 0; index < array.Count; index++)
            {
                EnterContext(index.ToString());
                VisitToken(array[index]);
                ExitContext();
            }
        }

        private void VisitPrimitive(JToken data)
        {
            var key = _currentPath;

            if (_data.ContainsKey(key))
            {
                throw new FormatException($"A duplicate key '{key}' was found.");
            }
            _data[key] = data.ToString();
        }

        private void EnterContext(string context)
        {
            _context.Push(context);
            _currentPath = string.Join(Constants.KeyDelimiter, _context.Reverse());
        }

        private void ExitContext()
        {
            _context.Pop();
            _currentPath = string.Join(Constants.KeyDelimiter, _context.Reverse());
        }
    }
}
person Dani    schedule 09.02.2016