ASP.NET CORE 1.0,来自另一个程序集的 AppSettings

ASP.NET CORE 1.0, AppSettings from another assembly

我将一个应用程序分成两个项目:一个 Web 应用程序和一个 class 库。 Startup 仅在网络应用程序中:

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

我想把我的 appsettings.json 放在那个 class 库中,然后从那里加载。那可能吗?我该怎么做?

是的,你可以实施 IConfigurationProvider

有一个基础 class ConfigurationProvider,您可以从中继承然后覆盖所有虚拟方法。

您还可以看到 JsonConfigurationProvider 是如何实现的。

所以我猜你的实现可以在内部使用 Json 提供程序代码来对付嵌入的 json 文件。

那么您还需要实现 ConfigurationBuilder 扩展来注册您的提供商,类似于使用 json 配置的代码。

其他人可以纠正我,但我认为您要查找的内容不存在。 App Configs 和 AppSettings 文件由 运行ning 的应用程序在 运行 时间读取。

Class 库看不到任何特定于它自己的 AppSettings,因为当它在 运行 时 运行 时,它位于 运行ning 的文件夹中申请。

我能看到让您的 class 库包含 json 文件的唯一可能方法是将 json 文件作为嵌入资源。 例如:在解决方案中,select json 文件,并将其设置为 Embedded Resource 而不是 'content'.

问题变成了从程序集中获取嵌入式配置文件,然后加载。

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");

理论上,您应该能够从 class 库中指定一个类型,以帮助 c# 代码专门针对 class 库。然后您只需要提供嵌入式 json 文件的命名空间和路径。

这是我的解决方案,感谢 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());
        }
    }
}

我找到的最佳解决方案需要创建一个新的 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();
    }

    ...
}

通过为 class 库更新 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);
    }
}