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

时间:2016-02-08 15:57:37

标签: c# asp.net-core appsettings asp.net-core-1.0

我有一个应用程序splittet分为两个项目:Web应用程序和类库。 Startup仅在网络应用程序中:

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

我希望我的appsettings.json在该类库中并从那里加载。那可能吗?我怎么能这样做?

4 个答案:

答案 0 :(得分:2)

是的,您可以实施IConfigurationProvider

您可以继承基类ConfigurationProvider,然后覆盖所有虚拟方法。

您还可以查看JsonConfigurationProvider的实施方式。

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

然后你也想要实现ConfigurationBuilder扩展来注册你的提供者,就像使用json config的代码一样。

答案 1 :(得分:2)

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

答案 2 :(得分:1)

其他人可以纠正我,但我不认为你在找什么。 App Configs和AppSettings文件在运行时由运行的应用程序读取。

类库无法看到任何特定于自身的AppSettings,因为它在运行时运行时,它位于正在运行的应用程序的文件夹中。

我可以看到让你的类库包含json文件的唯一可能方法是将json文件作为嵌入式资源。 例如:在解决方案中,选择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");

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

答案 3 :(得分:1)

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