Skip to content

Commit a1e11fa

Browse files
Add a sample to show interactions between VSSDK and VisualStudio.Extensibility portions of an in-proc extension (#535)
* Add a sample to show interactions between VSSDK and VisualStudio.Extensibility portions of an in-proc extension
1 parent b0f8b04 commit a1e11fa

File tree

16 files changed

+554
-17
lines changed

16 files changed

+554
-17
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"AsyncPackageAndMEF.InteractWithAsyncPackageCommand.DisplayName": "Interact with AsyncPackage",
3+
"AsyncPackageAndMEF.InteractWithMEFComponentCommand.DisplayName": "Interact with MEF Component"
4+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>net472</TargetFrameworks>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
<LangVersion>12</LangVersion>
7+
8+
<VssdkCompatibleExtension>true</VssdkCompatibleExtension>
9+
<GeneratePkgDefFile>true</GeneratePkgDefFile>
10+
<UseCodebase>true</UseCodebase>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.14.40254" PrivateAssets="all" />
15+
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.14.40254" PrivateAssets="all" />
16+
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.6.36389" ExcludeAssets="runtime" />
17+
</ItemGroup>
18+
</Project>
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
---
2+
title: In-proc VisualStudio.Extensibility extension with MEF components and AsyncPackage sample
3+
description: A reference sample for how VisualStudio.Extensibility in-proc extensions can include MEF components and AsyncPackages.
4+
date: 2025-11-07
5+
---
6+
7+
# Walkthrough: In-proc VisualStudio.Extensibility extension with MEF components and AsyncPackage sample
8+
9+
This is sample of how to write an in-proc VisualStudio.Extensibility extensions that also includes components
10+
that are common in VSSDK extensions: `AsyncPackage`s and MEF components.
11+
12+
Many functions of the `AsyncPackage`, like providing commands and tool windows, have a more modern
13+
alternative in VisualStudio.Extensibility APIs. Similarly functionalities, like classifications and taggers,
14+
that used to require MEF have now easier to use alternatives provided by VisualStudio.Extensibility APIs.
15+
This sample is not meant as an invitation for extenders to use `AsyncPackage`s and MEF, but as instructions
16+
on how to correctly interact with `AsyncPackage`s and MEF components in those cases when an extender needs to
17+
rely on APIs that don't have a VisualStudio.Extensibility alternative.
18+
19+
When creating such an extension, there is one important considerations to keep in mind: the
20+
VisualStudio.Extensibility extension class, the MEF component and the AsyncPackage are all initialized
21+
independently from each others. Before any interaction between them, the initiator must ensure that the other
22+
component is initialized.
23+
24+
We start with an empty in-proc VisualStudio.Extensibility extension project as described
25+
[here](https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/get-started/in-proc-extensions).
26+
27+
## Adding an AsyncPackage
28+
29+
In a VSSDK extension, the `AsyncPackage` acts as the main entry point for the extension. This is similar to how
30+
the `Extension` class acts as the main entry point for a VisualStudio.Extensibility extension. The `AsyncPackage`
31+
allows the extension to provide many functionalities, including providing VS services.
32+
33+
To add an `AsyncPackage` to the extension, we need to add a new class that derives from `AsyncPackage` and override
34+
the necessary methods. We will also define a new service (`MyService`) that will be provided by the package. This
35+
service will allow the VisualStudio.Extensibility `Extension` to interact with the package guaranteeing that the
36+
necessary initialization is performed.
37+
38+
```cs
39+
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
40+
[Guid(MyPackage.PackageGuidString)]
41+
[ProvideService(typeof(MyService), IsAsyncQueryable = true)]
42+
public sealed class MyPackage : AsyncPackage
43+
{
44+
public const string PackageGuidString = "ac1de0e2-bc69-4a63-bb7e-15f3274448c7";
45+
46+
protected override Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
47+
{
48+
this.AddService(
49+
typeof(MyService),
50+
(container, cancellationToken, serviceType) => Task.FromResult<object>(new MyService()),
51+
promote: true);
52+
53+
return Task.CompletedTask;
54+
}
55+
}
56+
```
57+
58+
We also need to update the project to generate a pkgdef file for the extension:
59+
60+
```xml
61+
<PropertyGroup>
62+
<GeneratePkgDefFile>true</GeneratePkgDefFile>
63+
<UseCodebase>true</UseCodebase>
64+
</PropertyGroup>
65+
```
66+
67+
And add the corresponding asset to the `.vsixmanifest` file:
68+
69+
```xml
70+
<Assets>
71+
<Asset Type="Microsoft.VisualStudio.VsPackage" Path="AsyncPackageAndMEF.pkgdef" />
72+
</Assets>
73+
```
74+
75+
With this setup, we can add any VSSDK functionality to the `AsyncPackage` and use the `MyService` to expose
76+
it to the VisualStudio.Extensibility part of the extension.
77+
78+
For example, we can add a command like this:
79+
80+
```cs
81+
[VisualStudioContribution]
82+
internal class InteractWithAsyncPackageCommand : Command
83+
{
84+
private readonly AsyncServiceProviderInjection<MyService, MyService> myServiceInjection;
85+
86+
public InteractWithAsyncPackageCommand(AsyncServiceProviderInjection<MyService, MyService> myServiceInjection)
87+
{
88+
this.myServiceInjection = myServiceInjection;
89+
}
90+
91+
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
92+
{
93+
var myService = await this.myServiceInjection.GetServiceAsync();
94+
// Use myService
95+
}
96+
97+
...
98+
```
99+
100+
By debugging the extension, we can verify that the `AsyncPackage` is only initialized when the command requests
101+
`MyService` by calling `GetServiceAsync`.
102+
103+
## Adding a MEF component
104+
105+
To add a MEF component to the extension, we need to create a new class that is decorated with the `Export`
106+
attribute.
107+
108+
```cs
109+
[Export(typeof(MyMEFComponent))]
110+
internal class MyMEFComponent
111+
{
112+
}
113+
```
114+
115+
We can also add code to the `MyMEFComponent` to import other MEF components as needed (but we won't do it in
116+
this example to keep it simple).
117+
118+
We must also add an asset to the `.vsixmanifest` file to declare that the extension contains MEF components:
119+
```xml
120+
<Assets>
121+
<Asset Type="Microsoft.VisualStudio.MefComponent" Path="AsyncPackageAndMEF.dll" />
122+
</Assets>
123+
```
124+
125+
Now we can retrieve the MEF component from the VisualStudio.Extensibility part of the extension:
126+
127+
```cs
128+
[VisualStudioContribution]
129+
internal class InteractWithMEFComponentCommand : Command
130+
{
131+
private readonly MefInjection<MyMEFComponent> myMefComponentInjection;
132+
133+
public InteractWithMEFComponentCommand(MefInjection<MyMEFComponent> myMefComponentInjection)
134+
{
135+
this.myMefComponentInjection = myMefComponentInjection;
136+
}
137+
138+
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
139+
{
140+
var myMefComponent = await this.myMefComponentInjection.GetServiceAsync();
141+
// Use myMefComponent
142+
}
143+
144+
...
145+
```
146+
147+
By debugging the extension, we can verify that `MyMEFComponent` is only initialized when the command requests
148+
is by calling `GetServiceAsync`. We can also verify that the `AsyncPackage` is not initialized when the MEF
149+
component is requested: MEF components and AsyncPackages are independent from each other.
150+
151+
## Communicating from the AsyncPackage to the VisualStudio.Extensibility extension
152+
153+
We have discussed how the VisualStudio.Extensibility part of an extension can interact with the VSSDK part
154+
(`AsyncPackage` and MEF components). However, there might be scenarios where the `AsyncPackage` or a MEF
155+
component need to interact with the VisualStudio.Extensibility part of the extension.
156+
157+
To achieve this we can expose a brokered service:
158+
159+
```cs
160+
public interface IMyBrokeredService
161+
{
162+
public static class Configuration
163+
{
164+
public const string ServiceName = "AsyncPackageAndMEF.MyBrokeredService";
165+
public static readonly Version ServiceVersion = new(1, 0);
166+
167+
public static readonly ServiceMoniker ServiceMoniker = new(ServiceName, ServiceVersion);
168+
169+
public static ServiceRpcDescriptor ServiceDescriptor => new ServiceJsonRpcDescriptor(
170+
ServiceMoniker,
171+
ServiceJsonRpcDescriptor.Formatters.MessagePack,
172+
ServiceJsonRpcDescriptor.MessageDelimiters.BigEndianInt32LengthHeader);
173+
}
174+
175+
// Add public methods as needed
176+
...
177+
}
178+
179+
[VisualStudioContribution]
180+
internal class MyBrokeredService : IMyBrokeredService
181+
{
182+
private readonly VisualStudioExtensibility extensibility;
183+
184+
private ProgressReporter? progressReporter;
185+
186+
public MyBrokeredService(VisualStudioExtensibility extensibility)
187+
{
188+
this.extensibility = extensibility;
189+
}
190+
191+
[VisualStudioContribution]
192+
public static BrokeredServiceConfiguration BrokeredServiceConfiguration
193+
=> new(IMyBrokeredService.Configuration.ServiceName, IMyBrokeredService.Configuration.ServiceVersion, typeof(MyBrokeredService))
194+
{
195+
ServiceAudience = BrokeredServiceAudience.Local,
196+
};
197+
198+
...
199+
}
200+
```
201+
202+
and update the `Extension` class to register the service:
203+
```cs
204+
protected override void InitializeServices(IServiceCollection serviceCollection)
205+
{
206+
serviceCollection.ProfferBrokeredService(MyBrokeredService.BrokeredServiceConfiguration, IMyBrokeredService.Configuration.ServiceDescriptor);
207+
base.InitializeServices(serviceCollection);
208+
}
209+
```
210+
211+
Now the `AsyncPackage` can retrieve a [proxy of the brokered service](https://microsoft.github.io/vs-streamjsonrpc/docs/proxies.html#proxy-traits) and interact with it:
212+
213+
```cs
214+
var serviceBrokerContainer = await this.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
215+
var serviceBroker = serviceBrokerContainer.GetFullAccessServiceBroker();
216+
IMyBrokeredService? myBrokeredServiceProxy = null;
217+
try
218+
{
219+
myBrokeredServiceProxy = await serviceBroker.GetProxyAsync<IMyBrokeredService>(IMyBrokeredService.Configuration.ServiceDescriptor, this.DisposalToken);
220+
/// Use myBrokeredServiceProxy
221+
}
222+
finally
223+
{
224+
(myBrokeredServiceProxy as IDisposable)?.Dispose();
225+
}
226+
```
227+
228+
If we wanted to do the same from some other part of the VSSDK extension where the `AsyncPackage` instance is
229+
not readily available, we could modify the code above to retrieve the `IBrokeredServiceContainer` from the global
230+
service provider:
231+
232+
```cs
233+
var serviceBrokerContainer = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
234+
```
235+
236+
By debugging the extension, we can verify that the VisualStudio.Extensibility part of the extension (the
237+
`Extension` class) is only initialized when the brokered service proxy is retrieved by calling `GetProxyAsync`.
238+
239+
This is the proper way for the VSSDK part of the extension to use the VisualStudio.Extensibility features
240+
provided by the `VisualStudioExtensibility` object.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace AsyncPackageAndMEF;
5+
6+
using System.ComponentModel.Composition;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.VisualStudio.Extensibility;
10+
using Microsoft.VisualStudio.Extensibility.Shell;
11+
12+
[Export(typeof(MyMEFComponent))]
13+
internal class MyMEFComponent
14+
{
15+
public MyMEFComponent()
16+
{
17+
}
18+
19+
internal Task SayHelloAsync(VisualStudioExtensibility extensibility, CancellationToken cancellationToken)
20+
=> extensibility.Shell().ShowPromptAsync("Hello from a MEF component!", PromptOptions.OK, cancellationToken);
21+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace AsyncPackageAndMEF;
5+
6+
using System.Runtime.InteropServices;
7+
using System.Threading;
8+
using Microsoft;
9+
using Microsoft.ServiceHub.Framework;
10+
using Microsoft.VisualStudio.Shell;
11+
using Microsoft.VisualStudio.Shell.ServiceBroker;
12+
13+
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
14+
[Guid(MyPackage.PackageGuidString)]
15+
[ProvideService(typeof(MyService), IsAsyncQueryable = true)]
16+
public sealed class MyPackage : AsyncPackage
17+
{
18+
public const string PackageGuidString = "ac1de0e2-bc69-4a63-bb7e-15f3274448c7";
19+
20+
protected override Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
21+
{
22+
this.AddService(
23+
typeof(MyService),
24+
(container, cancellationToken, serviceType) => Task.FromResult<object>(new MyService()),
25+
promote: true);
26+
27+
this.DoSomethingAsync()
28+
.FileAndForget("AsyncPackageAndMEF/MyPackage/DoSomething");
29+
30+
return Task.CompletedTask;
31+
}
32+
33+
private async Task DoSomethingAsync()
34+
{
35+
var serviceBrokerContainer = await this.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
36+
var serviceBroker = serviceBrokerContainer.GetFullAccessServiceBroker();
37+
IMyBrokeredService? myBrokeredServiceProxy = null;
38+
try
39+
{
40+
myBrokeredServiceProxy = await serviceBroker.GetProxyAsync<IMyBrokeredService>(IMyBrokeredService.Configuration.ServiceDescriptor, this.DisposalToken);
41+
42+
Assumes.NotNull(myBrokeredServiceProxy);
43+
await myBrokeredServiceProxy.StartReportingProgressAsync("Doing some work", this.DisposalToken);
44+
45+
// Simulate doing some work.
46+
await Task.Delay(10000, this.DisposalToken);
47+
48+
await myBrokeredServiceProxy.StopReportingProgressAsync(this.DisposalToken);
49+
}
50+
finally
51+
{
52+
(myBrokeredServiceProxy as IDisposable)?.Dispose();
53+
}
54+
}
55+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace AsyncPackageAndMEF;
5+
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.VisualStudio.Extensibility;
9+
using Microsoft.VisualStudio.Extensibility.Shell;
10+
11+
internal class MyService
12+
{
13+
internal Task SayHelloAsync(VisualStudioExtensibility extensibility, CancellationToken cancellationToken)
14+
=> extensibility.Shell().ShowPromptAsync("Hello from a VS service!", PromptOptions.OK, cancellationToken);
15+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace AsyncPackageAndMEF;
5+
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.VisualStudio.Extensibility;
8+
9+
[VisualStudioContribution]
10+
public class ExtensionEntrypoint : Extension
11+
{
12+
public override ExtensionConfiguration ExtensionConfiguration => new()
13+
{
14+
RequiresInProcessHosting = true,
15+
};
16+
17+
protected override void InitializeServices(IServiceCollection serviceCollection)
18+
{
19+
serviceCollection.ProfferBrokeredService(MyBrokeredService.BrokeredServiceConfiguration, IMyBrokeredService.Configuration.ServiceDescriptor);
20+
base.InitializeServices(serviceCollection);
21+
}
22+
}

0 commit comments

Comments
 (0)