Merq.VisualStudio 2.0.0-alpha

Prefix Reserved
This is a prerelease version of Merq.VisualStudio.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Merq.VisualStudio --version 2.0.0-alpha                
NuGet\Install-Package Merq.VisualStudio -Version 2.0.0-alpha                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Merq.VisualStudio" Version="2.0.0-alpha" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Merq.VisualStudio --version 2.0.0-alpha                
#r "nuget: Merq.VisualStudio, 2.0.0-alpha"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Merq.VisualStudio as a Cake Addin
#addin nuget:?package=Merq.VisualStudio&version=2.0.0-alpha&prerelease

// Install Merq.VisualStudio as a Cake Tool
#tool nuget:?package=Merq.VisualStudio&version=2.0.0-alpha&prerelease                

This package provides a MEF-ready customization of the Merq default implementation, which makes it trivial to consume from an application that uses Microsoft.VisualStudio.Composition to load MEF-based components.

This is built-in Visual Studio, and you can take a dependency on Merq from your project by simply declaring it as a prerequisite for your extension via the manifest:

<PackageManifest Version="2.0.0" ...>
	<Prerequisites>
		<Prerequisite Id="Microsoft.VisualStudio.Component.Merq" Version="[17.0,)" DisplayName="Common Xamarin internal tools" />
	</Prerequisites>
</PackageManifest>

With that in place, you can get access to the IMessageBus by simply importing it in your MEF components:

[Export]
[PartCreationPolicy(CreationPolicy.Shared)]
class MyComponent
{
    readonly IMessageBus bus;
    
    [ImportingConstructor]
    public MyComponent(IMessageBus bus)
    {
        this.bus = bus;
        
        bus.Observe<OnDidOpenSolution>().Subscribe(OnSolutionOpened);
    }

    void OnSolutionOpened(OnDidOpenSolution e)
    {
        // do something, perhaps execute some command?
        bus.Execute(new MyCommand("Hello World"));

        // perhaps raise further events?
        bus.Notify(new MyOtherEvent());
    }
}

To export command handlers to VS, you must export them with the relevant interface they implement, such as:

public record OpenSolution(string Path) : IAsyncCommand;

[Export(typeof(IAsyncCommandHandler<OpenSolution>))]
public class OpenSolutionHandler : IAsyncCommandHandler<OpenSolution>
{
    public bool CanExecute(OpenSolution command) 
        => !string.IsNullOrEmpty(command.Path) && File.Exists(command.Path);
            
    public Task ExecuteAsync(OpenSolution command, CancellationToken cancellation)
    {
        // switch to main thread
        // invoke relevant VS API
    }
}

Events can be notified directly on the bus, as shown in the first example, or can be produced externally. For example, the producer of OnDidOpenSolution would look like the following:

public record OnDidOpenSolution(string Path);

[Export(typeof(IObservable<OnDidOpenSolution>))]
public class OnDidOpenSolutionObservable : IObservable<OnDidOpenSolution>, IVsSolutionEvents
{
    readonly JoinableTaskContext jtc;
    readonly Subject<OnDidOpenSolution> subject = new();
    readonly AsyncLazy<IVsSolution?> lazySolution;

    [ImportingConstructor]
    public OnDidOpenSolutionObservable(
        [Import(typeof(SVsServiceProvider))] IServiceProvider servideProvider,
        JoinableTaskContext joinableTaskContext)
    {
        jtc = joinableTaskContext;
        
        lazySolution = new AsyncLazy<IVsSolution?>(async () =>
        {
            await jtc.Factory.SwitchToMainThreadAsync();
            var solution = servideProvider.GetService<SVsSolution, IVsSolution>();
            solution.AdviseSolutionEvents(this, out var _);
            return solution;
        }, jtc.Factory);    
    }

    public IDisposable Subscribe(IObserver<OnDidOpenSolution> observer)
        => subject.Subscribe(observer);

    int IVsSolutionEvents.OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
    {
        var path = jtc.Factory.Run(async () =>
        {
            if (await lazySolution.GetValueAsync() is IVsSolution solution &&
                ErrorHandler.Succeeded(solution.GetSolutionInfo(out var _, out var slnFile, out var _)) &&
                !string.IsNullOrEmpty(slnFile))
            {
                return slnFile;
            }

            return null;
        });

        if (path is string)
        {
            subject.OnNext(new OnDidOpenSolution(path));
        }

        return VSConstants.S_OK;
    }

    // rest of IVsSolutionEvents members
}

The implementation above is just an example, but wouldn't be too far from a real one using the IVs* APIs. It's worth remembering how simple this is to consume though:

[ImportingConstructor]
public MyComponent(IMessageBus bus)
{
    bus.Observe<OnDidOpenSolution>().Subscribe(OnSolutionOpened);
}

void OnSolutionOpened(OnDidOpenSolution e)
{
    // ...
}

The benefit of the external producer implementing IObservable<T> itself is that it won't be instantiated at all unless someone called Observe<T>, which minimizes the startup and ongoing cost of having this extensibility mechanism built-in.

If you are hosting VS MEF in your app, the same concepts apply, so it should be a familiar experience.

Product Compatible and additional computed target framework versions.
.NET Framework net472 is compatible.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.0 180 1/29/2024
2.0.0-rc.6 58 1/29/2024
2.0.0-rc.5 61 1/27/2024
2.0.0-rc.3 107 7/10/2023
2.0.0-rc.2 101 7/10/2023
2.0.0-rc.1 102 7/7/2023
2.0.0-beta.4 101 7/6/2023
2.0.0-beta.3 116 11/19/2022
2.0.0-beta.2 116 11/18/2022
2.0.0-beta 162 11/16/2022
2.0.0-alpha 144 11/16/2022
1.5.0 449 2/16/2023
1.3.0 524 7/28/2022
1.2.0-beta 162 7/20/2022
1.2.0-alpha 174 7/16/2022