C# fetch connection details inside a constructor - c#

I have my DB class defined as below:
public class DbAdapterService : DbAdapterService
{
private readonly AppSettings _settings;
public DbAdapterService(IOptions<AppSettings> settings)
{
_settings = settings?.Value;
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = _settings.ConnectionString;
}
}
In the above class I am fetching my connection string from my appsettings.json and it works fine.
Now we need to fetch the connecting strings username and password from another method defined in our class. This method fetches these details from our stored vault. Example as below:
public class CredentialsService : ICredentialsService
{
public Credentials GetDetails()
{
//return credentials
}
}
My questions is can I call this method in my DbAdapterService constructor above or if there is a better way to handle this.
Thanks
--Updated--
public class DbAdapterService : DbAdapterService
{
private readonly AppSettings _settings;
public ICredentialsService _credentialsService;
private bool isInitialized = false;
public DbAdapterService(IOptions<AppSettings> settings, ICredentialsService credentialsService)
{
_settings = settings?.Value;
_credentialsService = credentialsService;
if (!isInitialized)
{
Initialize(_credentialsService);
}
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = _settings.ConnectionString;
}
public void Initialize(ICredentialsService credentialsService)
{
if (isInitialized)
return;
else
{
//credentialsService.GetDetails();
isInitialized = true;
}
}
}

You seem to want to initialize the connection string in the constructor. If you're reaching out to some external component (file system, database, or API for example) to retrieve a value, that's possibly going to be an async operation. There's no reliable way of calling an async method in a constructor.
So, what can we do? Well, there's no rule saying we must do it in the constructor. The constructor was a convenient place, because it ensures that by the time you invoke any other methods, the initialization will have taken place. But there are other patterns to accomplish this. Here's one:
public class DbAdapterService : IDbAdapterService
{
string _connectionString;
readonly AppSettings _settings;
readonly ICredentialsService _credentialsService;
public DbAdapterService(IOptions<AppSettings> settings,
ICredentialsService credentialsService)
{
_settings = settings.Value;
_credentialsService = credentialsService;
}
async Task EnsureInitializedAsync()
{
if (!string.IsNullOrEmpty(_connectionString))
{
//no need to reinitialize
//unless the credentials might change during the lifecycle of this object
return;
}
var credentials = await _credentialsService.GetDetailsAsync();
var builder = new DbConnectionStringBuilder(_settings.ConnectionString);
builder.Username = credentials.Username;
builder.Password = credentials.Password;
_connectionString = builder.ConnectionString;
}
public async Task DoSomethingAsync()
{
await EnsureInitializedAsync();
//now you can use _connectionString here
}
}
The key part is remembering to invoke EnsureInitializedAsync() in any method that needs to make use of the connection string. But at least code that consumed DbAdapterService won't have to know whether to initialize the connection string or not.
While this pattern isn't as necessary for non-async code, it's great for operations that might become async in the future, and the pattern makes more sense if the details of the connection might actually change at runtime, but your objects are constructed when you configure IoC container.

you can try this
public class DbAdapterService : DbAdapterService
{
private readonly AppSettings _settings;
private readonly ICredentialsService _credentialsService ;
public DbAdapterService(
IOptions<AppSettings> settings,
ICredentialsService credentialsService )
{
_credentialsService= credentialsService ;
_settings = settings?.Value;
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = _settings.ConnectionString;
}
}
after this you can call any method from the credentialService
or a little shorter if you only need credentials
private readonly AppSettings _settings;
private readonly Credentials _credentials;
public DbAdapterService(
IOptions<AppSettings> settings,
ICredentialsService credentialsService )
{
_credentials= credentialsService.GetDetails();
_settings = settings?.Value;
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = _settings.ConnectionString;
}

Related

WPF SimpleInjector call to client.GetAsync hanging

