C
C#ā€¢2y ago
kosh

āœ… Accessing the appsettings.json that resides in a ASP.NET Core 6 project from another project

Do I need to always pass that down as a constructor or is there any other way to do it. I need the database connection strings for my DAL project
43 Replies
Pobiega
Pobiegaā€¢2y ago
your DAL project shouldn't access it, it should be fed in somehow your APi project is the "core" of your application that assembles all the other parts
kosh
koshā€¢2y ago
I understand the second part, but the first part is what I'm hung up on. Do I need to create a new class like AppSettingsContexts that gets populated in the API project that then goes into the DAL? I'm trying to understand what the best practices are
Pobiega
Pobiegaā€¢2y ago
why do you feel that you need the connection string in your DAL? also, are you using something like EF or is this just SqlConnection or similar?
kosh
koshā€¢2y ago
I'm not using EF, so I guess that's part of why it's so hard for me to wrap my head around this. SqlConnection is what I want to use
Pobiega
Pobiegaā€¢2y ago
Well, how EF works internally is that you configure a DbContextOptions object in your API when you register EF and then when the context needs to use the connstr, it gets it from the options object
kosh
koshā€¢2y ago
how does that make it's way to another project, via dependency injection? so on the controller level you need to pass that context down?
Pobiega
Pobiegaā€¢2y ago
Yep, DI not on controller level, as the DbContext is always injected so it gets it from the service provider
Angius
Angiusā€¢2y ago
The connection string gets passed as the context gets registered in the DI container
Pobiega
Pobiegaā€¢2y ago
so if your project uses DI, you could just register a config object and inject that into your DAL where you need the connstr
kosh
koshā€¢2y ago
alright cool. so in my API program.cs file I have this configure service method, this is what it looks like currently
private static void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen();
services.AddScoped<ILookupRepository, LookupRepository>();

services.AddScoped<IMarkerRepository, MarkerRepository>();
services.AddScoped<IMarkerService, MarkerService>();

services.AddScoped<IDestinationRepository, DestinationRepository>();
services.AddScoped<IDestinationService, DestinationService>();

services.AddScoped<IFuelDataRepository, FuelDataRepository>();
services.AddScoped<IFuelDataService, FuelDataService>();

services.AddScoped<IPathfindingRepository, PathfindingRepository>();
services.AddScoped<IPathfindingService, PathfindingService>();

services.AddScoped<IEnvironmentRepository, EnvironmentRepository>();
services.AddScoped<IEnvironmentService, EnvironmentService>();


}
private static void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen();
services.AddScoped<ILookupRepository, LookupRepository>();

services.AddScoped<IMarkerRepository, MarkerRepository>();
services.AddScoped<IMarkerService, MarkerService>();

services.AddScoped<IDestinationRepository, DestinationRepository>();
services.AddScoped<IDestinationService, DestinationService>();

services.AddScoped<IFuelDataRepository, FuelDataRepository>();
services.AddScoped<IFuelDataService, FuelDataService>();

services.AddScoped<IPathfindingRepository, PathfindingRepository>();
services.AddScoped<IPathfindingService, PathfindingService>();

services.AddScoped<IEnvironmentRepository, EnvironmentRepository>();
services.AddScoped<IEnvironmentService, EnvironmentService>();


}
Pobiega
Pobiegaā€¢2y ago
yupyup and its your repositories that need the connection string, ye?
kosh
koshā€¢2y ago
this is how I've laid out my project
Pobiega
Pobiegaā€¢2y ago
yupyup
kosh
koshā€¢2y ago
this is one of my controllers
using Microsoft.AspNetCore.Mvc;
using SEEFARE.Core.Responses.Markers;
using SEEFARE.Services.Interfaces;
using System.Text.Json.Nodes;

namespace SEEFARE.Controllers
{
[ApiController]
[Route("[controller]")]
public class MarkersController : ControllerBase
{
private readonly IMarkerService _service;
private readonly ILogger<MarkersController> _logger;
public MarkersController(IMarkerService service, ILogger<MarkersController> logger)
{
_service = service;
_logger = logger;
}

[HttpGet]
public async Task<IActionResult> Index()
{

GetAllMarkersResponse ret = await _service.GetAllMarkers();
return new JsonResult(ret);
}
}
}
using Microsoft.AspNetCore.Mvc;
using SEEFARE.Core.Responses.Markers;
using SEEFARE.Services.Interfaces;
using System.Text.Json.Nodes;

