Skip to content

Commit cd76fd3

Browse files
committed
Support keyed services in dependency injection. Fixes #1391
Add tests for MySqlConnector.DependencyInjection project.
1 parent c5ca41c commit cd76fd3

File tree

10 files changed

+348
-3
lines changed

10 files changed

+348
-3
lines changed

.ci/build-steps.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ steps:
4747
artifactName: 'Conformance.Tests-8.0-$(Agent.OS)'
4848
targetPath: 'artifacts/publish/Conformance.Tests/release_net8.0'
4949

50+
- task: DotNetCoreCLI@2
51+
displayName: 'Publish MySqlConnector.DependencyInjection.Tests'
52+
inputs:
53+
command: 'publish'
54+
arguments: '-c Release -f net8.0 --no-build tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj'
55+
publishWebProjects: false
56+
zipAfterPublish: false
57+
- task: PublishPipelineArtifact@0
58+
inputs:
59+
artifactName: 'MySqlConnector.DependencyInjection.Tests-8.0-$(Agent.OS)'
60+
targetPath: 'artifacts/publish/MySqlConnector.DependencyInjection.Tests/release_net8.0'
61+
5062
- task: DotNetCoreCLI@2
5163
displayName: 'Publish IntegrationTests (7.0)'
5264
inputs:

.ci/mysqlconnector-tests-steps.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ steps:
1414
command: 'custom'
1515
custom: 'vstest'
1616
arguments: 'MySqlConnector.Tests.dll /logger:trx'
17+
- task: DownloadPipelineArtifact@0
18+
inputs:
19+
artifactName: 'MySqlConnector.DependencyInjection.Tests-8.0-$(Agent.OS)'
20+
targetPath: $(System.DefaultWorkingDirectory)
21+
- task: DotNetCoreCLI@2
22+
displayName: 'Run MySqlConnector.DependencyInjection.Tests'
23+
inputs:
24+
command: 'custom'
25+
custom: 'vstest'
26+
arguments: 'MySqlConnector.DependencyInjection.Tests.dll /logger:trx'
1727
- task: PublishTestResults@2
1828
inputs:
1929
testResultsFormat: VSTest

.ci/test.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ if ($LASTEXITCODE -ne 0){
2323
exit $LASTEXITCODE;
2424
}
2525
popd
26+
pushd tests\MySqlConnector.DependencyInjection.Tests
27+
dotnet test -c Release
28+
if ($LASTEXITCODE -ne 0){
29+
exit $LASTEXITCODE;
30+
}
31+
popd
2632

2733
pushd .\tests\IntegrationTests
2834

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
1313
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
1414
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
15-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
15+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
16+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
1617
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
1718
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
1819
<PackageVersion Include="MySql.Data" Version="8.2.0" />

