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

3

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

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

我希望将appsettings.json文件放在类库中,并从那里加载。这是否可行?我该怎么做?


你是想读整个appsettings.json文件,还是只是想在库中使用Web应用程序的appsetttings.json的某些配置值?对于后一种情况,我只需在库中创建一个配置类和该类的静态属性。在Startup.Configure中,我将此属性绑定到appsertings.json中的相应部分:Configuration.GetSection("MyLibrarySettings").Bind(MyLibrary.Settings.Default);。不知道这是否是一个好的解决方案,但它很简单。 - noox
请查看我在这个问题上的答案。您不应该编写任何代码来使您的配置在另一个程序集中可访问。https://dev59.com/_aDia4cB1Zd3GeqPDWtu - Razor
4个回答

5

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

1
cebru,这太棒了。正是我在寻找的东西。 - ThisGuy
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/file-providers?view=aspnetcore-2.1#embeddedfileprovider - Gaui

2

是的,您可以实现IConfigurationProvider

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

您还可以看到如何实现JsonConfigurationProvider

因此,我想您的实现可以在嵌入式JSON文件中内部使用Json提供程序代码。

然后,您还需要实现ConfigurationBuilder扩展来注册您的提供程序,类似于使用json配置的代码。


如何嵌入一个JSON文件? - Dani
这是一个不同的问题,而不是是否可能。我建议先谷歌这个新问题,如果找不到所需内容,请提出一个更具体的新问题。 - Joe Audette
我这么说仅仅是因为我搜索了一下,我知道你能找到答案。 - Joe Audette
@Dani请查看我的提交答案。在我看来,最好的解决方案是嵌入JSON文件。 - cebru

1

有人可以纠正我,但我认为你要找的东西不存在。 应用程序配置和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");

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

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());
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接