Skip to content

Commit 8fe11de

Browse files
Added the ability to use commands that are registered into the DI container and instantiated by the DI container.
1 parent e10cf98 commit 8fe11de

14 files changed

+135
-72
lines changed

Community.VisualStudio.Toolkit.DependencyInjection.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
88
appveyor.yml = appveyor.yml
99
src\Directory.Build.props = src\Directory.Build.props
1010
src\Directory.Build.targets = src\Directory.Build.targets
11+
nuget.config = nuget.config
1112
README.md = README.md
1213
EndProjectSection
1314
EndProject

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ public sealed class TestExtensionPackage : MicrosoftDIToolkitPackage<TestExtensi
2525
{
2626
// Register your services here
2727
services.AddSingleton<IYourService, YourService>();
28+
29+
// Register any commands. They can be registered as a 'Singleton' or 'Scoped'.
30+
// 'Transient' will work but in practice it will behave the same as 'Scoped'.
31+
services.AddSingleton<YourCommand>();
32+
33+
// Alternatively, you can use the 'RegisterCommands' extension method to automatically register all commands in an assembly.
34+
services.RegisterCommands(ServiceLifetime.Singleton);
2835
...
2936
}
3037

@@ -41,6 +48,67 @@ public sealed class TestExtensionPackage : MicrosoftDIToolkitPackage<TestExtensi
4148

