wpfsettingsvisual-studio-extensions

Creating an OptionsProvider in a separate project/library to the VSIX project in a Visual Studio Extension


I'm making a VS Extension to manage Github issues. Due to some peculiarities of CommunityToolkit.MVVM and on the advice of @Sergio0694, I've had to separate my ViewModels, Services etc into a separate project in my solution. Because of this, I've also had to move my OptionsProvider (which manages VS>Tools>Options for the extension) into the additional library as ViewModels and Services will rely heavily on these options.

The main VSIX Project VisGit holds the main ToolWindow as a view called MainView. Viewmodels/services etc exist in a separate project, VisGit.Core. This view is bound to an instance of MainViewModel purely via xaml (nothing in code-behind):

<UserControl x:Class="VisGit.Views.MainView"
        xmlns:viewmodels="clr-namespace:VisGit.Core.ViewModels;assembly=VisGit.Core"/>
    <UserControl.DataContext>
        <viewmodels:MainViewModel />
    </UserControl.DataContext>

All is working - VisGit contains a reference to VisGit.Core. My OptionsProvider class is in VisGit.Core. I had to create this in VisGit first and move it, as "Options Page (Community)" was not avaiable in VisGit.Core as this is just a standard class library. The code:

namespace VisGit.Core.Services
{
    public partial class OptionsProvider
    {
        // Register the options with this attribute on your package class:
        // [ProvideOptionPage(typeof(OptionsProvider.UserSettingsOptions), "VisGit.Services", "UserSettings", 0, 0, true, SupportsProfiles = true)]
        [ComVisible(true)]
        public class UserSettingsOptions : BaseOptionPage<UserSettings>
        { }
    }

    public class UserSettings : BaseOptionModel<UserSettings>
    {
        private string personalAccessToken = "{UNSET}";

        [DisplayName("Personal Access Token")]
        public string PersonalAccessToken
        {
            get => Encyption.DpapiToInsecureString(Encyption.DpapiDecryptString(personalAccessToken));
            set => personalAccessToken = Encyption.DpapiEncryptString(Encyption.DpapiToSecureString(value));
        }
    }
}

I have referenced Community.VisualStudio.Toolkit.17 in VisGit.Core. Now, because it is not in the VSIX project, I've amended the boilerplate directions to point to the right location for UserSettings in my VisGitPackage.cs file in VisGit:

namespace VisGit
{
    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
    [InstalledProductRegistration(Vsix.Name, Vsix.Description, Vsix.Version)]
    [ProvideToolWindow(typeof(MainToolWindow.Pane), Style = VsDockStyle.Tabbed, Window = WindowGuids.SolutionExplorer)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(PackageGuids.VisGitString)]
    [ProvideOptionPage(typeof(VisGit.Core.Services.OptionsProvider.UserSettingsOptions), "VisGit.Core.Services", "UserSettings", 0, 0, true, SupportsProfiles = true)]
    public sealed class VisGitPackage : ToolkitPackage
    {
        protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
        {
            await this.RegisterCommandsAsync();

            this.RegisterToolWindows();
        }
    }
}

VisGit.Core compiles, but VisGit doesn't. I get the baffling error:

Error       CreatePkgDef : error : TypeLoadException: Could not load type 'VisGit.Core.Services.OptionsProvider+UserSettingsOptions' from assembly 'VisGit.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
   at System.Reflection.CustomAttribute._CreateCaObject(RuntimeModule pModule, IRuntimeMethodInfo pCtor, Byte** ppBlob, Byte* pEndBlob, Int32* pcNamedArgs)
   at System.Reflection.CustomAttribute.CreateCaObject(RuntimeModule module, IRuntimeMethodInfo ctor, IntPtr& blob, IntPtr blobEnd, Int32& namedArgs)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes, Boolean isDecoratedTargetSecurityTransparent)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeType type, RuntimeType caType, Boolean inherit)
   at Microsoft.VisualStudio.Tools.CreatePkgDef.ProcessAssembly(String fileName, Hive hive, PkgDefContext context, Boolean register, RegistrationMode mode) in D:\a\_work\1\s\src\product\vssdk\tools\CreatePkgDef\CreatePkgDef.cs:line 341
   at Microsoft.VisualStudio.Tools.CreatePkgDef.DoCreatePkgDef(InputArguments inputArguments) in D:\a\_work\1\s\src\product\vssdk\tools\CreatePkgDef\CreatePkgDef.cs:line 202
   at Microsoft.VisualStudio.Tools.CreatePkgDef.Main(String[] arguments) in D:\a\_work\1\s\src\product\vssdk\tools\CreatePkgDef\CreatePkgDef.cs:line 91 VisGit      

I do note that the OptionsProvider class is partial, but if I create a new OptionsProvider in the VSIX project, and try to "Go to Definition" on the class name, I can't find any other definition for this class (i.e. no other partial).

With present architecture, I can't instantiate the UserOptions in the VSIX project (VisGit) and pass them to any view models, as the ViewModels are instantiated in the View XAML. Besides, if there were a way to do this, it feels a bit hacky as the view model should be able to create its own instance to be fully separate from the view. Admittedly, I'm still a bit baffled by how to instantiate viewmodels properly.

Github code here

Any advice appreciated.


Solution

  • Could not load type 'VisGit.Core.Services.OptionsProvider+UserSettingsOptions' from assembly 'VisGit.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null')

    It seems that the compiler cannot parse the class 'VisGit.Core.Services.OptionsProvider+UserSettingsOptions'. After checking your directory structure, it looks like you service folder are under namesapce VisGit.Core. The name of the namespace must be a valid C# identifier name.

    Please try to remove the point(".") in your namespace VisGit.Core and rename it to VisGitCore. For example

    [ProvideOptionPage(typeof(VisGitCore.Services.OptionsProvider.UserSettingsOptions), "VisGitCore.Services", "UserSettings", 0, 0, true, SupportsProfiles = true)]
    

    After doing that, i am able to build VisGit fine without errors.

    I think that as Namespaces are delimited by using the . operator but if Nampespace's name includes dots, it will treat . as a separator.

    Hope it can help you.