diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/Controllers/UserControllerIntegrationTest.cs b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/Controllers/UserControllerIntegrationTest.cs index 816648ae31..672a8ca79d 100644 --- a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/Controllers/UserControllerIntegrationTest.cs +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/Controllers/UserControllerIntegrationTest.cs @@ -5,14 +5,22 @@ namespace FixUnableToResolveServiceIssue.Tests.Controllers { - internal class UserControllerIntegrationTest + public class UserControllerIntegrationTest { + private readonly WebApplicationFactory _application; private readonly HttpClient _httpClient; - + public UserControllerIntegrationTest() { - var application = new WebApplicationFactory(); - _httpClient = application.CreateClient(); + _application = new WebApplicationFactory(); + _httpClient = _application.CreateClient(); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + _httpClient.Dispose(); + _application.Dispose(); } [Test] diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/FixUnableToResolveServiceIssue.Tests.csproj b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/FixUnableToResolveServiceIssue.Tests.csproj index 98717a0a07..f655cc63b0 100644 --- a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/FixUnableToResolveServiceIssue.Tests.csproj +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/FixUnableToResolveServiceIssue.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -10,12 +10,12 @@ - - - - - - + + + + + + diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/Services/ServiceResolutionTest.cs b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/Services/ServiceResolutionTest.cs new file mode 100644 index 0000000000..d64c8cfd72 --- /dev/null +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.Tests/Services/ServiceResolutionTest.cs @@ -0,0 +1,56 @@ +using FixUnableToResolveServiceIssue.Services; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; + +namespace FixUnableToResolveServiceIssue.Tests.Services +{ + public class ServiceResolutionTest + { + private WebApplicationFactory _factory = null!; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + _factory = new WebApplicationFactory(); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + _factory.Dispose(); + } + + [Test] + public void WhenSmtpSettingsAreBoundToOptions_EmailServiceResolvesWithConfiguredValues() + { + //Arrange + using var scope = _factory.Services.CreateScope(); + + //Act + var emailService = scope.ServiceProvider.GetRequiredService(); + + //Assert + Assert.That(emailService.GetServerAddress(), Is.EqualTo("smtp.example.com:587")); + } + + [Test] + public void WhenAddHttpClientIsRegistered_HttpClientTypesResolve() + { + //Arrange + using var scope = _factory.Services.CreateScope(); + var provider = scope.ServiceProvider; + + //Act + var httpClientFactory = provider.GetService(); + var httpClient = provider.GetService(); + var weatherClient = provider.GetService(); + + //Assert + Assert.That(httpClientFactory, Is.Not.Null); + Assert.That(httpClient, Is.Not.Null); + Assert.That(weatherClient, Is.Not.Null); + Assert.That(httpClientFactory!.CreateClient(nameof(WeatherClient)).BaseAddress, + Is.EqualTo(new Uri("https://api.example.com/"))); + } + } +} diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.csproj b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.csproj index 5c0c4bd353..0268d75ddb 100644 --- a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.csproj +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue.csproj @@ -1,14 +1,13 @@ - net7.0 + net10.0 enable enable - - + diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Program.cs b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Program.cs index 4487448a61..06829b772c 100644 --- a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Program.cs +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Program.cs @@ -1,24 +1,38 @@ using FixUnableToResolveServiceIssue.Interfaces; using FixUnableToResolveServiceIssue.Services; +using FixUnableToResolveServiceIssue.Settings; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); -//Add this line to register the service as a singleton in the dependency injection container: +//Add this line to register the service with a scoped lifetime in the dependency injection container: builder.Services.AddScoped(); + +// A plain string cannot be resolved by type, so the SMTP values are bound to an options class +// and EmailService asks for IOptions instead: +builder.Services.Configure(builder.Configuration.GetSection("Smtp")); +builder.Services.AddScoped(); + +// Registers IHttpClientFactory and makes a plain HttpClient injectable: +builder.Services.AddHttpClient(); + +// Registers WeatherClient as a typed client with its own configured HttpClient: +builder.Services.AddHttpClient(client => +{ + client.BaseAddress = new Uri("https://api.example.com/"); +}); + var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseSwagger(); - app.UseSwaggerUI(); + app.MapOpenApi(); } app.UseHttpsRedirection(); diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Properties/launchSettings.json b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Properties/launchSettings.json index e53f3f9831..8d56c6ee72 100644 --- a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Properties/launchSettings.json +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Properties/launchSettings.json @@ -13,7 +13,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "api/User", "applicationUrl": "https://localhost:7027;http://localhost:5073", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Services/EmailService.cs b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Services/EmailService.cs new file mode 100644 index 0000000000..7006e9f364 --- /dev/null +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Services/EmailService.cs @@ -0,0 +1,20 @@ +using FixUnableToResolveServiceIssue.Settings; +using Microsoft.Extensions.Options; + +namespace FixUnableToResolveServiceIssue.Services +{ + public class EmailService + { + private readonly SmtpSettings _settings; + + // Asking for "string host" here instead would throw + // "Unable to resolve service for type 'System.String'", because the container + // cannot tell which string we mean. IOptions is a type it can resolve. + public EmailService(IOptions options) + { + _settings = options.Value; + } + + public string GetServerAddress() => $"{_settings.Host}:{_settings.Port}"; + } +} diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Services/WeatherClient.cs b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Services/WeatherClient.cs new file mode 100644 index 0000000000..d134a58219 --- /dev/null +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Services/WeatherClient.cs @@ -0,0 +1,7 @@ +namespace FixUnableToResolveServiceIssue.Services +{ + public class WeatherClient(HttpClient httpClient) + { + public Task GetForecastAsync() => httpClient.GetStringAsync("forecast"); + } +} diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Settings/SmtpSettings.cs b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Settings/SmtpSettings.cs new file mode 100644 index 0000000000..e79087523f --- /dev/null +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/Settings/SmtpSettings.cs @@ -0,0 +1,8 @@ +namespace FixUnableToResolveServiceIssue.Settings +{ + public class SmtpSettings + { + public string Host { get; set; } = string.Empty; + public int Port { get; set; } + } +} diff --git a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/appsettings.json b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/appsettings.json index 10f68b8c8b..d9bb92bb69 100644 --- a/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/appsettings.json +++ b/dotnet-dependency-injection/FixUnableToResolveServiceIssue/FixUnableToResolveServiceIssue/appsettings.json @@ -5,5 +5,9 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Smtp": { + "Host": "smtp.example.com", + "Port": 587 + } }