I am trying to use SimpleInjector in a WPF Application (.NET Framework). We use it in exactly the same way in many of our Services but for some reason when I am attempting to implement the same logic in this WPF Application, the call to the HttpClient().GetAsync is hanging. We think it is because for some reason the Task is not executing.
I am registering the objects from the OnStartUp element of App.xaml.cs as below. Inside the SetupService Constructor we call a SetupService URL (set in the SetupConfiguration Section of the App.Config) to get the SetupResponse to use in the app.
It is ultimately hanging in the ServiceClient.GetAsync method, I have tried to show the flow below:
All classes appear to have been injected correctly, and the ServiceClient is populated in exactly the same way as the same point in one of our working services. We're at a loss as to what is happening, and how to fix this.
Finally, SetupService is being injected in other Classes - so I would rather get it working like this, rather than remove the call from the SimpleInjector mechanism.
Any help is very much appreciated.
public partial class App : Application
{
private static readonly Container _container = new Container();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
RegisterDependencies();
_container.Verify();
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
public class SetupService: ISetupService
{
private static readonly Dictionary<string, string> AcceptType = new Dictionary<string, string>
{
{"Accept", "application/xml"}
};
private const string AuthenticationType = "Basic";
private readonly IServiceClient _serviceClient;
private readonly ILoggingProvider _logger;
private readonly IConfigurationSection _configuration;
public SetupService(IConfigurationSection configuration, IServiceClient serviceClient, ILoggingProvider logger)
{
_serviceClient = serviceClient;
_logger = logger;
_configuration = kmsConfiguration;
RefreshSetup();
}
public void RefreshSetup()
{
try
{
var token = BuildIdentityToken();
var authHeaderClear = string.Format("IDENTITY_TOKEN:{0}", token);
var authenticationHeaderValue =
new AuthenticationHeaderValue(AuthenticationType, Convert.ToBase64String(Encoding.ASCII.GetBytes(authHeaderClear)));
_serviceClient.Url = _configuration.Url;
var httpResponse = _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType).Result;
var responseString = httpResponse.Content.ReadAsStringAsync().Result;
_response = responseString.FromXML<SetupResponse>();
}
catch (Exception e)
{
throw
}
}
public class ServiceClient : IServiceClient
{
private const string ContentType = "application/json";
private string _userAgent;
private ILoggingProvider _logger;
public string Url { get; set; }
public string ProxyAddress { get; set; }
public int TimeoutForRequestAndResponseMs { get; set; }
public int HttpCode { get; private set; }
public ServiceClient(ILoggingProvider logger = null)
{
_logger = logger;
}
public async Task<HttpResponseMessage> GetAsync(string endpoint, AuthenticationHeaderValue authenticationHeaderValue = null, IDictionary<string, string> additionalData = null, IDictionary<string, string> additionalParams = null)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Url);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentType));
if (authenticationHeaderValue != null)
client.DefaultRequestHeaders.Authorization = authenticationHeaderValue;
ProcessHeader(client.DefaultRequestHeaders, additionalData);
var paramsQueryString = ProcessParams(additionalParams);
if (!string.IsNullOrEmpty(paramsQueryString))
endpoint = $"{endpoint}?{paramsQueryString}";
return await client.GetAsync(endpoint); **// HANGS ON THIS LINE!**
}
}
}
}
If you block on asynchronous code from a UI thread, then you can expect deadlocks. I explain this fully on my blog. In this case, the cause of the deadlock is Result. There's a couple of solutions.
The one I recommend is to rethink your user experience. Your UI shouldn't be blocking on an HTTP call to complete before it shows anything; instead, immediately (and synchronously) display a UI (i.e., some "loading..." screen), and then update that UI when the HTTP call completes.
The other is to block during startup. There's a few patterns for this. None of them work in all situations, but one that usually works is to wrap the asynchronous work in Task.Run and then block on that, e.g., var httpResponse = Task.Run(() => _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType)).GetAwaiter().GetResult(); and similar for other blocking calls.
Blocking before showing a UI is generally considered a bad UX. App stores generally disallow it. So that's why I recommend changing the UX. You may find an approach like this helpful.
Thanks for your Responses, I just wanted to sync the solution I've gone for.
It was risky for me to change the code in SetupService to remove the .Result, even though this was probably the correct solution, as I did not want to affect the other working Services using the SetupService library already there.
I ended up moving the regsitrations off the UI Thread by embedding the SimpleInjector code in a Code library, Creating a Program.cs and Main() and setting that as my Entry point.
static class Program
{
public static readonly Container _container = new Container();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(){
var app = new MyApp.App();
Register();
app.Run(_container.GetInstance<MainWindow>());
}
static void Register()
{
_container.Register<MainWindow>();
MySimpleInjector.Register(_container);
_container.Verify();
}
}
and then, in a Separate .dll project, MyApp.Common
public class MySimpleInjector
{
private readonly Container _container;
public static void Register(Container container)
{
var injector = new MySimpleInjector(container);
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
I appreciate that this may not be the ideal solution - but it suits my purposes.
Again, thanks for your help and comments!
Andrew.

Is it possible to create asynchronous class construction in C#?

I am planning to do an async/await in C# Blazor class constructor method. Although this is written in Blazor it's a generic for C# so it doesn't matter.
public class DoctorsService : IDoctorsService
{
private readonly IConfiguration _config;
public DoctorsService(IConfiguration config, IClinicsDoctorsService clinicsDoctorsService)
{
_config = config;
clinicsDoctorsService.GetClinicsDoctorsListAsync(new Dictionary<string, string>());
}
}
If you noticed the clinicsDoctorsService isn't awaited using await, that's bc the compiler will complain that the method must be in async Task. If I write it like public async Task DoctorsService(), the compiler will complain with another issue because you cannot name a method same with the class name.
Sync ctor + async Init
public class DoctorsService : IDoctorsService
{
private readonly IConfiguration _config;
private readonly IClinicsDoctorsService _clinicsDoctorsService;
private bool _isInitialized = false;
internal DoctorsService(IConfiguration config, IClinicsDoctorsService clinicsDoctorsService)
{
_config = config;
_clinicsDoctorsService = clinicsDoctorsService;
//Set only those fields which are NOT depending on external data
}
internal async Task Init()
{
_clinicsDoctorsService.GetClinicsDoctorsListAsync(new Dictionary<string, string>());
_isInitialized = true;
//Set those fields which are depending on external data
}
public void SomeMethod()
{
if (!_isInitialized)
throw new NotSupportedException("Please call Init before you call any other instance method.");
//Some business logic goes here
}
}
Please note the internal access modifiers.
Dummy factory method
public static class DoctorServiceFactory
{
public static async Task<IDoctorsService> CreateDoctorsService(IConfiguration config, IClinicsDoctorsService clinicsDoctorsService)
{
IDoctorsService svc = new DoctorsService(config, clinicsDoctorsService);
await svc.Init();
return svc;
}
}

When do Injection dependency Integration Event, controller not called c#

I am trying to make communication between microservices using eventbus, when I use dependency injection my controller can no longer be called.
I have my Controller
public class CustomersController : ControllerBase
{
private readonly ICustomerRepository _customerRepository;
private readonly IIdentityService _identityService;
private readonly ICustomerIntegrationEventService _customerIntegrationEventService;
public CustomersController(ICustomerRepository customerRepository, IIdentityService identityService, ICustomerIntegrationEventService customerIntegrationEventService)
{
_customerRepository = customerRepository ?? throw new ArgumentNullException(nameof(customerRepository));
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
_customerIntegrationEventService = customerIntegrationEventService ?? throw new ArgumentNullException(nameof(customerIntegrationEventService));
}
}
In this Controller, I have a method named Add. It basically adds a client. When client is added, I would like to notify other microservice and send the data to a service bus. So far I'm using Integration Event. But in the moment that the dependency injection is done in the controller. the front can no longer hit the controller, returning an error 500.
public async Task<IActionResult> Add(Customer value)
{
var idAdded = await _customerRepository.Add(value).ConfigureAwait(false);
if (!idAdded.HasValue)
return BadRequest();
var integrationEvent = new CustomerIntegrationEvent(idAdded.Value, value);
await _customerIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
return CreatedAtAction(nameof(Get), new { id = idAdded.Value }, null);
}
Soon below is how this class building _customerIntegrationEventService
CustomerIntegrationEventService
public class CustomerIntegrationEventService : ICustomerIntegrationEventService
{
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
private readonly IEventBus _eventBus;
private readonly ApplicationDataContext _osDataContext;
private readonly IntegrationEventLogContext _eventLogContext;
private readonly IIntegrationEventLogService _eventLogService;
public CustomerIntegrationEventService(
IEventBus eventBus,
ApplicationDataContext hrDataContext,
IntegrationEventLogContext eventLogContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
{
_osDataContext = hrDataContext ?? throw new ArgumentNullException(nameof(hrDataContext));
_eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext));
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_eventLogService = _integrationEventLogServiceFactory(hrDataContext.Database.GetDbConnection());
}
public async Task PublishEventsThroughEventBusAsync()
{
var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync();
foreach (var logEvt in pendindLogEvents)
{
try
{
await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId);
_eventBus.Publish(logEvt.IntegrationEvent);
await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId);
}
catch (Exception)
{
await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId);
}
}
}
public async Task AddAndSaveEventAsync(IntegrationEvent evt)
{
await _eventLogService.SaveEventAsync(evt, _osDataContext.Database.CurrentTransaction.GetDbTransaction());
}
}
all of these codes were taken from the example https://learn.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/subscribe-events
I made the dependency injection in the startup, but anyway the error persists
public void AddIntegrationServices(IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
services.AddTransient<ICustomerIntegrationEventService, CustomerIntegrationEventService>();
}
How could I at least see the error behind things, or how I come up with that solution. This code is based on microsoft eShopOnContainers