4249
```
4350

51+
## Commands
52+
**Note**: Your commands ***MUST*** inherit from the `BaseDICommand` in order for them to work with the dependency injection system.
53+
54+
55+
### Example Command
56+
```csharp
57+
[Command(PackageIds.MyCommand)]
58+
public class MyCommand: BaseDICommand
59+
{
60+
// This is passed in through the constructor
61+
private readonly SomeSingletonObject _singletonObject;
62+
63+
public DependencyInjectionCommand(
64+
DIToolkitPackage package,
65+
SomeSingletonObject singletonObject)
66+
: base(package)
67+
{
68+
this._singletonObject = singletonObject;
69+
}
70+
71+
protected async override Task ExecuteAsync(OleMenuCmdEventArgs e)
72+
{
73+
// Your execution logic here. This is executed in the context of a new scope.
74+
...
75+
}
76+
77+
protected override void BeforeQueryStatus(EventArgs e)
78+
{
79+
// Your query logic here. This is executed in the context of a new scope.
80+
...
81+
}
82+
}
83+
```
84+
85+
### Registering Your Commands
86+
87+
You can register your commands in the DI container just like any other service.
88+
Your commands will be instantiated at the time of invocation and the lifetime with which they were registered will be followed.
89+
For example, if a command was registered as a singleton, then the same instance will be returned every time the command is invoked.
90+
If a command is registered as `Scoped`, then a new instance will be instantiated every time the command is invoked, including when the `BeforeQueryStatus` message is called.
91+
92+
```csharp
93+
protected override void InitializeServices(IServiceCollection services)
94+
{
95+
...
96+
97+
// Register any commands. They can be registered as a 'Singleton' or 'Scoped'.
98+
// 'Transient' will work but in practice it will behave the same as 'Scoped'.
99+
services.AddSingleton<YourCommand>();
100+
101+
// Alternatively, you can use the 'RegisterCommands' extension method to automatically register all commands in an assembly.
102+
services.RegisterCommands(ServiceLifetime.Singleton);
103+
104+
...
105+
}
106+
```
107+
108+
109+
Every call to the methods on your command use their own scope.
110+
What that means is that any services retrieved from the DI container will be retrieved from that scoped container.
111+
44112
## Retrieving the `IServiceProvider` from the main VS Service Provider
45113

46114
As part of the initialization process, the DI container is registered into Visual Studio's container as well.

nuget.config

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<packageSources>
4+
<add key="nuget.org" value="https://www.nuget.org/api/v2/" />
5+
<add key="Toolkit CI" value="https://ci.appveyor.com/nuget/community-visualstudio-toolkit" />
6+
</packageSources>
7+
</configuration>

src/Core/14.0/Community.VisualStudio.Toolkit.DependencyInjection.Core.14.0.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Community.VisualStudio.Toolkit.14" Version="14.0.394" />
10+
<PackageReference Include="Community.VisualStudio.Toolkit.14" Version="14.0.414" />
1111
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
1212
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
1313
</ItemGroup>

src/Core/15.0/Community.VisualStudio.Toolkit.DependencyInjection.Core.15.0.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Community.VisualStudio.Toolkit.15" Version="15.0.394" />
10+
<PackageReference Include="Community.VisualStudio.Toolkit.15" Version="15.0.414" />
1111
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="1.1.1" />
1212
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
1313
</ItemGroup>

src/Core/16.0/Community.VisualStudio.Toolkit.DependencyInjection.Core.16.0.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Community.VisualStudio.Toolkit.16" Version="16.0.394" />
10+
<PackageReference Include="Community.VisualStudio.Toolkit.16" Version="16.0.414" />
1111
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
1212
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
1313
</ItemGroup>

src/Core/17.0/Community.VisualStudio.Toolkit.DependencyInjection.Core.17.0.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Community.VisualStudio.Toolkit.17" Version="17.0.394" />
10+
<PackageReference Include="Community.VisualStudio.Toolkit.17" Version="17.0.414" />
1111
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
1212
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
1313
</ItemGroup>

src/Core/Shared/BaseDICommand.cs

Lines changed: 4 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,24 @@
1-
using System;
2-
using System.Reflection;
3-
using Microsoft.Extensions.DependencyInjection;
1+
using Microsoft.Extensions.DependencyInjection;
42
using Microsoft.VisualStudio.Shell;
5-
using Task = System.Threading.Tasks.Task;
63

74
namespace Community.VisualStudio.Toolkit.DependencyInjection.Core
85
{
96
/// <summary>
107
/// Base class used for commands instantiated by the DI container.
118
/// </summary>
12-
public abstract class BaseDICommand
9+
public abstract class BaseDICommand : BaseCommand
1310
{
14-
private static readonly PropertyInfo _commandPropertyInfo = typeof(CommandWrapper<>).GetProperty(nameof(Command));
15-
16-
/// <summary>
17-
/// The package.
18-
/// </summary>
19-
public DIToolkitPackage Package { get; }
20-
21-
/// <summary>
22-
/// The command object associated with the command ID (GUID/ID).
23-
/// </summary>
24-
public OleMenuCommand Command { get; }
25-
2611
/// <summary>
27-
/// Constructor
12+
/// Constructor for the BaseDICommand
2813
/// </summary>
2914
/// <param name="package"></param>
3015
public BaseDICommand(DIToolkitPackage package)
3116
{
3217
this.Package = package;
3318
var commandWrapperType = typeof(CommandWrapper<>).MakeGenericType(this.GetType());
3419
var commandWrapper = package.ServiceProvider.GetRequiredService(commandWrapperType);
35-
PropertyInfo commandPropertyInfo = commandWrapperType.GetProperty(nameof(Command));
20+
var commandPropertyInfo = commandWrapperType.GetProperty(nameof(BaseCommand.Command));
3621
this.Command = (OleMenuCommand)commandPropertyInfo.GetValue(commandWrapper, null);
3722
}
38-
39-
/// <summary>
40-
/// Execute the command logica
41-
/// </summary>
42-
/// <param name="sender"></param>
43-
/// <param name="e"></param>
44-
protected internal virtual void Execute(object sender, EventArgs e)
45-
{
46-
Package.JoinableTaskFactory.RunAsync(async delegate
47-
{
48-
try
49-
{
50-
await ExecuteAsync((OleMenuCmdEventArgs)e);
51-
}
52-
catch (Exception ex)
53-
{
54-
await ex.LogAsync();
55-
}
56-
}).FireAndForget();
57-
}
58-
59-
/// <summary>Executes asynchronously when the command is invoked and <see cref="Execute(object, EventArgs)"/> isn't overridden.</summary>
60-
/// <remarks>Use this method instead of <see cref="Execute"/> if you're invoking any async tasks by using async/await patterns.</remarks>
61-
protected internal virtual Task ExecuteAsync(OleMenuCmdEventArgs e)
62-
{
63-
return Task.CompletedTask;
64-
}
65-
66-
/// <summary>
67-
/// BeforeQueryStatus
68-
/// </summary>
69-
/// <param name="sender"></param>
70-
/// <param name="e"></param>
71-
protected internal virtual void BeforeQueryStatus(object sender, EventArgs e)
72-
{
73-
BeforeQueryStatus(e);
74-
}
75-
76-
/// <summary>Override this method to control the commands visibility and other properties.</summary>
77-
protected virtual void BeforeQueryStatus(EventArgs e)
78-
{
79-
// Leave empty
80-
}
8123
}
8224
}

src/Core/Shared/CommandWrapper.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.ComponentModel.Design;
33
using System.Linq;
4+
using System.Reflection;
45
using Microsoft.Extensions.DependencyInjection;
56
using Microsoft.VisualStudio.Shell;
67
using Task = System.Threading.Tasks.Task;
@@ -10,6 +11,10 @@ namespace Community.VisualStudio.Toolkit.DependencyInjection.Core
1011
internal class CommandWrapper<T>
1112
where T : BaseDICommand
1213
{
14+
private readonly MethodInfo _beforeQueryStatusMethod = typeof(BaseDICommand).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).First(x => x.Name == "BeforeQueryStatus" && x.GetParameters().Count() == 2);
15+
private readonly MethodInfo _executeMethod = typeof(BaseDICommand).GetMethod("Execute", BindingFlags.Instance | BindingFlags.NonPublic);
16+
private readonly MethodInfo _executeAsyncMethod = typeof(BaseDICommand).GetMethod("ExecuteAsync", BindingFlags.Instance | BindingFlags.NonPublic);
17+
1318
private readonly IServiceProvider _serviceProvider;
1419

1520
public CommandWrapper(IServiceProvider serviceProvider, AsyncPackage package)
@@ -44,21 +49,22 @@ protected void BeforeQueryStatus(object sender, EventArgs e)
4449
{
4550
using var scope = this._serviceProvider.CreateScope();
4651
var instance = (BaseDICommand)scope.ServiceProvider.GetRequiredService(typeof(T));
47-
instance.BeforeQueryStatus(sender, e);
52+
_beforeQueryStatusMethod.Invoke(instance, new object[] { sender, e });
4853
}
4954

5055
protected void Execute(object sender, EventArgs e)
5156
{
5257
using var scope = this._serviceProvider.CreateScope();
5358
var instance = (BaseDICommand)scope.ServiceProvider.GetRequiredService(typeof(T));
54-
instance.Execute(sender, e);
59+
_executeMethod.Invoke(instance, new object[] { sender, e });
5560
}
5661

5762
protected async Task ExecuteAsync(OleMenuCmdEventArgs e)
5863
{
5964
using var scope = this._serviceProvider.CreateScope();
6065
var instance = (BaseDICommand)scope.ServiceProvider.GetRequiredService(typeof(T));
61-
await instance.ExecuteAsync(e);
66+
var executeAsyncTask = (Task)_executeAsyncMethod.Invoke(instance, new object[] { e });
67+
await executeAsyncTask;
6268
}
6369
}
6470
}

src/Core/Shared/Community.VisualStudio.Toolkit.DependencyInjection.Core.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<Compile Include="$(MSBuildThisFileDirectory)BaseDICommand.cs" />
1313
<Compile Include="$(MSBuildThisFileDirectory)CommandWrapper.cs" />
1414
<Compile Include="$(MSBuildThisFileDirectory)DIToolkitPackage.cs" />
15+
<Compile Include="$(MSBuildThisFileDirectory)Extensions.cs" />
1516
<Compile Include="$(MSBuildThisFileDirectory)IToolkitServiceProvider.cs" />
1617
<Compile Include="$(MSBuildThisFileDirectory)SToolkitServiceProvider.cs" />
1718
<Compile Include="$(MSBuildThisFileDirectory)ToolkitServiceProvider.cs" />

0 commit comments

Comments
 (0)