Skip to content

Commit f9371ad

Browse files
Dependency Injection in ASP.NET Core: new net10.0 sample (#2248)
Migrates the article's sample from CodeMazeBlog/dependency-injection (netcoreapp3.1) into dotnet-dependency-injection/DependencyInjectionInAspNetCore: net10.0 MVC app (Program.cs registrations, GameController with constructor injection, BetterPlayerGenerator, PlayerOfTheDay with IServiceScopeFactory) and 4 xUnit tests.
1 parent 363f021 commit f9371ad

90 files changed

Lines changed: 83287 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
<IsTestProject>true</IsTestProject>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<Using Include="Xunit" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.12" />
17+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
18+
<PackageReference Include="xunit.v3.mtp-off" Version="4.0.1" />
19+
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<ProjectReference Include="..\DependencyInjectionDemo\DependencyInjectionDemo.csproj" />
24+
</ItemGroup>
25+
26+
</Project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using DependencyInjectionDemo.Controllers;
2+
using DependencyInjectionDemo.Models;
3+
using DependencyInjectionDemo.Services;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.Extensions.Logging.Abstractions;
6+
7+
namespace DependencyInjectionDemo.Tests;
8+
9+
public class GameControllerTests
10+
{
11+
[Fact]
12+
public void Index_ShowsThePlayerFromTheGenerator()
13+
{
14+
var controller = new GameController(NullLogger<GameController>.Instance, new FakePlayerGenerator());
15+
16+
var result = controller.Index();
17+
18+
var view = Assert.IsType<ViewResult>(result);
19+
var player = Assert.IsType<Player>(view.Model);
20+
Assert.Equal("Test Hero", player.Name);
21+
}
22+
23+
private class FakePlayerGenerator : IPlayerGenerator
24+
{
25+
public Player CreateNewPlayer() => new() { Name = "Test Hero", Race = "Human" };
26+
}
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.AspNetCore.Mvc.Testing;
2+
3+
namespace DependencyInjectionDemo.Tests;
4+
5+
// WebApplicationFactory runs the app in the Development environment,
6+
// so the container's ValidateScopes and ValidateOnBuild checks run at startup too.
7+
public class GamePageTests(WebApplicationFactory<Program> factory)
8+
: IClassFixture<WebApplicationFactory<Program>>
9+
{
10+
[Fact]
11+
public async Task GetGame_ShowsMinsc()
12+
{
13+
var client = factory.CreateClient();
14+
15+
var page = await client.GetStringAsync("/Game", TestContext.Current.CancellationToken);
16+
17+
Assert.Contains("Name: Minsc", page);
18+
}
19+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using DependencyInjectionDemo.Models;
2+
using DependencyInjectionDemo.Services;
3+
using Microsoft.Extensions.DependencyInjection;
4+
5+
namespace DependencyInjectionDemo.Tests;
6+
7+
public class ServiceRegistrationTests
8+
{
9+
// The checks ASP.NET Core turns on in the Development environment.
10+
private static readonly ServiceProviderOptions DevelopmentChecks = new()
11+
{
12+
ValidateScopes = true,
13+
ValidateOnBuild = true
14+
};
15+
16+
[Fact]
17+
public void PlayerOfTheDay_PassesScopeValidation()
18+
{
19+
var services = new ServiceCollection();
20+
services.AddScoped<IPlayerGenerator, PlayerGenerator>();
21+
services.AddSingleton<PlayerOfTheDay>();
22+
23+
using var provider = services.BuildServiceProvider(DevelopmentChecks);
24+
25+
var first = provider.GetRequiredService<PlayerOfTheDay>();
26+
var second = provider.GetRequiredService<PlayerOfTheDay>();
27+
28+
Assert.Same(first.Player, second.Player);
29+
}
30+
31+
[Fact]
32+
public void SingletonThatTakesAScopedService_FailsScopeValidation()
33+
{
34+
var services = new ServiceCollection();
35+
services.AddScoped<IPlayerGenerator, PlayerGenerator>();
36+
services.AddSingleton<CapturingPlayerOfTheDay>();
37+
38+
var exception = Assert.Throws<AggregateException>(() => services.BuildServiceProvider(DevelopmentChecks));
39+
40+
Assert.Contains("Cannot consume scoped service", exception.Message);
41+
}
42+
43+
// The article's first, wrong PlayerOfTheDay. It lives only here, so the sample never ships the bug.
44+
private class CapturingPlayerOfTheDay
45+
{
46+
public CapturingPlayerOfTheDay(IPlayerGenerator playerGenerator)
47+
{
48+
Player = playerGenerator.CreateNewPlayer();
49+
}
50+
51+
public Player Player { get; }
52+
}
53+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31903.59
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjectionDemo", "DependencyInjectionDemo\DependencyInjectionDemo.csproj", "{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyInjectionDemo.Tests", "DependencyInjectionDemo.Tests\DependencyInjectionDemo.Tests.csproj", "{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Debug|x64 = Debug|x64
14+
Debug|x86 = Debug|x86
15+
Release|Any CPU = Release|Any CPU
16+
Release|x64 = Release|x64
17+
Release|x86 = Release|x86
18+
EndGlobalSection
19+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Debug|x64.ActiveCfg = Debug|Any CPU
23+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Debug|x64.Build.0 = Debug|Any CPU
24+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Debug|x86.ActiveCfg = Debug|Any CPU
25+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Debug|x86.Build.0 = Debug|Any CPU
26+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Release|Any CPU.ActiveCfg = Release|Any CPU
27+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Release|Any CPU.Build.0 = Release|Any CPU
28+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Release|x64.ActiveCfg = Release|Any CPU
29+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Release|x64.Build.0 = Release|Any CPU
30+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Release|x86.ActiveCfg = Release|Any CPU
31+
{D8768F5F-B5BE-421F-ABFD-E102F6A1192E}.Release|x86.Build.0 = Release|Any CPU
32+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
34+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Debug|x64.ActiveCfg = Debug|Any CPU
35+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Debug|x64.Build.0 = Debug|Any CPU
36+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Debug|x86.ActiveCfg = Debug|Any CPU
37+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Debug|x86.Build.0 = Debug|Any CPU
38+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
39+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Release|Any CPU.Build.0 = Release|Any CPU
40+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Release|x64.ActiveCfg = Release|Any CPU
41+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Release|x64.Build.0 = Release|Any CPU
42+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Release|x86.ActiveCfg = Release|Any CPU
43+
{4342E2EC-DB14-4EED-A067-1CB0CA0AFEC5}.Release|x86.Build.0 = Release|Any CPU
44+
EndGlobalSection
45+
GlobalSection(SolutionProperties) = preSolution
46+
HideSolutionNode = FALSE
47+
EndGlobalSection
48+
EndGlobal
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using DependencyInjectionDemo.Services;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
namespace DependencyInjectionDemo.Controllers;
5+
6+
public class GameController : Controller
7+
{
8+
private readonly ILogger<GameController> _logger;
9+
private readonly IPlayerGenerator _playerGenerator;
10+
11+
public GameController(ILogger<GameController> logger, IPlayerGenerator playerGenerator)
12+
{
13+
_logger = logger;
14+
_playerGenerator = playerGenerator;
15+
}
16+
17+
public IActionResult Index()
18+
{
19+
var newPlayer = _playerGenerator.CreateNewPlayer();
20+
_logger.LogInformation("Created a new player: {Name}", newPlayer.Name);
21+
22+
return View(newPlayer);
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Diagnostics;
2+
using Microsoft.AspNetCore.Mvc;
3+
using DependencyInjectionDemo.Models;
4+
5+
namespace DependencyInjectionDemo.Controllers;
6+
7+
public class HomeController : Controller
8+
{
9+
public IActionResult Index()
10+
{
11+
return View();
12+
}
13+
14+
public IActionResult Privacy()
15+
{
16+
return View();
17+
}
18+
19+
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
20+
public IActionResult Error()
21+
{
22+
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
23+
}
24+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace DependencyInjectionDemo.Models;
2+
3+
public class ErrorViewModel
4+
{
5+
public string? RequestId { get; set; }
6+
7+
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
8+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace DependencyInjectionDemo.Models;
2+
3+
public class Player
4+
{
5+
public required string Name { get; set; }
6+
public Gender Gender { get; set; }
7+
public int Age { get; set; }
8+
public HairColor HairColor { get; set; }
9+
public int Strength { get; set; }
10+
public required string Race { get; set; }
11+
}
12+
13+
public enum Gender
14+
{
15+
Male = 0,
16+
Female = 1
17+
}
18+
19+
public enum HairColor
20+
{
21+
Blue = 0,
22+
Black = 1,
23+
Brown = 2,
24+
Green = 3,
25+
Pink = 4,
26+
White = 5,
27+
Orange = 6
28+
}

0 commit comments

Comments
 (0)