Add custom properties to telemetry request at controller level

I am trying to add specific properties to telemetry request for every route.
After digging a bit, I've found that I can create my own custom TelemetryInitializer by implementing ITelemetryInitializer.
By doing this I've managed to add global properties to the request.
However, I still need to add specific properties at the controller level.
Do you have any idea how can I achieve this?
I've tried to inject TelemetryClient into the controller, but if I use it the properties are shared between requests.
This is how I've tried to log in the controller:
private TelemetryClient telemetryClient;
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
telemetryClient.Context.GlobalProperties["param1"] = param1;
telemetryClient.Context.GlobalProperties["param2"] = param2;
}
[HttpGet]
public async Task<IActionResult> RouteTwo([FromQuery(Name = "param3")]string param3, [FromQuery(Name = "param4")]string param4)
{
telemetryClient.Context.GlobalProperties["param3"] = param3;
telemetryClient.Context.GlobalProperties["param4"] = param4;
}
And this is the implementation of ITelemetryInitializer:
public class CustomPropertiesTelemetryInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomPropertiesTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.GlobalProperties["RequestId"] = httpContextAccessor.HttpContext.GetProperty("requestId");
telemetry.Context.GlobalProperties["Ip"] = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress.ToString();
telemetry.Context.GlobalProperties["RoutePath"] = httpContextAccessor.HttpContext?.Request.Path;
}
}
If the properties you added are always like "paramxxx", then there is a workaround(but it's really not very elegant).
In the controller constructor, check the GlobalProperties if it contains key like "paramxxx":
public ValueController(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
var props = this.telemetryClient.Context.GlobalProperties;
foreach (var p in props)
{
if (p.Key.Contains("param"))
{
props.Remove(p.Key);
}
}
}
The key here is to use the DI framework. You can use it to get request-scoped data or services into your ITelemetryInitializer.
(These examples are based on the standard ASP.Net Dependency Injection framework. This pattern should work with any DI framework, but will need to be adjusted slightly.)
First, create a class to represent your request-scoped telemetry. I've used a simple DTO, but this could also be a service that knows how to fetch/generate the data itself. Register it using AddScoped. "Scoped" means that a new instance will be created for each HTTP request, and then that instance will be re-used within that request.
Because I used a DTO, I didn't bother with an interface--you should use an interface if the class contains any logic you'll want to mock in unit tests.
public class RequestScopedTelemetry
{
public string MyCustomProperty { get; set; }
}
services.AddScoped<RequestScopedTelemetry>();
Now, create the ITelemetryInitializer and register it as a singleton. App Insights will discover and use it through the DI framework.
class RequestScopedTelemetryInitializer : ITelemetryInitializer
{
readonly IHttpContextAccessor httpContextAccessor;
public RequestScopedTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
=> this.httpContextAccessor = httpContextAccessor;
public void Initialize(ITelemetry telemetry)
{
// Attempt to resolve the request-scoped telemetry from the DI container
var requestScopedTelemetry = httpContextAccessor
.HttpContext?
.RequestServices?
.GetService<RequestScopedTelemetry>();
// RequestScopedTelemetry is only available within an active request scope
// If no telemetry available, just move along...
if (requestScopedTelemetry == null)
return;
// If telemetry was available, add it to the App Insights telemetry collection
telemetry.Context.GlobalProperties[nameof(RequestScopedTelemetry.MyCustomProperty)]
= requestScopedTelemetry.MyCustomProperty;
}
}
services.AddSingleton<ITelemetryInitializer, RequestScopedTelemetryInitializer>();
Finally, in your controller method, set your per-request values. This part isn't necessary if your telemetry class is able to fetch or generate the data itself.
public class ExampleController : ControllerBase
{
readonly RequestScopedTelemetry telemetry;
public ValuesController(RequestScopedTelemetry telemetry)
=> this.telemetry = telemetry;
[HttpGet]
public ActionResult Get()
{
telemetry.MyCustomProperty = "MyCustomValue";
// Do what you want to
return Ok();
}
}
In order to add per request data into telemetry, you need to have a way to share data within the request. A reliable way is by using HttpContent.Items property, which is basically a Dictionary.
You can create a service to keep a Dictionary inside HttpContent.Items with all custom data you want in telemetry (key prefix is used to ensure we only read the things we want later in Initializer):
public class LogTelemetryRequest
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public LogTelemetryRequest(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void AddProperty(string key, string value)
{
_httpContextAccessor.HttpContext.Items[KEY_PREFIX + key] = value;
}
}
Register this as scoped in Startup.cs:
services.AddScoped<LogTelemetryRequest>();
Use it in your controller:
private LogTelemetryRequest logTelemetryRequest;
public ValueController(LogTelemetryRequest logTelemetryRequest)
{
this.logTelemetryRequest = logTelemetryRequest;
}
[HttpGet]
public async Task<IActionResult> RouteOne([FromQuery(Name = "param1")]string param1, [FromQuery(Name = "param2")]string param2)
{
// telemetryClient.Context.GlobalProperties["param1"] = param1;
// telemetryClient.Context.GlobalProperties["param2"] = param2;
logTelemetryRequest.AddProperty("param1", param1);
logTelemetryRequest.AddProperty("param2", param2);
}
Then read it within initializer:
public class AddCustomTelemetryInitializer : ITelemetryInitializer
{
private const string KEY_PREFIX = "CustomTelemetryData_";
private readonly IHttpContextAccessor _httpContextAccessor;
public AddCustomTelemetryInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
foreach (var item in _httpContextAccessor.HttpContext.Items)
{
if (item.Key is string key && key.StartsWith(KEY_PREFIX))
requestTelemetry.Properties.Add(key, item.Value.ToString());
}
}
}
Ideally LogTelemetryRequest should be registered using an interface, and the key prefix should be a single shared constant, didn't do for the sake of simplicity.

