【问题标题】:ASP.NET CORE 1.0, AppSettings from another assemblyASP.NET CORE 1.0,来自另一个程序集的 AppSettings
【发布时间】:2018-12-24 13:46:15
【问题描述】:

我有一个应用程序分为两个项目:一个 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);。不知道这是否是一个好的解决方案,但它很简单。
  • 请看我对这个问题的回答。您不必编写任何代码就可以在另一个程序集中访问您的配置。 stackoverflow.com/questions/43080695/…

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


【解决方案1】:

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

是的,你可以实现IConfigurationProvider

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

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

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

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

【讨论】:

  • 如何嵌入 json 文件?
  • 这是一个不同的问题。我建议先用谷歌搜索这个新问题,然后如果你没有找到你需要的东西,请提出一个新的更具体的问题。
  • 我之所以这么说,是因为我用谷歌搜索过,现在你可以找到答案
  • @Dani 查看我提交的答案。在我看来,最好的解决方案是嵌入 JSON 文件。
【解决方案3】:

其他人可以纠正我,但我认为您要查找的内容不存在。 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 文件的命名空间和路径。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-02
      • 2016-09-23
      • 2012-09-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多