Skip to content

Commit

Permalink
Support keyed services in dependency injection. Fixes #1391
Browse files Browse the repository at this point in the history
Add tests for MySqlConnector.DependencyInjection project.
  • Loading branch information
bgrainger committed Nov 16, 2023
1 parent c5ca41c commit 3c101e5
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 3 deletions.
12 changes: 12 additions & 0 deletions .ci/build-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ steps:
artifactName: 'Conformance.Tests-8.0-$(Agent.OS)'
targetPath: 'artifacts/publish/Conformance.Tests/release_net8.0'

- task: DotNetCoreCLI@2
displayName: 'Publish MySqlConnector.DependencyInjection.Tests'
inputs:
command: 'publish'
arguments: '-c Release -f net8.0 --no-build tests/MySqlConnector.DependencyInjection.Tests/MySqlConnector.DependencyInjection.Tests.csproj'
publishWebProjects: false
zipAfterPublish: false
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'MySqlConnector.DependencyInjection.Tests-8.0-$(Agent.OS)'
targetPath: 'artifacts/publish/MySqlConnector.DependencyInjection.Tests/release_net8.0'

- task: DotNetCoreCLI@2
displayName: 'Publish IntegrationTests (7.0)'
inputs:
Expand Down
10 changes: 10 additions & 0 deletions .ci/mysqlconnector-tests-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ steps:
command: 'custom'
custom: 'vstest'
arguments: 'MySqlConnector.Tests.dll /logger:trx'
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'MySqlConnector.DependencyInjection.Tests-8.0-$(Agent.OS)'
targetPath: $(System.DefaultWorkingDirectory)
- task: DotNetCoreCLI@2
displayName: 'Run MySqlConnector.DependencyInjection.Tests'
inputs:
command: 'custom'
custom: 'vstest'
arguments: 'MySqlConnector.DependencyInjection.Tests.dll /logger:trx'
- task: PublishTestResults@2
inputs:
testResultsFormat: VSTest
Expand Down
6 changes: 6 additions & 0 deletions .ci/test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ if ($LASTEXITCODE -ne 0){
exit $LASTEXITCODE;
}
popd
pushd tests\MySqlConnector.DependencyIntegration.Tests
dotnet test -c Release
if ($LASTEXITCODE -ne 0){
exit $LASTEXITCODE;
}
popd

pushd .\tests\IntegrationTests

Expand Down
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="MySql.Data" Version="8.2.0" />
Expand Down
6 changes: 6 additions & 0 deletions MySqlConnector.sln
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SchemaCollectionGenerator",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlConnector.DependencyInjection", "src\MySqlConnector.DependencyInjection\MySqlConnector.DependencyInjection.csproj", "{D48B3619-7FE1-420C-A96C-B231B7EA73EA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlConnector.DependencyInjection.Tests", "tests\MySqlConnector.DependencyInjection.Tests\MySqlConnector.DependencyInjection.Tests.csproj", "{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -76,6 +78,10 @@ Global
{D48B3619-7FE1-420C-A96C-B231B7EA73EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D48B3619-7FE1-420C-A96C-B231B7EA73EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D48B3619-7FE1-420C-A96C-B231B7EA73EA}.Release|Any CPU.Build.0 = Release|Any CPU
{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E41AD8B7-2F67-444F-A8DC-51C3C8B1FD16}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,45 @@ public static class MySqlConnectorServiceCollectionExtensions
ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton) =>
DoAddMySqlDataSource(serviceCollection, connectionString, dataSourceBuilderAction, connectionLifetime, dataSourceLifetime);

/// <summary>
/// Registers a <see cref="MySqlDataSource" /> and a <see cref="MySqlConnection" /> in the <see cref="IServiceCollection" />.
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <param name="connectionString">A MySQL connection string.</param>
/// <param name="connectionLifetime">The lifetime with which to register the <see cref="MySqlConnection" /> in the container. Defaults to <see cref="ServiceLifetime.Transient" />.</param>
/// <param name="dataSourceLifetime">The lifetime with which to register the <see cref="MySqlDataSource" /> service in the container. Defaults to <see cref="ServiceLifetime.Singleton" />.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
/// <remarks>If the <paramref name="serviceKey"/> is a <see langword="string"/>, it will automatically be used to initialize the data source name.</remarks>
public static IServiceCollection AddKeyedMySqlDataSource(
this IServiceCollection serviceCollection,
object? serviceKey,
string connectionString,
ServiceLifetime connectionLifetime = ServiceLifetime.Transient,
ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton) =>
DoAddMySqlDataSource(serviceCollection, serviceKey, connectionString, dataSourceBuilderAction: null, connectionLifetime, dataSourceLifetime);

/// <summary>
/// Registers a <see cref="MySqlDataSource" /> and a <see cref="MySqlConnection" /> in the <see cref="IServiceCollection" />.
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <param name="connectionString">A MySQL connection string.</param>
/// <param name="dataSourceBuilderAction">An action to configure the <see cref="MySqlDataSourceBuilder" /> for further customizations of the <see cref="MySqlDataSource" />.</param>
/// <param name="connectionLifetime">The lifetime with which to register the <see cref="MySqlConnection" /> in the container. Defaults to <see cref="ServiceLifetime.Transient" />.</param>
/// <param name="dataSourceLifetime">The lifetime with which to register the <see cref="MySqlDataSource" /> service in the container. Defaults to <see cref="ServiceLifetime.Singleton" />.</param>
/// <returns>The same service collection so that multiple calls can be chained.</returns>
/// <remarks>If the <paramref name="serviceKey"/> is a <see langword="string"/>, it will automatically be used to initialize the data source name; this can
/// be overridden by the <paramref name="dataSourceBuilderAction"/> configuration action.</remarks>
public static IServiceCollection AddKeyedMySqlDataSource(
this IServiceCollection serviceCollection,
object? serviceKey,
string connectionString,
Action<MySqlDataSourceBuilder> dataSourceBuilderAction,
ServiceLifetime connectionLifetime = ServiceLifetime.Transient,
ServiceLifetime dataSourceLifetime = ServiceLifetime.Singleton) =>
DoAddMySqlDataSource(serviceCollection, serviceKey, connectionString, dataSourceBuilderAction, connectionLifetime, dataSourceLifetime);

private static IServiceCollection DoAddMySqlDataSource(
this IServiceCollection serviceCollection,
string connectionString,
Expand All @@ -52,10 +91,10 @@ public static class MySqlConnectorServiceCollectionExtensions
serviceCollection.TryAdd(
new ServiceDescriptor(
typeof(MySqlDataSource),
x =>
serviceProvider =>
{
var dataSourceBuilder = new MySqlDataSourceBuilder(connectionString)
.UseLoggerFactory(x.GetService<ILoggerFactory>());
.UseLoggerFactory(serviceProvider.GetService<ILoggerFactory>());
dataSourceBuilderAction?.Invoke(dataSourceBuilder);
return dataSourceBuilder.Build();
},
Expand All @@ -71,4 +110,37 @@ public static class MySqlConnectorServiceCollectionExtensions

return serviceCollection;
}

private static IServiceCollection DoAddMySqlDataSource(
this IServiceCollection serviceCollection,
object? serviceKey,
string connectionString,
Action<MySqlDataSourceBuilder>? dataSourceBuilderAction,
ServiceLifetime connectionLifetime,
ServiceLifetime dataSourceLifetime)
{
serviceCollection.TryAdd(
new ServiceDescriptor(
typeof(MySqlDataSource),
serviceKey,
(serviceProvider, serviceKey) =>
{
var dataSourceBuilder = new MySqlDataSourceBuilder(connectionString)
.UseLoggerFactory(serviceProvider.GetService<ILoggerFactory>())
.UseName(serviceKey as string);
dataSourceBuilderAction?.Invoke(dataSourceBuilder);
return dataSourceBuilder.Build();
},
dataSourceLifetime));

serviceCollection.TryAdd(new ServiceDescriptor(typeof(MySqlConnection), serviceKey, (sp, sk) => sp.GetRequiredKeyedService<MySqlDataSource>(sk).CreateConnection(), connectionLifetime));

#if NET7_0_OR_GREATER
serviceCollection.TryAdd(new ServiceDescriptor(typeof(DbDataSource), serviceKey, (sp, sk) => sp.GetRequiredKeyedService<MySqlDataSource>(sk), dataSourceLifetime));
#endif

serviceCollection.TryAdd(new ServiceDescriptor(typeof(DbConnection), serviceKey, (sp, sk) => sp.GetRequiredKeyedService<MySqlConnection>(sk), connectionLifetime));

return serviceCollection;
}
}
30 changes: 30 additions & 0 deletions src/MySqlConnector.DependencyInjection/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,33 @@ builder.Services.AddMySqlDataSource("Server=server;User ID=test;Password=test;Da
x => x.UseRemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) => { /* custom logic */ })
);
```

## Keyed Services

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).
This is useful if you have multiple connection strings or need to connect to multiple databases.
If the service key is a string, it will automatically be used as the `MySqlDataSource` name;
to customize this, call the `AddKeyedMySqlDataSource(object?, string, Action<MySqlDataSourceBuilder>)` overload and call `MySqlDataSourceBuilder.UseName`.

```csharp
builder.Services.AddKeyedMySqlDataSource("users", builder.Configuration.GetConnectionString("Users"));
builder.Services.AddKeyedMySqlDataSource("products", builder.Configuration.GetConnectionString("Products"));

