我有一个应用程序分为两个项目:一个Web应用程序和一个类库。 Startup
只在Web应用程序中:
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
我希望将appsettings.json文件放在类库中,并从那里加载。这是否可行?我该怎么做?
我有一个应用程序分为两个项目:一个Web应用程序和一个类库。 Startup
只在Web应用程序中:
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
我希望将appsettings.json文件放在类库中,并从那里加载。这是否可行?我该怎么做?
我找到的最佳解决方案需要创建一个新的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();
}
}
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);
}
}
是的,您可以实现IConfigurationProvider
有一个基类ConfigurationProvider,您可以从中继承,然后覆盖所有虚拟方法。
您还可以看到如何实现JsonConfigurationProvider。
因此,我想您的实现可以在嵌入式JSON文件中内部使用Json提供程序代码。
然后,您还需要实现ConfigurationBuilder扩展来注册您的提供程序,类似于使用json配置的代码。
有人可以纠正我,但我认为你要找的东西不存在。 应用程序配置和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");
这是我的解决方案,感谢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());
}
}
}
Configuration.GetSection("MyLibrarySettings").Bind(MyLibrary.Settings.Default);
。不知道这是否是一个好的解决方案,但它很简单。 - noox