-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ability to use injected IoC services within the IHost integrated model
- Loading branch information
1 parent
050f5e1
commit 26c5804
Showing
10 changed files
with
218 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Using IoC Services | ||
|
||
Very frequently, folks have wanted to either use services from their IoC/DI container for their application, or to | ||
have Oakton resolve the command objects from the application's DI container. New in Oakton 6.2 is that very ability. | ||
|
||
## Injecting Services into Commands | ||
|
||
If you are using [Oakton's IHost integration](/oakton/guide/host/integration_with_i_host), you can write commands that | ||
use IoC services by simply decorating a publicly settable property on your Oakton command classes with the | ||
new `[InjectService]` attribute. | ||
|
||
First though, just to make sure you're clear about when and when this isn't applicable, this applies to Oakton used | ||
from an `IHostBuilder` or `ApplicationBuilder` like so: | ||
|
||
snippet: sample_using_ihost_activation | ||
|
||
Then you can decorate your command types something like this: | ||
|
||
snippet: sample_MyDbCommand | ||
|
||
## Using IoC Command Creators | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using System; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Hosting; | ||
using Oakton.Help; | ||
|
||
namespace Oakton; | ||
|
||
internal class HostWrapperCommand : IOaktonCommand | ||
{ | ||
private readonly IOaktonCommand _inner; | ||
private readonly Func<IHost> _hostSource; | ||
private readonly PropertyInfo[] _props; | ||
|
||
public HostWrapperCommand(IOaktonCommand inner, Func<IHost> hostSource, PropertyInfo[] props) | ||
{ | ||
_inner = inner; | ||
_hostSource = hostSource; | ||
_props = props; | ||
} | ||
|
||
public Type InputType => _inner.InputType; | ||
public UsageGraph Usages => _inner.Usages; | ||
public async Task<bool> Execute(object input) | ||
{ | ||
using var host = _hostSource(); | ||
using var scope = host.Services.CreateScope(); | ||
foreach (var prop in _props) | ||
{ | ||
var serviceType = prop.PropertyType; | ||
var service = scope.ServiceProvider.GetRequiredService(serviceType); | ||
prop.SetValue(_inner, service); | ||
} | ||
|
||
return await _inner.Execute(input); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System; | ||
|
||
namespace Oakton; | ||
|
||
/// <summary> | ||
/// Decorate Oakton commands that are being called by | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Property)] | ||
public class InjectServiceAttribute : Attribute | ||
{ | ||
|
||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Hosting; | ||
using Oakton; | ||
using Oakton.Help; | ||
using Shouldly; | ||
using Xunit; | ||
|
||
[assembly: OaktonCommandAssembly] | ||
|
||
namespace Tests; | ||
|
||
public class using_injected_services | ||
{ | ||
[Fact] | ||
public async Task can_use_injected_services() | ||
{ | ||
var success = await Host.CreateDefaultBuilder() | ||
.ConfigureServices(services => | ||
{ | ||
services.AddScoped<MyService>(); | ||
services.AddScoped<OtherService>(); | ||
}) | ||
.RunOaktonCommands(new string[] { "injected", "Bob Marley" }); | ||
|
||
success.ShouldBe(0); | ||
|
||
MyService.WasCalled.ShouldBeTrue(); | ||
MyService.Name.ShouldBe("Bob Marley"); | ||
MyService.WasDisposed.ShouldBeTrue(); | ||
|
||
OtherService.WasCalled.ShouldBeTrue(); | ||
OtherService.Name.ShouldBe("Bob Marley"); | ||
OtherService.WasDisposed.ShouldBeTrue(); | ||
|
||
} | ||
} | ||
|
||
public class InjectedInput | ||
{ | ||
public string Name { get; set; } | ||
} | ||
|
||
[Description("Injected command", Name = "injected")] | ||
public class InjectedCommand : OaktonCommand<InjectedInput> | ||
{ | ||
[InjectService] | ||
public MyService One { get; set; } | ||
|
||
[InjectService] | ||
public OtherService Two { get; set; } | ||
|
||
public override bool Execute(InjectedInput input) | ||
{ | ||
One.DoStuff(input.Name); | ||
Two.DoStuff(input.Name); | ||
|
||
return true; | ||
} | ||
} | ||
|
||
public class MyService : IDisposable | ||
{ | ||
public static bool WasCalled; | ||
public static string Name; | ||
|
||
public static bool WasDisposed; | ||
|
||
public void DoStuff(string name) | ||
{ | ||
WasCalled = true; | ||
Name = name; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
WasDisposed = true; | ||
} | ||
} | ||
|
||
public class OtherService : IDisposable | ||
{ | ||
public static bool WasCalled; | ||
public static string Name; | ||
|
||
public static bool WasDisposed; | ||
|
||
public void DoStuff(string name) | ||
{ | ||
WasCalled = true; | ||
Name = name; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
WasDisposed = true; | ||
} | ||
} | ||
|
||
public class MyDbContext{} | ||
|
||
public class MyInput | ||
{ | ||
|
||
} | ||
|
||
#region sample_MyDbCommand | ||
|
||
public class MyDbCommand : OaktonAsyncCommand<MyInput> | ||
{ | ||
[InjectService] | ||
public MyDbContext DbContext { get; set; } | ||
|
||
public override Task<bool> Execute(MyInput input) | ||
{ | ||
// do stuff with DbContext from up above | ||
return Task.FromResult(true); | ||
} | ||
} | ||
|
||
#endregion |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters