Handling strongly typed options and adding a config get

This commit is contained in:
Zoe Roux 2021-05-15 23:18:08 +02:00
parent 9ae846f88a
commit 1929994128
4 changed files with 132 additions and 8 deletions

View File

@ -98,6 +98,7 @@ namespace Kyoo.Authentication
services.Configure<PermissionOption>(_configuration.GetSection(PermissionOption.Path));
services.Configure<CertificateOption>(_configuration.GetSection(CertificateOption.Path));
services.Configure<AuthenticationOption>(_configuration.GetSection(AuthenticationOption.Path));
services.AddConfiguration<AuthenticationOption>(AuthenticationOption.Path);
List<Client> clients = new();

View File

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
namespace Kyoo.Models
{
/// <summary>
/// A class given information about a strongly typed configuration.
/// </summary>
public class ConfigurationReference
{
/// <summary>
/// The path of the resource (separated by ':')
/// </summary>
public string Path { get; }
/// <summary>
/// The type of the resource.
/// </summary>
public Type Type { get; }
/// <summary>
/// Create a new <see cref="ConfigurationReference"/> using a given path and type.
/// This method does not create sub configuration resources. Please see <see cref="CreateReference"/>
/// </summary>
/// <param name="path">The path of the resource (separated by ':' or "__")</param>
/// <param name="type">The type of the resource</param>
/// <seealso cref="CreateReference"/>
public ConfigurationReference(string path, Type type)
{
Path = path;
Type = type;
}
/// <summary>
/// Return the list of configuration reference a type has.
/// </summary>
/// <param name="path">
/// The base path of the type (separated by ':' or "__". If empty, it will start at root)
/// </param>
/// <param name="type">The type of the object</param>
/// <returns>The list of configuration reference a type has.</returns>
public static IEnumerable<ConfigurationReference> CreateReference(string path, [NotNull] Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
List<ConfigurationReference> ret = new()
{
new ConfigurationReference(path, type)
};
if (!type.IsClass || type.AssemblyQualifiedName?.StartsWith("System") == true)
return ret;
Type enumerable = Utility.GetGenericDefinition(type, typeof(IEnumerable<>));
Type dictionary = Utility.GetGenericDefinition(type, typeof(IDictionary<,>));
Type dictionaryKey = dictionary?.GetGenericArguments()[0];
if (dictionary != null && dictionaryKey == typeof(string))
ret.AddRange(CreateReference($"{path}:{type.Name}:*", dictionary.GetGenericArguments()[1]));
else if (dictionary != null && dictionaryKey == typeof(int))
ret.AddRange(CreateReference($"{path}:{type.Name}:", dictionary.GetGenericArguments()[1]));
else if (enumerable != null)
ret.AddRange(CreateReference($"{path}:{type.Name}:", enumerable.GetGenericArguments()[0]));
else
{
foreach (PropertyInfo child in type.GetProperties())
ret.AddRange(CreateReference($"{path}:{child.Name}", child.PropertyType));
}
return ret;
}
/// <summary>
/// Return the list of configuration reference a type has.
/// </summary>
/// <param name="path">
/// The base path of the type (separated by ':' or "__". If empty, it will start at root)
/// </param>
/// <typeparam name="T">The type of the object</typeparam>
/// <returns>The list of configuration reference a type has.</returns>
public static IEnumerable<ConfigurationReference> CreateReference<T>(string path)
{
return CreateReference(path, typeof(T));
}
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Linq;
using Kyoo.Controllers;
using Kyoo.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -64,10 +66,20 @@ namespace Kyoo
return services.AddRepository<T2>(lifetime);
}
public static IServiceCollection AddConfiguration<T>(this IServiceCollection services, IConfiguration config, string path)
/// <summary>
/// Add an editable configuration to the editable configuration list
/// </summary>
/// <param name="services">The service collection to edit</param>
/// <param name="path">The root path of the editable configuration. It should not be a nested type.</param>
/// <typeparam name="T">The type of the configuration</typeparam>
/// <returns>The given service collection is returned.</returns>
public static IServiceCollection AddConfiguration<T>(this IServiceCollection services, string path)
where T : class
{
services.Configure<T>(config.GetSection(path));
services.AddSingleton<ConfigReference>(new ConfigReference(path, typeof(T)));
if (services.Any(x => x.ServiceType == typeof(T)))
return services;
foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference<T>(path))
services.AddSingleton(confRef);
return services;
}
}

View File

@ -1,3 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models;
using Kyoo.Models.Permissions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
@ -15,15 +19,22 @@ namespace Kyoo.Api
/// <summary>
/// The configuration to retrieve and edit.
/// </summary>
private readonly IConfiguration _config;
private readonly IConfiguration _configuration;
/// <summary>
/// The strongly typed list of options
/// </summary>
private readonly Dictionary<string, Type> _references;
/// <summary>
/// Create a new <see cref="ConfigurationApi"/> using the given configuration.
/// </summary>
/// <param name="config">The configuration to use.</param>
public ConfigurationApi(IConfiguration config)
/// <param name="configuration">The configuration to use.</param>
/// <param name="references">The strongly typed option list.</param>
public ConfigurationApi(IConfiguration configuration, IEnumerable<ConfigurationReference> references)
{
_config = config;
_configuration = configuration;
_references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
@ -35,7 +46,16 @@ namespace Kyoo.Api
[Permission(nameof(ConfigurationApi), Kind.Admin)]
public ActionResult<object> GetConfiguration(string slug)
{
return _config[slug];
slug = slug.Replace("__", ":");
// TODO handle lists and dictionaries.
if (!_references.TryGetValue(slug, out Type type))
return NotFound();
object ret = _configuration.GetValue(type, slug);
if (ret != null)
return ret;
object option = Activator.CreateInstance(type);
_configuration.Bind(slug, option);
return option;
}
}
}