MySqlConnector.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SchemaCollectionGenerator",
2626
EndProject
2727
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlConnector.DependencyInjection", "src\MySqlConnector.DependencyInjection\MySqlConnector.DependencyInjection.csproj", "{D48B3619-7FE1-420C-A96C-B231B7EA73EA}"
2828
EndProject
29+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlConnector.DependencyInjection.Tests", "tests\MySqlConnector.DependencyInjection.Tests\MySqlConnector.DependencyInjection.Tests.csproj", "{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}"
30+
EndProject
2931
Global
3032
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3133
Debug|Any CPU = Debug|Any CPU
@@ -76,6 +78,10 @@ Global
7678
{D48B3619-7FE1-420C-A96C-B231B7EA73EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
7779
{D48B3619-7FE1-420C-A96C-B231B7EA73EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
7880
{D48B3619-7FE1-420C-A96C-B231B7EA73EA}.Release|Any CPU.Build.0 = Release|Any CPU
81+
{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
82+
{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}.Debug|Any CPU.Build.0 = Debug|Any CPU
83+
{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}.Release|Any CPU.ActiveCfg = Release|Any CPU
84+
{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}.Release|Any CPU.Build.0 = Release|Any CPU
7985
EndGlobalSection
8086
GlobalSection(SolutionProperties) = preSolution
8187
HideSolutionNode = FALSE

src/MySqlConnector.DependencyInjection/MySqlConnectorServiceCollectionExtensions.cs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,45 @@ public static IServiceCollection AddMySqlDataSource(
4242
ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton) =>
4343
DoAddMySqlDataSource(serviceCollection, connectionString, dataSourceBuilderAction, connectionLifetime, dataSourceLifetime);
4444

45+
/// <summary>
46+
/// Registers a <see cref="MySqlDataSource" /> and a <see cref="MySqlConnection" /> in the <see cref="IServiceCollection" />.
47+
/// </summary>
48+
/// <param name="serviceCollection">The <see cref="IServiceCollection" /> to add services to.</param>
49+
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
50+
/// <param name="connectionString">A MySQL connection string.</param>
51+
/// <param name="connectionLifetime">The lifetime with which to register the <see cref="MySqlConnection" /> in the container. Defaults to <see cref="ServiceLifetime.Transient" />.</param>
52+
/// <param name="dataSourceLifetime">The lifetime with which to register the <see cref="MySqlDataSource" /> service in the container. Defaults to <see cref="ServiceLifetime.Singleton" />.</param>
53+
/// <returns>The same service collection so that multiple calls can be chained.</returns>
54+
/// <remarks>If the <paramref name="serviceKey"/> is a <see langword="string"/>, it will automatically be used to initialize the data source name.</remarks>
55+
public static IServiceCollection AddKeyedMySqlDataSource(
56+
this IServiceCollection serviceCollection,
57+
object? serviceKey,
58+
string connectionString,
59+
ServiceLifetime connectionLifetime = ServiceLifetime.Transient,
60+
ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton) =>
61+
DoAddMySqlDataSource(serviceCollection, serviceKey, connectionString, dataSourceBuilderAction: null, connectionLifetime, dataSourceLifetime);
62+
63+
/// <summary>
64+
/// Registers a <see cref="MySqlDataSource" /> and a <see cref="MySqlConnection" /> in the <see cref="IServiceCollection" />.
65+
/// </summary>
66+
/// <param name="serviceCollection">The <see cref="IServiceCollection" /> to add services to.</param>
67+
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
68+
/// <param name="connectionString">A MySQL connection string.</param>
69+
/// <param name="dataSourceBuilderAction">An action to configure the <see cref="MySqlDataSourceBuilder" /> for further customizations of the <see cref="MySqlDataSource" />.</param>
70+
/// <param name="connectionLifetime">The lifetime with which to register the <see cref="MySqlConnection" /> in the container. Defaults to <see cref="ServiceLifetime.Transient" />.</param>
71+
/// <param name="dataSourceLifetime">The lifetime with which to register the <see cref="MySqlDataSource" /> service in the container. Defaults to <see cref="ServiceLifetime.Singleton" />.</param>
72+
/// <returns>The same service collection so that multiple calls can be chained.</returns>
73+
/// <remarks>If the <paramref name="serviceKey"/> is a <see langword="string"/>, it will automatically be used to initialize the data source name; this can
74+
/// be overridden by the <paramref name="dataSourceBuilderAction"/> configuration action.</remarks>
75+
public static IServiceCollection AddKeyedMySqlDataSource(
76+
this IServiceCollection serviceCollection,
77+
object? serviceKey,
78+
string connectionString,
79+
Action<MySqlDataSourceBuilder> dataSourceBuilderAction,
80+
ServiceLifetime connectionLifetime = ServiceLifetime.Transient,
81+
ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton) =>
82+
DoAddMySqlDataSource(serviceCollection, serviceKey, connectionString, dataSourceBuilderAction, connectionLifetime, dataSourceLifetime);
83+
4584
private static IServiceCollection DoAddMySqlDataSource(
4685
this IServiceCollection serviceCollection,
4786
string connectionString,
@@ -52,10 +91,10 @@ private static IServiceCollection DoAddMySqlDataSource(
5291
serviceCollection.TryAdd(
5392
new ServiceDescriptor(
5493
typeof(MySqlDataSource),
55-
x =>
94+
serviceProvider =>
5695
{
5796
var dataSourceBuilder = new MySqlDataSourceBuilder(connectionString)
58-
.UseLoggerFactory(x.GetService<ILoggerFactory>());
97+
.UseLoggerFactory(serviceProvider.GetService<ILoggerFactory>());
5998
dataSourceBuilderAction?.Invoke(dataSourceBuilder);
6099
return dataSourceBuilder.Build();
61100
},
@@ -71,4 +110,37 @@ private static IServiceCollection DoAddMySqlDataSource(
71110

72111
return serviceCollection;
73112
}
113+
114+
private static IServiceCollection DoAddMySqlDataSource(
115+
this IServiceCollection serviceCollection,
116+
object? serviceKey,
117+
string connectionString,
118+
Action<MySqlDataSourceBuilder>? dataSourceBuilderAction,
119+
ServiceLifetime connectionLifetime,
120+
ServiceLifetime dataSourceLifetime)
121+
{
122+
serviceCollection.TryAdd(
123+
new ServiceDescriptor(
124+
typeof(MySqlDataSource),
125+
serviceKey,
126+
(serviceProvider, serviceKey) =>
127+
{
128+
var dataSourceBuilder = new MySqlDataSourceBuilder(connectionString)
129+
.UseLoggerFactory(serviceProvider.GetService<ILoggerFactory>())
130+
.UseName(serviceKey as string);
131+
dataSourceBuilderAction?.Invoke(dataSourceBuilder);
132+
return dataSourceBuilder.Build();
133+
},
134+
dataSourceLifetime));
135+
136+
serviceCollection.TryAdd(new ServiceDescriptor(typeof(MySqlConnection), serviceKey, (sp, sk) => sp.GetRequiredKeyedService<MySqlDataSource>(sk).CreateConnection(), connectionLifetime));
137+
138+
#if NET7_0_OR_GREATER
139+
serviceCollection.TryAdd(new ServiceDescriptor(typeof(DbDataSource), serviceKey, (sp, sk) => sp.GetRequiredKeyedService<MySqlDataSource>(sk), dataSourceLifetime));
140+
#endif
141+
142+
serviceCollection.TryAdd(new ServiceDescriptor(typeof(DbConnection), serviceKey, (sp, sk) => sp.GetRequiredKeyedService<MySqlConnection>(sk), connectionLifetime));
143+
144+
return serviceCollection;
145+
}
74146
}

src/MySqlConnector.DependencyInjection/docs/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,33 @@ builder.Services.AddMySqlDataSource("Server=server;User ID=test;Password=test;Da
4848
x => x.UseRemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => { /* custom logic */ })
4949
);
5050
```
51+
52+
## Keyed Services
53+
54+
Use the `AddKeyedMySqlDataSource` method to register a `MySqlDataSource` as a [keyed service](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#keyed-di-services).
55+
This is useful if you have multiple connection strings or need to connect to multiple databases.
56+
If the service key is a string, it will automatically be used as the `MySqlDataSource` name;
57+
to customize this, call the `AddKeyedMySqlDataSource(object?, string, Action<MySqlDataSourceBuilder>)` overload and call `MySqlDataSourceBuilder.UseName`.
58+
59+
```csharp
60+
builder.Services.AddKeyedMySqlDataSource("users", builder.Configuration.GetConnectionString("Users"));
61+
builder.Services.AddKeyedMySqlDataSource("products", builder.Configuration.GetConnectionString("Products"));
62+
63+
app.MapGet("/users/{userId}", async (int userId, [FromKeyedServices("users")] MySqlConnection connection) =>
64+
{
65+
await connection.OpenAsync();
66+
await using var command = connection.CreateCommand();
67+
command.CommandText = "SELECT name FROM users WHERE user_id = @userId LIMIT 1";
68+
command.Parameters.AddWithValue("@userId", userId);
69+
return $"Hello, {await command.ExecuteScalarAsync()}";
70+
});
71+
72+
app.MapGet("/products/{productId}", async (int productId, [FromKeyedServices("products")] MySqlConnection connection) =>
73+
{
74+
await connection.OpenAsync();
75+
await using var command = connection.CreateCommand();
76+
command.CommandText = "SELECT name FROM products WHERE product_id = @productId LIMIT 1";
77+
command.Parameters.AddWithValue("@productId", productId);
78+
return await command.ExecuteScalarAsync();
79+
});
80+
```

src/MySqlConnector/MySqlConnector.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
</ItemGroup>
3535

3636
<ItemGroup>
37+
<InternalsVisibleTo Include="MySqlConnector.DependencyInjection.Tests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
3738
<InternalsVisibleTo Include="MySqlConnector.Tests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
3839
<InternalsVisibleTo Include="IntegrationTests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
3940
<Using Include="System.Data" />

0 commit comments

Comments
 (0)