Dependency Injection Notes - Habilya/LearningCourseNotes GitHub Wiki
var builder = WebApplication.CreateBuilder(args);
// ConfigureServices starts
builder.Services.AddEndpointsApiExplorer();
// ConfigureServices ends
var app = builder.Build();
// this groups the logical services together to not pollute the configure services section
public static class SomethingServiceCollectionExtensions
{
public static IServiceCollection AddEndpointsApiExplorer(
this IServiceCollection services)
{
services.TryAddSingleton<IActionDescriptorCollectionProvider, DefaultActionDescriptorCollectionProvider>();
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApiDescriptionProvider, EndpointMetadataApiDescriptionProvider>());
return services;
}
}
- Singleton - Single instance of a dependency will be created and always returned throughout the lifetime of whole application
- Transient - Every time you require it, new instance of dependency will be returned
- Scoped - Dependency lifetime is the scope of instance of a class
// or IServiceProvider serviceProvider
var serviceProvider = services.BuildServiceProvider();
// throws an exception if unresolved service
var someservice = serviceProvider.GetRequiredService<OldFilesRemover>();
// returns null if unresolved
var someservice2 = serviceProvider.GetService<OldFilesRemover>();
buildrt.Services.AddScoped(provider =>
{
var logger = provider.GetRequiredService<ILogger<DurationLoggerFilter>>();
return new DurationLoggerFilter(logger);
});
services registration can be grouped into logical methods
create a file DependencyInjection.cs
// !!!!!!! Note the namespace usage here -- the consuming class, now doesn't have to add using statement for this class
// Green squigly lines, but it's Ok
namespace Microsoft.Extensions.DependencyInjection;
public static class DependencyInjection
{
public static ILoggingBuilder AddTeamUpWebScraperLogging(this ILoggingBuilder loggingBuilder, IConfiguration configuration)
{
var serilogLogger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.Enrich.FromLogContext()
.CreateLogger();
// Clear default logging providers and add Serilog
loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog(serilogLogger);
return loggingBuilder;
}
public static IServiceCollection AddTeamUpWebScraperLibraryServices(this IServiceCollection services, HostBuilderContext context)
{
// Register the services from the library
services.AddSingleton<IDateTimeProvider, DateTimeProvider>();
services.AddTransient(typeof(ILoggerAdapter<>), typeof(LoggerAdapter<>));
services.AddSingleton<InputValidation>();
services.AddSingleton<ITeamUpAPIService, TeamUpAPIService>();
services.AddSingleton<IXLWorkBookFactory, XLWorkBookFactory>();
services.AddSingleton<IEventApiResponseTransformer, EventApiResponseTransformer>();
services.AddSingleton<IExcelSpreadsheetReportProvider, ExcelSpreadsheetReportProvider>();
services.AddSingleton<IDisplayGridViewProvider, DisplayGridViewProvider>();
// Example of adding HttpClient if needed for the library
services.AddHttpClient(TeamUpApiConstants.HTTP_CLIENTNAME, (serviceProvider, httpClient) =>
{
var config = serviceProvider.GetRequiredService<IConfiguration>();
var baseURL = config.GetValue<string>($"{AppsettingsConstants.CONFIG_SECTION_NAME_TEAMUP_API}:{TeamUpApiConstants.CONFIG_BaseURL_NAME}");
var calendarId = config.GetValue<string>($"{AppsettingsConstants.CONFIG_SECTION_NAME_TEAMUP_API}:{TeamUpApiConstants.CONFIG_CalendarId_NAME}");
var teamupToken = config.GetValue<string>($"{AppsettingsConstants.CONFIG_SECTION_NAME_TEAMUP_API}:{TeamUpApiConstants.CONFIG_TeamupToken_NAME}");
httpClient.BaseAddress = new Uri(baseURL + calendarId + "/");
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
httpClient.DefaultRequestHeaders.Add(TeamUpApiConstants.API_TOKEN_HEADER_NAME, teamupToken);
});
// Read appsettings.json into appropriate models
// !!! BUG or FEATURE? !!! This Bind is uncapable of reading model annotations
// ex: [JsonPropertyName("name")]
// Probably because config is supposed to be concieved by the developer
// and everythig is supposed to be rightly named
#region TeamUpApiConfiguration as a dependency
var teamUpApiConfiguration = new TeamUpApiConfiguration();
context.Configuration.GetSection(AppsettingsConstants.CONFIG_SECTION_NAME_TEAMUP_API).Bind(teamUpApiConfiguration);
services.AddSingleton(teamUpApiConfiguration);
#endregion
#region ExcelReportSpreadSheetConfiguration as a dependency
var excelReportSpreadSheetConfiguration = new ExcelReportSpreadSheetConfig();
context.Configuration.GetSection(AppsettingsConstants.CONFIG_SECTION_NAME_EXCEL_SPREADSHEET).Bind(excelReportSpreadSheetConfiguration);
services.AddSingleton(excelReportSpreadSheetConfiguration);
#endregion
// Add other necessary services as needed
return services;
}
}
in program.cs
static IHostBuilder CreateHostBuilder()
{
return Host
.CreateDefaultBuilder()
.ConfigureAppConfiguration((hostContext, config) =>
{
config.AddJsonFile(CONFIG_JSON_FILE_PATH, optional: false)
.AddEnvironmentVariables()
.Build();
})
.ConfigureLogging((hostContext, logging) =>
{
logging.AddTeamUpWebScraperLogging(hostContext.Configuration);
})
.ConfigureServices((context, services) =>
{
services.AddTransient<Dashboard>();
services.AddSingleton<TeamUpController>();
services.AddTeamUpWebScraperLibraryServices(context);
});
}
Timing an API request
public class WeatherService : IWeatherService
{
public async Task<WeatherResponse?> GetCurrentWeatherAsync(string city)
{
var sw = Stopwatch.StartNew();
try
{
var url = $"https://someapi.org/?q={city}";
var httpClient = _httpClientFactory.CreateClient();
var weatherResponse = await httpClient.GetAsync(url);
if(weatherResponse.StatusCode == HttpStatusCode.NotFound)
{
return null;
}
var weather = await weatherResponse.Content.ReadFromJsonAsync<WeatherResponse>();
return weather;
}
finally
{
sw.Stop();
_logger.LogInformation($"Weather retrieval for city {0}, took {1}ms",
city, sw.ElapsedMilliseconds);
}
}
}
program.cs
services.AddTransient<IWeatherService, OpenWeatherService>();
metric collection should not be part of service API call...
public class LoggedWeatherService : IWeatherService // <-- Decorator of OpenWeatherService
{
private readonly IWeatherService _weatherService; // <-- OpenWeatherService
private readonly ILogger<IWeatherService> _logger;
public LoggedWeatherService(IWeatherService weatherService,
ILogger<IWeatherService> logger)
{
_weatherService = weatherService;
_logger = logger;
}
public async Task<WeatherResponse?> GetCurrentWeatherAsync(string city)
{
var sw = Stopwatch.StartNew();
try
{
return await _weatherService.GetCurrentWeatherAsync(city);
}
finally
{
sw.Stop();
_logger.LogInformation($"Weather retrieval for city {0}, took {1}ms",
city, sw.ElapsedMilliseconds);
}
}
}
program.cs
services.AddTransient<OpenWeatherService>();
services.AddTransient<IWeatherService>(provider =>
new LoggedWeatherService(provider.GetRequiredService<OpenWeatherService>(),
provider.GetRequiredService<ILogger<IWeatherService>>()));