How to read Appsettings.Json in .net 6 Web API with F# - asp.net-web-api

I want to read a simple connection string from the appsettings.json file in F#
"ConnectionStrings": {
"myConnectionString": "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;"
}
In C# I could use the read the connection string with an object from a class implementing the Iconfiguration provided in the constructor. But in the F# web API with .net 6, I couldn't find anything that made me read the connection string section or an other key in from the AppSettings.json file
Here is an example how I could read from the Appsettings.json in C# .net 6 Web API. This is what I want to do.
public class TestModel : PageModel
{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration _configuration;
public TestModel(IConfiguration configuration)
{
_configuration = configuration;
}
public void Connect()
{
var connectionString = _config.GetConnectionString("myConnectionString");
// Do Something with the string
}
}

The F# implementation of the Web API project is very similar to the C# one. In order to use the configuration in one of your classes you just need to inject the IConfiguration parameter to the constructor of your class. The default bootstrap code takes care of setting up the dependency injection for the configuration framework.
I created a sample Web API project for F# and injected the IConfiguration to the controller:
namespace WebApiTest.Controllers
open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.Logging
open Microsoft.Extensions.Logging
open WebApiTest
open Microsoft.Extensions.Configuration
[<ApiController>]
[<Route("[controller]")>]
type WeatherForecastController (logger : ILogger<WeatherForecastController>, configuration: IConfiguration) =
inherit ControllerBase()
let summaries =
[|
"Freezing"
"Bracing"
"Chilly"
"Cool"
"Mild"
"Warm"
"Balmy"
"Hot"
"Sweltering"
"Scorching"
|]
[<HttpGet>]
member _.Get() =
let cstr = configuration.GetConnectionString("MyConnectionString")
logger.LogInformation($"Connection string: {cstr}")
let rng = System.Random()
[|
for index in 0..4 ->
{ Date = DateTime.Now.AddDays(float index)
TemperatureC = rng.Next(-20,55)
Summary = summaries.[rng.Next(summaries.Length)] }
|]
As you see, the configuration parameter injected in the constructor is accessible as a private member variable in the Get() method. I just log the connection string to illustrate that the code works as expected.
Here's my appsettings.json:
{
"ConnectionStrings": {
"MyConnectionString": "SampleConnectionString"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

Related

net6 minimal web API override settings for test

Program.cs
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
string foo = builder.Configuration.GetValue<string>("foo"); // Is null. Shoudn't be.
public partial class Program{}
Test project
public class MyWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((context, configBuilder) =>
{
configBuilder.AddInMemoryCollection(
(new Dictionary<string, string?>
{
["foo"] = "bar"
}).AsEnumerable());
});
}
}
public class Test
{
private readonly HttpClient _client;
public Test(MyWebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder().CreateClient();
But the new settings are never added -- when I debug the test foo is always null.
I don't see how the settings can added, either, because Program creates a new builder that never goes anywhere near the WebApplicationFactory.
I think this might be something to do with my Program needing to read settings in order to configure services, but the settings not being updated by the test system until after the services have been configured?
Can it be made to work?

ASP.NET Core MVC - Connection strings and Data Libraries

I'll prefix this and say I'm new to .net core. I've been taken by surprise when trying to access a connection string I have in my appsettings.json file.
My solution has 2 projects, an MVC project and a Data Library. In the past with standard .net I was able to add a connection string into my web.config and access it from my data library like so:
namespace DataLibrary.DataAccess
{
public static class SqlDataAccess
{
public static string GetConnectionString(string myConnection = "DemoDB")
{
return ConfigurationManager.ConnectionStrings[myConnection].Connectionstring;
}
}
}
The "DemoDB" is the name of the connection string in the web.config. This doesn't work with .net core. I was wondering if someone could get me a step by step, talk to me like I'm 5, explanation of how I would get the connection string from my appsettings.json file in my MVC project into my DataLibrary?
Thanks in advance!
As far as I know, if you want to use data library in the MVC project, you should register it as a service in the main project.
More details, you could refer to below codes:
public static class ServiceCollectionExtensions
{
public static IServiceCollection RegisterDataService(this IServiceCollection serviceDescriptors,IConfiguration configure) {
serviceDescriptors.AddDbContext<TestDBContext>(o => o.UseSqlServer(configure.GetConnectionString("ConnectionStringanme")));
return serviceDescriptors;
}
}
Then in the main MVC project's startup.cs method, you could use below codes:
services.RegisterDataServices(Configuration)
At last, you could add connection string into appsettings.json as below:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-ASPNETCoreIdentity-AE5A1558-8D92-4DA4-9A53-DDFB0BA30404;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
More details, you could refer to this article.

Read an app setting in my .Net 5 application without using DI

I created an MVC application using .Net 5.
Then I created a custom Attribute should read some settings from appsettings.json.
Here a working solution:
public class MyCustomAttribute : Attribute
{
public MyCustomAttribute(string key)
: base()
{
IConfiguration conf = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var value = conf.GetValue<string>(key);
...
}
...
}
It works, but I do not think it is the correct solution. I try to explain why.
In the Program.cs, beforse the host is builded, the startup class is instantiated:
var builder = Host.CreateDefaultBuilder(args); builder
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
If I inject the IConfiguration to the stratup, I can get the appsettings.json values:
I think that from now, I have the appsettings.json and conseguently the configuration in memory.
So it seems really strange to me that in my custom attribute I must read the setting from the file again!
The question is: How can I read the in-memory configuration inside my custom attribute?
Is my consideration correct?
Thank you.
EDIT
I have found another solution, but I still does not like it:
I have modified the startup.cs ctor in this way:
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
this._environment = environment;
this._configuration = configuration;
AppDomain.CurrentDomain.SetData("conf", this._configuration);
}
And the ctor of MyCustomAttribute in this way:
public class MyCustomAttribute : Attribute
{
public MyCustomAttribute(string key)
: base()
{
var conf = AppDomain.CurrentDomain.GetData("conf") as IConfiguration;
var value = conf.GetValue<string>(key);
...
}
...
}
Also this solution works. But I hope something out-of-the-box in .Net 5 exists. I would expect the normal behavior when the configuration is read, would be something similar to my solution.

Issue with Simple Injector while using with Web API

I am having issue using Simple Injector with WebAPI project that gets created default with VS 2015.
I am having the AccountController having the below constructor
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager,
ISecureDataFormat<AuthenticationTicket> accessTokenFormat)
{
UserManager = userManager;
AccessTokenFormat = accessTokenFormat;
}
In order to register these I used the below code in Simple Injector
// Create the container.
var apiContainer = new Container();
apiContainer.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
apiContainer.Options.ConstructorResolutionBehavior = new ConstructorBehavior();
//register the classes that we are going to use for dependency injection
apiContainer.Register<IUserStore<ApplicationUser>>(() => new UserStore<ApplicationUser>(new ApplicationDbContext()),Lifestyle.Scoped);
apiContainer.Register<IDataProtector>(() => new Microsoft.Owin.Security.DataProtection.DpapiDataProtectionProvider().Create("ASP.NET Identity"),Lifestyle.Transient);
apiContainer.Register<ISecureDataFormat<AuthenticationTicket>, SecureDataFormat<AuthenticationTicket>>(Lifestyle.Transient);
apiContainer.Register<ITextEncoder, Base64UrlTextEncoder>(Lifestyle.Scoped);
apiContainer.Register<IDataSerializer<AuthenticationTicket>, TicketSerializer>(Lifestyle.Scoped);
//apiContainer.RegisterCommonClasses();
//register the webapi controller
apiContainer.RegisterWebApiControllers(configuration);
but after this I am getting the warning message that says
[Disposable Transient Component] ApplicationUserManager is registered as transient, but implements IDisposable.
Can someone Please help me with this how to resolve this ? With Default Web api project with VS 2015 it adds Account controller and that use ApplicationUserManager and has below details
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
Another issue I am getting as below
The constructor of type HttpConfiguration contains the parameter with name 'routes' and type HttpRouteCollection that is not registered. Please ensure HttpRouteCollection is registered, or change the constructor of HttpConfiguration.
This is with the HelpController as it uses the below details:
public HelpController()
: this(GlobalConfiguration.Configuration)
{
}
public HelpController(HttpConfiguration config)
{
Configuration = config;
}