Best practice with lifetime of IMongoDatabase object in C#?

I'm brand new to NoSQL and I have a question. I've created a DatabaseHandler that allows me to grab a collection globally across my solution. I then noticed that I'm using 1 instance of IMongoDatabase for the entire lifetime of my application, is this correct?
Obviously coming form a MySQL background I'm used to using and opening a new connection on each call to DatabaseHandler
I'm just asking for someone to check if this is okay, as I'm really new and It's sort of confusing me.
internal sealed class DatabaseHandler : IDisposable
{
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
public IMongoDatabase MongoDatabase;
public DatabaseHandler()
{
var config = Program.Server.ConfigHandler;
var databaseHost = config.GetValue("database.hostname");
var databasePort = config.GetValue("database.port");
var mongoClient = new MongoClient(new MongoClientSettings
{
Server = new MongoServerAddress(databaseHost, databasePort.ToInteger()),
ServerSelectionTimeout = TimeSpan.FromSeconds(3)
});
MongoDatabase = mongoClient.GetDatabase(config.GetValue("database.name"));
var isMongoLive = MongoDatabase.RunCommandAsync((Command<BsonDocument>)"{ping:1}").Wait(1000);
if (!isMongoLive)
{
Logger.Error("We couldn't establish a connection with the database.");
}
}
public IMongoCollection<T> GetCollection<T>(string name)
{
return MongoDatabase.GetCollection<T>(name);
}
public void Dispose()
{
MongoDatabase.D
}
}

Categories