Access appsettings from an abstract class used in my test scripts - json

Working with asp.net core 2.0
I have created a BD test project.
I have an abstract base class structured like this:
[ExcludeFromCodeCoverage]
[Category("Integration")]
public abstract class BaseIntegrationContext : BaseIntegrationSetUp
{
protected MyDataBaseContext Context;
private IWebHostBuilder _webHostBuilder;
protected override void FixtureSetUp()
{
base.FixtureSetUp();
WebSetUp();
DbSetUp();
}
private void WebSetUp()
{
_webHostBuilder = new WebHostBuilder()
.UseStartup<Startup>();
}
private void DbSetUp()
{
base.FixtureSetUp();
//options
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var options = new DbContextOptionsBuilder<InformedWorkerDbContext>()
.UseSqlServer(config.GetConnectionString("DefaultConnection"))
.Options;
Context = new MyDataBaseContext(options);
}
}
unless I copy and add the appsettings.json to this test project I will obviously get the error that the json file cannot be found.
What is the accepted way to access appsettings.json from withing an abstract base class that use in a test project?
I have tried adding an entity model called ServiceSettings that maps to the json file in my web project:
public class ServiceSettings
{
public string DatabaseServerConnection { get; set; }
}
which i instantiate in startup.cs:
services.Configure<ServiceSettings>(Configuration.GetSection("ServiceSettings"));
My Json file looks like this:
{
"ServiceSettings": {
"DatabaseServerConnection": "Server=localhost;Initial,Catalog=InformedWorker;Integrated Security=True;MultipleActiveResultSets=true"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Information"
},
"Console": {
"LogLevel": {
"Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
"Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
"Microsoft.AspNetCore.Mvc.Razor": "Error",
"Default": "Information"
}
}
}
}
and in my abstract class I do this:
IOptions<ServiceSettings> myOptions = Options.Create(new ServiceSettings()
{
});
but typing myOptions the intellisense only gives me 'Value' to work with..?

unless I copy and add the appsettings.json to this test project I will obviously get the error that the json file cannot be found.
This is cause you don't specify file location in
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
.AddJsonFile registers a JSON FileProvider and path parameter ("appsettings.json" in your case) is a Path relative to the base path stored in ConfigurationBuilder.Properties. So the default base path is null and a file is expected in current working directory.
To set base path you may use .SetBasePath(<basePath>) extension method
public static IConfigurationBuilder SetBasePath(this IConfigurationBuilder builder, string basePath);
where basePath: The absolute path of file-based providers.

Related

How to use DependencyResolver in Net48 selfhosted application?

I have gotten a task that contains creating a .Net 4.8 application that contains a "HttpSelfHostServer".
I'm stuck in the quest of assigning "IServiceCollection services" to config.DependencyResolver (of type System.Web.Http.Dependencies.IDependencyResolver)
I would really like not to use autofac or other frameworks, but all guids I can find are pointing toward these frameworks. Isn't Microsoft providing a way through?
I just had to solve the same issue. This is how i did it:
First I created a new facade class to map the IServiceCollection from the host builder to the interface HttpSelfHostConfiguration supports:
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;
using Microsoft.Extensions.DependencyInjection;
namespace IntegrationReceiver.WebApi
{
public class HttpSelfHostDependencyResolver : IDependencyResolver
{
private readonly IServiceProvider sp;
private readonly IServiceScope scope;
public HttpSelfHostDependencyResolver(IServiceProvider sp)
{
this.sp = sp;
this.scope = null;
}
public HttpSelfHostDependencyResolver(IServiceScope scope)
{
this.sp = scope.ServiceProvider;
this.scope = scope;
}
public IDependencyScope BeginScope() => new HttpSelfHostDependencyResolver(sp.CreateScope());
public void Dispose() => scope?.Dispose();
public object GetService(Type serviceType) => sp.GetService(serviceType);
public IEnumerable<object> GetServices(Type serviceType) => sp.GetServices(serviceType);
}
}
This required me to get the latest NuGet package Microsoft.Extensions.DependencyInjection.Abstractions according to an answer here: How do I see all services that a .NET IServiceProvider can provide?
I then registered my HttpSelfHostServer in the service provider with this code:
services.AddSingleton(sp => new HttpSelfHostDependencyResolver(sp));
services.AddSingleton(sp =>
{
//Starting the HttpSelfHostServer with user-level permissions requires to first run a command like
// netsh http add urlacl url=http://+:8080/ user=[DOMAINNAME]\[USERNAME]
var config = new HttpSelfHostConfiguration("http://localhost:8080");
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.DependencyResolver = sp.GetRequiredService<HttpSelfHostDependencyResolver>();
return new HttpSelfHostServer(config);
});
And finally, to find my ApiController, I had to register that too in the service provider. I did that simply with:
services.AddScoped<HealthCheckController>();
For brewity, I'm just including my api controller below to illustrate how it now gets its dependencies:
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
namespace IntegrationReceiver.WebApi
{
public class HealthCheckController : ApiController
{
private readonly ServiceBusRunner serviceBusRunner;
public HealthCheckController(ServiceBusRunner serviceBusRunner)
{
this.serviceBusRunner = serviceBusRunner;
}
[HttpGet]
public async Task<HttpResponseMessage> Get()
{
var response = new
{
serviceBusRunner.RunningTasks,
serviceBusRunner.MaxRunningTasks
};
return await Json(response)
.ExecuteAsync(System.Threading.CancellationToken.None);
}
}
}
This is a pretty dumb-down implementation but works for me until I can upgrade this code to net5.
I hope it helps you too!