app.MapGet("/users/{userId}", async (int userId, [FromKeyedServices("users")] MySqlConnection connection) =>
{
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText = "SELECT name FROM users WHERE user_id = @userId LIMIT 1";
command.Parameters.AddWithValue("@userId", userId);
return $"Hello, {await command.ExecuteScalarAsync()}";
});

app.MapGet("/products/{productId}", async (int productId, [FromKeyedServices("products")] MySqlConnection connection) =>
{
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText = "SELECT name FROM products WHERE product_id = @productId LIMIT 1";
command.Parameters.AddWithValue("@productId", productId);
return await command.ExecuteScalarAsync();
});
```
1 change: 1 addition & 0 deletions src/MySqlConnector/MySqlConnector.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="MySqlConnector.DependencyInjection.Tests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
<InternalsVisibleTo Include="MySqlConnector.Tests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
<InternalsVisibleTo Include="IntegrationTests" Key="00240000048000001402000006020000002400005253413100100000010001000521c81bf0f0ec7b261bb89bb583611d3767205d542c16c9353e317455acf612d3ec3dd03b77e7e6fda1aa8f15c58576d90dae0fb9f4fd4bd48709ae199b8c771963fa67d70b35f7ed2fbb6c60423935adfae0606716ea6ce31a1fcd56fdb206fc0c3b1205ec6ba56fb20c14c42105a601ddd0bfaea7207d535b29a39ffe82f00880f4f64f86e6bcf26eb5242a133bad9d7a32e3126036b68b13b413ce4097dfc18d9a5b1e494f1aed54dc84d7089fd0d931a49e679fdc7c8f07a5121df38ec27c2c9993a8f8f136b2937849aed32aef7324a5b7e482dc2eb693c7988f6074e82e75a41dd001587be4d79108588b25d40ed9aeb30ff921edaf509c94f71428e48219ba940f5f10c061421dc0c006e09feadec30df20b2d13d02c3ce4ceb32b6fbefd254288d45f3bb2c425b197e19699d7efdfc7aba5dd45b727bc98abd866d2f6e69e33a64e4b5a5ab1e4d749266c7bf285550da9fb036f10eff76b697de9c5ed8de4a3cdbca1174543540bed6c3a95641cfdacbac834896639f8a75ed1fb9cfd9983d83d0b43b76bd3894bd2b3da0dd23d1e0362985217f087acce1a7f56546c214890acae8fc60e27890ff31c38578f85e220342061a1a5c867362a14aafdffa003dc13af064f5f860d1757883ea5237feed3a6228c86200062bd88f5592d5c399ef270a562d458ae8eac5eaa382b5bcc3f64298cc34b4598f0b33d7943b8" />
<Using Include="System.Data" />
Expand Down

0 comments on commit 3c101e5

Please sign in to comment.