namespace SEEFARE.Controllers
{
[ApiController]
[Route("[controller]")]
public class MarkersController : ControllerBase
{
private readonly IMarkerService _service;
private readonly ILogger<MarkersController> _logger;
public MarkersController(IMarkerService service, ILogger<MarkersController> logger)
{
_service = service;
_logger = logger;
}

[HttpGet]
public async Task<IActionResult> Index()
{

GetAllMarkersResponse ret = await _service.GetAllMarkers();
return new JsonResult(ret);
}
}
}
so in the API's program.cs config method, I would need to register a new class that I'll call something like AppContext, and populate it with the values from appsettings, then use Di on the controller level to the services, who would then pass that down to the DAL? man this is rocking my brain right now This is my currently flow: API -> Services -> DAL
Pobiega
Pobiegaā€¢2y ago
public record MyDatabaseOptions(string ConnectionString);
...
services.AddSingleton(new MyDatabaseOptions(connectionString));
...
public class SomeRepository
{
private readonly SqlConnection _connection;

public SomeRepository(MyDatabaseOptions options)
{
_connection = new SqlConnection(options.ConnectionString);
}
...
}
public record MyDatabaseOptions(string ConnectionString);
...
services.AddSingleton(new MyDatabaseOptions(connectionString));
...
public class SomeRepository
{
private readonly SqlConnection _connection;

public SomeRepository(MyDatabaseOptions options)
{
_connection = new SqlConnection(options.ConnectionString);
}
...
}
tada
public static class ServiceCollectionExtensions
{
public static void AddDataAccess(this IServiceCollection services, string connectionString)
{
services.AddSingleton(new MyDatabaseOptions(connectionString));
}
}
public static class ServiceCollectionExtensions
{
public static void AddDataAccess(this IServiceCollection services, string connectionString)
{
services.AddSingleton(new MyDatabaseOptions(connectionString));
}
}
if you wanna be fancy šŸ™‚
Angius
Angiusā€¢2y ago
I'd have some DatabaseContext that wraps Dapper or whatever you're using (hopefully Dapper and not ADO), register that, and inject it into repositories/services Or inject just the options, yeah
kosh
koshā€¢2y ago
It's a fresh project and I'm relatively new to C# api's. I'm pretty sure I'd be using ADO (I've never used Dapper before) but I've got experience with ADO
Angius
Angiusā€¢2y ago
My condolences
kosh
koshā€¢2y ago
thanks for the help guys. last question @Pobiega , would I call ServiceCollectionExtensions.AddDataAccess from the ConfigureServices(IServiceCollection services) method? I'm looking at dapper right now, it looks pretty cool
Angius
Angiusā€¢2y ago
Yep Say goodbye to manually mapping some data reader and other bullshit in a loop Just a generic method, a class, bada bing bada boom, done
kosh
koshā€¢2y ago
I see why it's cool
var cs = @"Server=localhost\SQLEXPRESS;Database=testdb;Trusted_Connection=True;";

using var con = new SqlConnection(cs);
con.Open();

var cars = con.Query<Car>("SELECT * FROM cars").ToList();

cars.ForEach(car => Console.WriteLine(car));
var cs = @"Server=localhost\SQLEXPRESS;Database=testdb;Trusted_Connection=True;";

using var con = new SqlConnection(cs);
con.Open();

var cars = con.Query<Car>("SELECT * FROM cars").ToList();

cars.ForEach(car => Console.WriteLine(car));
haha yeah honestly it's so tedious
Pobiega
Pobiegaā€¢2y ago
services.AddDataAccess("your string here"); extension methods my man very cool stuff you could even register your repos in there its pretty common for "partial" applications like this to contain their own DI setup locally in an extension and the API just calls all those extensions
Angius
Angiusā€¢2y ago
Yeah, that .AddDataAccess() could also be registering the repositories And everything else from your DAL
Pobiega
Pobiegaā€¢2y ago
services.AddBusinessLogic();
services.AddDataAccess("connstr");
services.AddSecretDoomsdayDevice();`
services.AddBusinessLogic();
services.AddDataAccess("connstr");
services.AddSecretDoomsdayDevice();`
kosh
koshā€¢2y ago
if I wanted to pull the value of my connstring from my appsettings.json, how would I do that within the .AddDataAccess method? that's where my confusion lies as well, how does it know between local/production environments I want to host this API once I get it all flushed out so that's why I was having a hard time wrapping my head aroundit all
Jayy
Jayyā€¢2y ago
Asp puts it in the env object when it starts up You can access this by injecting IEnvironment in ur classes However, app settings should not be used for connection strings Full stop
kosh
koshā€¢2y ago
oh god where else do I include the two connection strings then? one for local and one for the live server
Pobiega
Pobiegaā€¢2y ago
your ConfigureServices method probably has access to the IConfiguration, so get it from there?
Angius
Angiusā€¢2y ago
Far as env vs dev goes, you can add many config sources that make up a hierarchy. If conn string isn't found in appsettings, look for it in env vars. If it's not there, look for it in keyvault, etc
Jayy
Jayyā€¢2y ago
Ohh ya sry IConfiguration im dumb Local dev is done using user-secrets production is done via keyvault or environment variables App settings is not for secrets Secrets should not be in appsettings
Jayy
Jayyā€¢2y ago
Jayy
Jayyā€¢2y ago
IConfiguration is what wires all these different providers up together So ur app code doesn't care if it's in prod or dev mode
kosh
koshā€¢2y ago
thanks for that. Reading the docs right now, but I currently don't see this directory/file. Do I need to create it? %APPDATA%\Microsoft\UserSecrets<user_secrets_id>\secrets.json specifically the UserSecrets directory
Angius
Angiusā€¢2y ago
Use the secret manager
Pobiega
Pobiegaā€¢2y ago
use dotnet user-secrets
Angius
Angiusā€¢2y ago
dotnet user-secrets actually šŸ¤“
Pobiega
Pobiegaā€¢2y ago
šŸ˜„
kosh
koshā€¢2y ago
yeah I ran user-secrets init and I got a GUID back, which is good lol alright sweet I see this here
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>356d59b2-7074-4347-8003-dc72d2bc3e57</UserSecretsId>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>356d59b2-7074-4347-8003-dc72d2bc3e57</UserSecretsId>
</PropertyGroup>
C:\Users\jonat\AppData\Local\Microsoft C:\Users\jonat\AppData\Roaming\Microsoft I do not see a UserSecrets directory alright I got it lol sorry last and final question. when I deploy this, how will it know to look for a keyvault rather than my secrets
Pobiega
Pobiegaā€¢2y ago
your secrets will not be there when its deployed, so it will fall back to environment variables or a keyvault or however you configured it
kosh
koshā€¢2y ago
love you guys, thanks a lot
kosh
koshā€¢2y ago
went with the Di approach and it's gif related
Pobiega
Pobiegaā€¢2y ago
don't forget to /close the thread