How to inject settings from appsettings.json to an ILoggerProvider?

I'm implementing a custom ILogger<T> due to some special requirements, and one of those requirements is to have a section, inside the Logging section in the project's appsettings.json, with configuration values for that logger. Question is, what's the right way (or a good way) to inject those settings in the logger? I presume I should start by injecting those settings in the corresponding ILoggerProvider and have the provider instantiate a logger with those settings, but I'm stumped on how to properly inject those values in the provider.
So far I have this in Program.cs:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging((context, logging) =>
{
logging.ClearProviders();
logging.AddConfiguration(context.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddProvider(new CustomLoggerProvider());
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
, the relevant section in appsettings.json is:
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"CustomLogger": {
"Level": "Error",
"Url": "http://[URL]"
}
},
, and the provider implementation is as follows:
[ProviderAlias("CustomLogger")]
public class CustomLoggerProvider : ILoggerProvider
{
private readonly Dictionary<string, CustomLogger> _loggers;
private readonly LogLevel _level;
private bool _already_disposed;
public LogLevel Level
{
get => _level;
}
public CustomLoggerProvider()
{
//Here's where I don't know what should I do to inject the proper config values
_level = LogLevel.Error;
}
public ILogger CreateLogger(string categoryName)
{
if (_loggers.ContainsKey(categoryName))
{
return _loggers[categoryName];
}
var l = new CustomLogger(this);
_loggers.Add(categoryName, l);
return l;
}
// Rest of implementation omitted for simplicity
}
I found an acceptable answer and I'll post it here, in case someone needs it.
Steps are:
Implement your ILoggerProvider and your ILogger. The examples on Microsoft's page are a good guide.
Have your ILoggerProvider implementation accept the needed settings on its constructor (via IOptions<T> or simple parameters; I'm using an options object called MyCustomLoggerSettings).
Register your ILoggerProvider in Startup.ConfigureServices() as a singleton, like this (let's say my implementation is called MyCustomLoggerProvider):
services.AddSingleton<ILoggerProvider>(p =>
{
var options = p.GetService<IOptions<MyCustomLoggerSettings>>();
return new MyCustomLoggerProvider(options);
});
Done.
As a bonus, in this way you can pass any other dependencies to this object (including objects registered on the service collection; the use of method overloads in services that accept an anonymous function give you the service collection as a parameter, and there you can use GetService<T> to resolve any dependencies.

Reading appsettings.json values in asp.netcore

I am trying to read some values from my appsettings.json file for Basic Authentication. Locally the code works fine but I'm confused on How can I do it when I am deploying my application live.
My appsettings.json file look like this
appsettings.json
{
"BasicAuth": {
"UserName": "admin",
"Password": "1234567789"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
The code that I am using for my middleware looks like this
startup.cs
public void ConfigureServices(IServiceCollection
{
services.AddHttpClient("MyClient", client => {
client.BaseAddress = new Uri("http://xx.xx.xx.xxx/1/#/nutch/query");
client.DefaultRequestHeaders.Add("username", "admin");
client.DefaultRequestHeaders.Add("password", "1234567789");
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<BasicAuthMiddleware>("http://xx.xx.xx.xxx/abc/#/1/query/");
}
And My Middleware Class looks like this
BasicAuthMiddleware.cs
As you can see I have to send the whole path to my appsettings.json file in .AddJsonFile
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace searchEngineTesting.Controllers
{
public class BasicAuthMiddleware
{
private readonly RequestDelegate _next;
private readonly string _realm;
public BasicAuthMiddleware(RequestDelegate next, string realm)
{
_next = next;
_realm = realm;
}
public async Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic "))
{
// Get the encoded username and password
var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();
// Decode from Base64 to string
var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));
// Split username and password
var username = decodedUsernamePassword.Split(':', 2)[0];
var password = decodedUsernamePassword.Split(':', 2)[1];
// Check if login is correct
if (IsAuthorized(username, password))
{
await _next.Invoke(context);
return;
}
}
// Return authentication type (causes browser to show login dialog)
context.Response.Headers["WWW-Authenticate"] = "Basic";
// Add realm if it is not null
if (!string.IsNullOrWhiteSpace(_realm))
{
context.Response.Headers["WWW-Authenticate"] += $" realm=\"{_realm}\"";
}
// Return unauthorized
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
// Make your own implementation of this
public bool IsAuthorized(string username, string password)
{
//IConfiguration config = new ConfigurationBuilder()
// .AddJsonFile("appsettings.json", true, true)
// .Build();
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("C:/programfiles/path/to/appsettings.json")
.Build();
var basicAuthUserName = config["BasicAuth:UserName"];
var basicAuthPassword = config["BasicAuth:Password"];
// Check that username and password are correct
return username.Equals(basicAuthUserName, StringComparison.InvariantCultureIgnoreCase)
&& password.Equals(basicAuthPassword);
}
}
}
I have tried only by giving name appsettings.json rather than giving the whole path, but it doesn't work, and the exception occurs of cannot find appsettings.json file. How can I give it a generalize path, so that I don't have to change it again and again, and I can read the values.
You can inject IConfiguration in your middleware constructor. No need to use the config builder.
private readonly RequestDelegate _next;
private readonly string _realm;
private readonly IConfiguration _configuration;
public BasicAuthMiddleware(RequestDelegate next, string realm, IConfiguration configuration)
{
_next = next;
_realm = realm;
_configuration = configuration;
}

Adding aspnet-api-versioning prevents UrlHelper from generating Controller API routes within a Razor Pages request

I can create a file->new aspnetcore API project and use the IUrlHelper to generate a route by name without any issues.
[Route("api/[controller]")]
public class ValuesController : Controller
{
public const string GetValues = "GetValues";
public const string GetValuesById = "GetValuesById";
public static string[] Values = new[] { "value1", "value2", "value3", };
// GET api/values
[HttpGet(Name = GetValues)]
public IEnumerable<object> Get()
{
var result = new List<object>();
for(int index = 0; index < Values.Length - 1; index++)
{
string routeForElement = this.Url.RouteUrl(GetValuesById, new { Id = index });
result.Add(new { Value = Values[index], Route = routeForElement });
}
return result;
}
// GET api/values/5
[HttpGet("{id}", Name = GetValuesById)]
public string Get(int id)
{
if (id > (Values.Length - 1))
{
return "Invalid Id";
}
return Values[id];
}
}
When the response is sent back, I correctly have the routes that I've created:
[
{
"value": "value1",
"route": "/api/v1/Values/0"
},
{
"value": "value2",
"route": "/api/v1/Values/1"
},
{
"value": "value3",
"route": "/api/v1/Values/2"
}
]
I can then use the Visual Studio Scaffolding to create a Razor Page and continue to generate the same route without any issues within my Razor Page:
Model
public class IndexModel : PageModel
{
public List<string> Routes { get; set; } = new List<string>();
public void OnGet()
{
for (int index = 0; index < ValuesController.Values.Length; index++)
{
string routeForElement = this.Url.RouteUrl(ValuesController.GetValuesById, new { Id = index });
Routes.Add(routeForElement);
}
}
}
Page
#page
#model UrlHelperWithPages.Pages.IndexModel
#foreach(string route in Model.Routes)
{
<h4>#route</h4>
}
This renders the routes without issue.
If I add the aspnet-api-versioning nuget package and configure it's services:
services.AddApiVersioning();
My API controller continues to work with the following modification. Any request that is destined for this controller has the routes generated correctly.
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ValuesController : Controller
However the Razor Pages stops working when we try to generate a route from within a Razor Pages request. The RouteUrl method now returns null. I've tried updating the route data provided to the RouteUrl method so that I pass a hard-coded version (for testing) and it doesn't work either.
new { version = 1, Id = index }
Is there any configuration that needs to happen on the api versioning package to support pages? We have razor pages that we want to generate API routes for rendering in the page, but it doesn't seem to work.

SignalR 2.0.0 beta2 IJsonSerializer extensibility

I want to add some custom serialization logic so that the converted json contains camel case properties.
For that reason i tried to replace the default IJsonSerializer with one the i found in this link:
https://github.com/SignalR/SignalR/issues/500
However there seems to be a problem. More specifically, the JsonNetSerializer and IJsonSerializer classes do not exist in any of the signalR assemblies. Is there any change that happened to the recent version of signalR in that respect?
Just to clarify this a bit, as of SignalR 2 you can't replace the serializer with one that isn't from from JSON.NET. However, the JSON.NET serializer used by SinglR can be created and set using the DependacyResolver.
Here's an example where a new JsonSerializer is created to handle reference loops:
protected void Application_Start()
{
var serializerSettings = new JsonSerializerSettings();
serializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
serializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
var serializer = JsonSerializer.Create(serializerSettings);
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => serializer);
}
In SignalR 2.0 you can't replace the JsonSerializer, there's no more IJsonSerializer abstraction. It's always JSON.NET.
Here's an example of overriding the SignalR Dependency Resolver using StructureMap.
In this particular example, I'm converting to camelCase properties and converting enums as strings.
During startup:
Microsoft.AspNet.SignalR.GlobalHost.DependencyResolver = new StructureMapSignalRDependencyResolver();
Here's the class:
public class StructureMapSignalRDependencyResolver : Microsoft.AspNet.SignalR.DefaultDependencyResolver
{
public override object GetService(Type serviceType)
{
object service;
if (!serviceType.IsAbstract && !serviceType.IsInterface && serviceType.IsClass)
{
// Concrete type resolution
service = StructureMap.ObjectFactory.GetInstance(serviceType);
}
else
{
// Other type resolution with base fallback
service = StructureMap.ObjectFactory.TryGetInstance(serviceType) ?? base.GetService(serviceType);
}
return service;
}
public override IEnumerable<object> GetServices(Type serviceType)
{
var objects = StructureMap.ObjectFactory.GetAllInstances(serviceType).Cast<object>();
return objects.Concat(base.GetServices(serviceType));
}
}
And StructureMap was setup with:
ObjectFactory.Configure(c =>
{
c.Scan(a =>
{
// scan the assembly that SignalR is referenced by
a.AssemblyContainingType<AppHost>();
a.WithDefaultConventions();
});
c.For<Newtonsoft.Json.JsonSerializer>()
.Singleton()
.Use(new Newtonsoft.Json.JsonSerializer
{
ContractResolver = new SignalRContractResolver(),
Converters = { new Newtonsoft.Json.Converters.StringEnumConverter() }
});
});
Here is the Contract Resolver:
public class SignalRContractResolver : Newtonsoft.Json.Serialization.IContractResolver
{
private readonly Assembly _assembly;
private readonly Newtonsoft.Json.Serialization.IContractResolver _camelCaseContractResolver;
private readonly Newtonsoft.Json.Serialization.IContractResolver _defaultContractSerializer;
public SignalRContractResolver()
{
_defaultContractSerializer = new Newtonsoft.Json.Serialization.DefaultContractResolver();
_camelCaseContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
_assembly = typeof(Connection).Assembly;
}
public Newtonsoft.Json.Serialization.JsonContract ResolveContract(Type type)
{
if (type.Assembly.Equals(_assembly))
{
return _defaultContractSerializer.ResolveContract(type);
}
return _camelCaseContractResolver.ResolveContract(type);
}
}