MEF and AssemblyCatalog / AggregateCatalog

I have a simple console app as below (un-relevant code removed for simplicity)
[ImportMany(typeof(ILogger))]
public IEnumerable<ILogger> _loggers {get;set;}
public interface ILogger
{
void Write(string message);
}
[Export(typeof(ILogger))]
public class ConsoleLogger : ILogger
{
public void Write(string message)
{
Console.WriteLine(message);
}
}
[Export(typeof(ILogger))]
public class DebugLogger : ILogger
{
public void Write(string message)
{
Debug.Print(message);
}
}
The code that initialize the catalog is below
(1) var catalog = new AggregateCatalog();
(2) catalog.Catalogs.Add(new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory));
(3) //var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);
If the catalog is initialized trough lines 1-2, nothing got loaded into _logger
If the catalog is initialized trough line 3, both logger got loaded into _logger
What's the issue with AggregateCatalog approach?
Thanks
It should work the way you are using it.
However, on line 2 you are creating a DirectoryCatalog, and on line 3 an AssemblyCatalog. Does it work as expected if you change line two into
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
I found the problem.
It seems DirectoryCatalog(path) search only in DLL by default, and my test program was a console application. And the exports were in EXE (not DLL) so they weren't loaded.
On the other hand, AssemblyCatalog(Assembly.GetExecutingAssembly()), obviously loaded exports from current assembly(which is the EXE).
The solution is to use the other constructor of DirectoryCatalog(path, searchPattern) , and use "*.*" for second param. And it works

Resources