microsoft-ui-xaml icon indicating copy to clipboard operation
microsoft-ui-xaml copied to clipboard

WinUI 3 Cant instantiate a UserControl from a location outside the application package or directory (XamlParseException)

Open williamfigtree opened this issue 4 years ago • 55 comments

Describe the bug

When instantiating a UserControl from outside the application package an XamlParseException is thrown from the UserControl constructor at this.InitializeComponent().

Exception thrown at 0x773B35D2 (KernelBase.dll) in HostApp.exe: WinRT originate error - 0x80004005 : 'Cannot locate resource from 'ms-appx:///Plugin/PluginUserControl.xaml'.'.
Exception thrown: 'Microsoft.UI.Xaml.Markup.XamlParseException' in WinRT.Runtime.dll
WinRT information: Cannot locate resource from 'ms-appx:///Plugin/PluginUserControl.xaml'.
XAML parsing failed.

Steps to reproduce the bug

See minimal reproducing repository https://github.com/williamfigtree/WinUIPluginIssue. Key steps to recreate:

  1. Create a class library "Plugin" using template "Class Library (WinUI 3 in Desktop)"

  2. Add a UserControl "PluginUserControl" using template "User Control (WinUI 3)" to Plugin.csproj

  3. Create an application "HostApp" using template "Black App, Packaged (WinUI 3 in Desktop)"

  4. Add the "PluginLoadContext" class from this tutorial https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support to HostApp.csproj

    using System;
    using System.Reflection;
    using System.Runtime.Loader;
    
    namespace HostApp
    {
        class PluginLoadContext : AssemblyLoadContext
        {
            private AssemblyDependencyResolver _resolver;
    
            public PluginLoadContext(string pluginPath)
            {
                _resolver = new AssemblyDependencyResolver(pluginPath);
            }
    
            protected override Assembly Load(AssemblyName assemblyName)
            {
                string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
                if (assemblyPath != null)
                {
                    return LoadFromAssemblyPath(assemblyPath);
                }
    
                return null;
            }
    
            protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
            {
                string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
                if (libraryPath != null)
                {
                    return LoadUnmanagedDllFromPath(libraryPath);
                }
    
                return IntPtr.Zero;
            }
        }
    }
    
    
  5. Add the following code to the App constructor. You may need to modify the path to Plugin.dll.

        public App()
        {
            this.InitializeComponent();
    
            // Attempt to load a UserControl from a plugin dll
            // PluginUserControl throws an XamlParsingExcpetion during instantiation
    
            // Locate plugin dll in the Plugin project bin directory
            var rootPath = Path.GetFullPath(@"..\..\..\..\..\..\..\..\", typeof(Program).Assembly.Location);
            var pluginDllPath = Path.Combine(rootPath, @"Plugin\bin\x86\Debug\net5.0-windows10.0.19041.0\Plugin.dll");
    
            // Instantiate PluginUserControl
            var pluginLoadContext = new PluginLoadContext(pluginDllPath);
            using (pluginLoadContext.EnterContextualReflection())
            {
                var pluginUserControl = Activator.CreateInstance("Plugin", "Plugin.PluginUserControl");
            }
        }
    
  6. Build Plugin.csproj

  7. Deploy and debug HostApp.csproj

Expected behavior

The UserControl is instantiated and the UserControl XAML resources supplied by the external project, package, or directory are located.

Screenshots

No response

NuGet package version

No response

Windows app type

  • [ ] UWP
  • [ ] Win32

Device form factor

Desktop

Windows version

November 2019 Update (18363)

Additional context

This is a blocking issue for applications which require plugins which supply their own UI.

Observed the issue with WinUI 3 1.0.0-preview3.

https://github.com/microsoft/microsoft-ui-xaml/issues/3888 describes a similar requirement but does not come to an appropriate solution as it requires files from the plugin to be copied into the main package. This prevents separate build and distribution of applications and plugins.

https://github.com/microsoft/microsoft-ui-xaml/issues/1365#issuecomment-534803147 and https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support suggest that AssemblyLoadContext is the intended mechanism for plugin support and do not indicate any separate system for XAML resources.

williamfigtree avatar Nov 15 '21 04:11 williamfigtree

I have the same issue. I followed the https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support example, but it only does console stuff. When trying to get the plugin to have UI components, it just doesn't work.

mbyerska avatar Nov 29 '21 19:11 mbyerska

This may be related to 5536

Daniellled avatar Dec 07 '21 20:12 Daniellled

Dynamic loading of a UserControl isn't supported yet, unless we can find a workaround. The restriction is in the code for the UserControl that's generated by the Xaml compiler, where it assumes that its markup is stored in the app's resources (ms-appx):

public void InitializeComponent()
{
    if (_contentLoaded)
        return;

    _contentLoaded = true;

    global::System.Uri resourceLocator = new global::System.Uri("ms-appx:///UserControl1.xaml");
    global::Microsoft.UI.Xaml.Application.LoadComponent(this, resourceLocator, global::Microsoft.UI.Xaml.Controls.Primitives.ComponentResourceLocation.Application);
}

There might be a way to work around it by not calling this generated InitializeComponent and instead writing that code with a different Uri for the resourceLocaler. @RealTommyKlein might have ideas. I know that that URI needs to be ms-appx, ms-appdata, or ms-resource.

MikeHillberg avatar Dec 15 '21 00:12 MikeHillberg

@MikeHillberg @RealTommyKlein I was not successful replacing the generated InitializeComponent() with a different implementation. That seems like it should work though, perhaps I'm doing something wrong...

Using an optional package to include my plugin project in the app dependency graph I expected to be able to use a URI like "ms-appx://PluginPackageIdentityName/UserControl1.xaml" per these docs: https://docs.microsoft.com/en-us/windows/uwp/app-resources/uri-schemes#authority-ms-appx-and-ms-appx-web. Unfortunately the same "Cannot locate resource..." exception is thrown.

What am I missing?

williamfigtree avatar Jan 10 '22 06:01 williamfigtree

At least for the InitializeComponent replacement, something like this should work:

        public MainWindow()
        {
            // Remove default compiler-generated InitializeComponent implementation, and use our hand-written one instead
            //this.InitializeComponent();
            this.InitializeComponentCustom();
        }

        public void InitializeComponentCustom()
        {
            if (_contentLoaded)
                return;

            _contentLoaded = true;

            global::System.Uri resourceLocator = new global::System.Uri("ms-appx:///SomeCustomLocation/PluginUserControl.xaml");
            global::Microsoft.UI.Xaml.Application.LoadComponent(this, resourceLocator, global::Microsoft.UI.Xaml.Controls.Primitives.ComponentResourceLocation.Application);
        }

But I suspect the LoadComponent call is failing because PluginUserControl.xaml isn't present in the application's resources.pri. Normally there's an entry generated for it during the build if the application references the "Plugin" library directly, but since it's loaded by reflection instead, the class library's resources.pri with the PluginUserControl.xaml entry isn't merged into the application's resources.pri. I don't think using a control from a library loaded via reflection is currently supported for that reason - @evelynwu-msft and @marb2000 since we were discussing this yesterday.

RealTommyKlein avatar Jan 11 '22 19:01 RealTommyKlein

What about resource merge at run-time?

I tried it some time ago, but I don't remember the results. But for sure it didn't end working. Just thinking...

The first approach was:

Plugin:

public static List<ResourceDictionary> GetResources()
{
	return new List<ResourceDictionary>()
	{
		new ResourceDictionary
		{
			Source = new Uri("ms-appx:///Views/ModulePage.xaml", UriKind.Relative)
		}
	};
}

Host:

foreach (ResourceDictionary re in plugin.GetResources())
{
	Application.Current.Resources.MergedDictionaries.Add(re);
}

The second approach was mannually getting resources (at least string values and only for testing) and setting control text properties ... no luck, as the MainResourceMap does not contain plugin resources, no matter if trying at plugin or host code.

var resourceContext = new Windows.ApplicationModel.Resources.Core.ResourceContext();// not using ResourceContext.GetForCurrentView
resourceContext.QualifierValues["Language"] = "en-US";
var resourceMap = Windows.ApplicationModel.Resources.Core.ResourceManager.Current.MainResourceMap.GetSubtree("Resources");
string resourceValue = resourceMap.GetValue("AppDescription", resourceContext).ValueAsString;

Then I ended up playing with makepri with no results.

Maybe something more drastic like resource merge at plugin install time, involving makepri?

Or try to use another resource system type/workflow in plugins?

iomismo avatar Jan 13 '22 17:01 iomismo

I discovered that I am able to successfuly load the UserControl if it is included in an optional package and the URI specifies the full path to the installed package location e.g. ms-appx:///C:/Program Files/WindowsApps/[Package full name]/HostApp.SimplePlugin/SimpleUI.xaml.

This is a viable workaround for simple things but leaves a big outstanding issue with resource location. Features like Resource.resw files and 3rd party controls with embedded xaml data do not work from the plugin package. So more on that:

@RealTommyKlein The plugin's resources.pri file does seem to be loaded into the application's resources although not into MainResourceMap. I can see this by inspecting AllResourceMaps on ResourceManager.Current at runtime: plugin resource map loaded at runtime

Perhaps the plugin's resources.pri is incorrect? I am using the resources.pri file that VisualStudio generates for a WinUI3 class library. Does it need to contain a named resource for the UserControl xaml and not the xbf? Is it formatted incorrectly? Here's the plugin pri dump resources.pri.xml.zip and a trimmed down version with just the relevant resource map:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PriInfo>
	<PriHeader>
		<WindowsEnvironment name="WinCore" version="1.2" checksum="1912541329"/>
		<AutoMerge>false</AutoMerge>
		<IsDeploymentMergeable>true</IsDeploymentMergeable>
		<TargetOS version="10.0.0"/>
		<ReverseMap>false</ReverseMap>
	</PriHeader>
	<QualifierInfo>
		<Qualifiers>
			<Qualifier name="Language" value="EN-US" priority="700" scoreAsDefault="1.0" index="1"/>
		</Qualifiers>
		<QualifierSets>
			<QualifierSet index="1">
				<Qualifier name="Language" value="EN-US" priority="700" scoreAsDefault="1.0" index="1"/>
			</QualifierSet>
		</QualifierSets>
		<Decisions>
			<Decision index="2">
				<QualifierSet index="1">
					<Qualifier name="Language" value="EN-US" priority="700" scoreAsDefault="1.0" index="1"/>
				</QualifierSet>
			</Decision>
		</Decisions>
	</QualifierInfo>
	<ResourceMap name="HostApp.SimplePlugin" primary="true" uniqueName="ms-appx://HostApp.SimplePlugin/" version="1.0">
		<VersionInfo version="1.0" checksum="1169589394" numScopes="16" numItems="15"/>
		<ResourceMapSubtree index="1" name="Files">
			<ResourceMapSubtree index="15" name="HostApp.SimplePlugin">
				<NamedResource name="SimpleUI.xbf" index="14" uri="ms-resource://HostApp.SimplePlugin/Files/HostApp.SimplePlugin/SimpleUI.xbf">
					<Decision index="1">
						<QualifierSet index="0"/>
					</Decision>
					<Candidate type="Path">
						<QualifierSet index="0"/>
						<Value>HostApp.SimplePlugin\SimpleUI.xbf</Value>
					</Candidate>
				</NamedResource>
			</ResourceMapSubtree>
		</ResourceMapSubtree>
	</ResourceMap>
</PriInfo>

And here is the repo I'm using to test this with optional packages https://github.com/williamfigtree/WinUI3-plugin-sample. I've had to use a custom build script on the optional package project due to lack of VisualStudio tooling for WinUI3.

williamfigtree avatar Jan 28 '22 03:01 williamfigtree

I have tried https://github.com/microsoft/microsoft-ui-xaml/issues/6299#issuecomment-1023856405 by @williamfigtree and it works for a single UserControl. However, if that UserControl contains another UserControl, then it fails.

So if you have a MainApp that tries to load a UserControlA from a AssemblyX, you would be able to load UserControlA as mentioned. However if there is another UserControlB in AssemblyX and UserControlA uses it in the XAML file, then we get XAML Parsing exception when loading the UserControlA. @RealTommyKlein do you have any suggestion or work around that you can think off.

Also, @marb2000 has mentioned that the WinUI team is concentrating on the core infrastructure this year, instead of adding more controls. Would you consider this issue/feature among core infrastructure tickets that needs to be addressed in 1.1 or 1.2? This issue is preventing us from developing a class of application (plugins with UIs). I am sure many others in the community too would like to see it addressed soon. @marb2000, @MikeHillberg, A guidance would help us immensely to determine the direction for our project.

harvinders avatar Feb 27 '22 02:02 harvinders

WE have further found few more things

[1] It is possible to load multiple UserControls, however you would need to do it in the code and then assign then to the Content properties, something like

<Page>
     <ContentControl x:Name="ControlA"/>
....
     <ContentControl x:Name="ControlB"/>
</Page>

ControlA.Content = new MyUserControl();

[2] Also, these UserControls can't have a DependencyProperty.

[3] Also, the plugin can't define a AttachedProperty that can be used in the UserControls XAML

harvinders avatar Mar 13 '22 10:03 harvinders

Issue still present in 1.1.0-preview2. The plugin architecture is a critical piece of my companies application and this needs to be resolved before we can move forward with WinUI 3.

cody3b avatar Apr 28 '22 15:04 cody3b

This would be useful for XAML Studio in the future as well to be able to load custom diagnostic tools from other assemblies based on some plug-in API. This seems like a pretty common scenario we see a lot of folks build into apps for mods/customizations/plugins.

michael-hawker avatar Jun 03 '22 23:06 michael-hawker

It's still an issue in 1.1 full release :( I can't create my application with that error.

Pietro228 avatar Jun 09 '22 18:06 Pietro228

@evelynwu-msft is this related to http://task.ms/36925415? mention the workaround?

(update: since this is dynamically activated, rather than project-referenced, looks like some of the approaches above will be necessary)

Scottj1s avatar Jun 15 '22 15:06 Scottj1s

@evelynwu-msft is this related to http://task.ms/36925415? mention the workaround?

(update: since this is dynamically activated, rather than project-referenced, looks like some of the approaches above will be necessary)

I can't open that link because it gives me an error :(

Pietro228 avatar Jul 01 '22 09:07 Pietro228

@evelynwu-msft is this related to http://task.ms/36925415? mention the workaround?

(update: since this is dynamically activated, rather than project-referenced, looks like some of the approaches above will be necessary)

No, this is a bona fide feature request.

evelynwu-msft avatar Jul 05 '22 22:07 evelynwu-msft

Describe the bug

When instantiating a UserControl from outside the application package an XamlParseException is thrown from the UserControl constructor at this.InitializeComponent().

Have you tried with a CustomControl using template Custom Control (WinUI 3)? Do you have the same issue?

OculiViridi avatar Sep 08 '22 13:09 OculiViridi

This may be related to https://github.com/microsoft/microsoft-ui-xaml/issues/7788

boexler avatar Oct 06 '22 06:10 boexler

I have the same issue. Any updates on this?

ancker1 avatar Nov 15 '22 14:11 ancker1

Does still not work.

SoftEntwickler avatar Nov 18 '22 13:11 SoftEntwickler

We would like this as well with C++/WinRT and XAML UI.

axodox avatar Jan 03 '23 14:01 axodox

We seem to have found a workaround which allows this to work, it requires two basic things:

  • First you need to use a custom InitializeComponent() implementation, so you specify your package name in the path as described above
  • In addition you need implement a IXamlMetadataProvider interface, and inside it you need to redirect the calls to your custom DLL's XamlMetadataProvider classes, which you can instantiate with reflection or winrt::create_instance, as they are autogenerated classes their name is known. This implementation is autodetected by tooling and invoked when namespaces and types are resolved in XAML.

Using the above two you will be able to load custom controls including xaml files which reference other custom controls, converters etc. This allows you to create lately discovered plug-ins, without related sets. So you can have a main app deployed, and you can make an optional package later which has UI and load it successfully in the app.

So basically this is already possible however it needs documentation, and the InitializeComponent code generation should be fixed so there is no need for a workaround.

Note we are using UWP + WinUI 2 with C++/WinRT, but maybe somebody can test this on WinUI 3 as well.

axodox avatar Jan 05 '23 13:01 axodox

@axodox Would you mind providing some example code showing how you went about this?

cody3b avatar Jan 05 '23 14:01 cody3b

@axodox Would you mind providing some example code showing how you went about this?

Yes, but I have some other work to do now, so probably sometime next week.

axodox avatar Jan 05 '23 14:01 axodox

We seem to have found a workaround which allows this to work, it requires two basic things:

  • First you need to use a custom InitializeComponent() implementation, so you specify your package name in the path as described above
  • In addition you need implement a IXamlMetadataProvider interface, and inside it you need to redirect the calls to your custom DLL's XamlMetadataProvider classes, which you can instantiate with reflection or winrt::create_instance, as they are autogenerated classes their name is known. This implementation is autodetected by tooling and invoked when namespaces and types are resolved in XAML.

Using the above two you will be able to load custom controls including xaml files which reference other custom controls, converters etc. This allows you to create lately discovered plug-ins, without related sets. So you can have a main app deployed, and you can make an optional package later which has UI and load it successfully in the app.

So basically this is already possible however it needs documentation, and the InitializeComponent code generation should be fixed so there is no need for a workaround.

Note we are using UWP + WinUI 2 with C++/WinRT, but maybe somebody can test this on WinUI 3 as well.

That is very interesting! We will try it in Win UI 3 during tomorrow or next week. If you find the time to upload the examples, I would also be very interested in seeing it :-) I will update in here with the results for Win UI 3.

ancker1 avatar Jan 05 '23 18:01 ancker1

So here is how we were able to load WinUI controls / converters from plug-ins:

First some notes:

  • We are using WinUI 2.x Nuget with C++/WinRT in a UWP project.
  • Our plugins for the UI are based on the Windows Runtime Component (C++/WinRT) template
  • To make things easy on us (and our future users) we have created a custom nuget package which supplies our plugin SDK headers, DLLs and a build targets, which can deploy the plug-in and generate the MSIX package for it as well in a single Visual Studio project
  • We generate the plugin MSIX from command line using the MakeAppX.exe, SignTool.exe etc., note this requires some specific steps, for example for XAML you must make sure to have the package resource index (PRI) files also in the package (something I wasted some time on...), we are NOT using related sets
  • Our plug-ins have a single C export called Init over the regular winrt activation stuff, which receives a pointer to our dependency container implementation, in the method the plug-ins can resolve components from the dependency container and register their services, e.g. register a new XAML panel on our tool panel

And now the code snippets:

  • First we locate the plug-ins, which are in optional packages:
  PluginCollection PluginProviderBase::GetPluginPackages()
  {
    _logger.log(log_severity::info, L"Enumerating plug-ins...");
    auto dependencies = Package::Current().Dependencies();

    unordered_map<wstring, Package> results;
    for (const auto& dependency : dependencies)
    {
      if (dependency.IsFramework() || !dependency.IsOptional()) continue;

      auto pluginId = wstring(dependency.Id().FamilyName());
      results.emplace(pluginId, dependency);
      _logger.log(log_severity::info, L"Plug-in detected: %s", dependency.DisplayName().c_str());
    }

    return results;
  }
  • Then if a plug-in is selected we load it:
  bool PluginProviderBase::TryLoadPlugin(const std::wstring& pluginId)
  {
    if (_loadedPlugins.count(pluginId)) return true;

    try
    {
      //Check if the plugin exists
      auto pluginPackageIt = _availablePlugins.find(pluginId);
      if (pluginPackageIt == _availablePlugins.end()) throw runtime_error("The specified plug-in package does not exist.");

      //Locate plug-in DLL
      auto pluginPackage = pluginPackageIt->second;
      auto packageFolder = pluginPackage.InstalledLocation();
      auto pluginLibraryPath = packageFolder.Path() + L"\\" + pluginPackage.Id().Name() + L".dll";

      //Load plugin DLL
      AddDllDirectory(packageFolder.Path().c_str());
      auto pluginLibrary = LoadLibrary(pluginLibraryPath.c_str());
      if (!pluginLibrary) throw_last_error();

      //Initialize plug-in
      InitializePlugin(pluginId, pluginLibrary);

      return true;
    }
    catch (const hresult_error& error)
    {
      _logger.log(log_severity::error, L"Failed to load plug-in %s: %s", pluginId.c_str(), error.message().c_str());
    }
    catch (const exception& error)
    {
      _logger.log(log_severity::error, L"Failed to load plug-in %s: %s", pluginId.c_str(), to_wstring(error.what()).c_str());
    }
    catch (...)
    {
      _logger.log(log_severity::error, L"Failed to load plug-in %s: unknown error.", pluginId.c_str());
    }

    return false;
  }
  • Then we initialize our plug-in - here you can see how we instantiate the XamlMetadataProvider of the plug-in, which is generated by the WinRT tooling when XAML controls are present in the runtime component:
  void PluginProvider::InitializePlugin(const wstring& /*pluginId*/, HMODULE pluginLibrary)
  {
    //Load Xaml Metadata
    wstring filePath;
    filePath.resize(256);
    GetModuleFileNameW(pluginLibrary, filePath.data(), DWORD(filePath.length()));

    auto libraryName = path(filePath).filename().replace_extension();
    auto metadataFactory = try_get_activation_factory(libraryName + L".XamlMetaDataProvider");
    if (metadataFactory)
    {
      auto metadataProvider = metadataFactory.ActivateInstance<IXamlMetadataProvider>();
      PluginXamlMetadataProvider::MetadataProviders().Append(metadataProvider);
    }

    //Initialize plug-in
    auto initializer = PluginInitializer(GetProcAddress(pluginLibrary, "Init"));
    if (!initializer) throw runtime_error("Failed to find plug-in initializer method.");

    auto container = _dependencyContainerReference.try_lock();
    if (!container) throw runtime_error("The dependency container is not available.");
    
    initializer(container.get());
  }
  • We then implemented a class PluginXamlMetadataProvider, which inherits from IXamlMetadataProvider - this class is however not in the main executable project, but in another windows runtime component one, which is referenced by the main project, this trigger the WinRT tooling to find it and use it for resolving namespaces in XAML code:
  [default_interface]
  runtimeclass PluginXamlMetadataProvider : Windows.UI.Xaml.Markup.IXamlMetadataProvider
  {
    PluginXamlMetadataProvider();

    static IVector<Windows.UI.Xaml.Markup.IXamlMetadataProvider> MetadataProviders{ get; };
  }
  IVector<IXamlMetadataProvider> PluginXamlMetadataProvider::_metadataProviders = single_threaded_vector<IXamlMetadataProvider>();

  IXamlType PluginXamlMetadataProvider::GetXamlType(TypeName const& type)
  {
    for (const auto& provider : _metadataProviders)
    {
      auto xamlType = provider.GetXamlType(type);
      if (xamlType) return xamlType;
    }

    return nullptr;
  }

  IXamlType PluginXamlMetadataProvider::GetXamlType(hstring const& fullName)
  {
    for (const auto& provider : _metadataProviders)
    {
      auto xamlType = provider.GetXamlType(fullName);
      if (xamlType) return xamlType;
    }

    return nullptr;
  }

  com_array<XmlnsDefinition> PluginXamlMetadataProvider::GetXmlnsDefinitions()
  {
    vector<XmlnsDefinition> xmlnsDefinitions;
    for (const auto& provider : _metadataProviders)
    {
      auto definitions = provider.GetXmlnsDefinitions();
      for (auto& definition : definitions)
      {
        xmlnsDefinitions.push_back(definition);
      }
    }

    return { xmlnsDefinitions.begin(), xmlnsDefinitions.end() };
  }

  IVector<IXamlMetadataProvider> PluginXamlMetadataProvider::MetadataProviders()
  {
    return _metadataProviders;
  }
  • The above will cause the std::vector<::winrt::Windows::UI::Xaml::Markup::IXamlMetadataProvider> const& XamlTypeInfoProvider::OtherProviders() method to add PluginXamlMetadataProvider to the XAML metadata provider list in Generated Files\XamlTypeInfo.g.cpp.

This allows the xaml namespaces to be resolved. You will still need the custom InitializeComponent method with the package name in the UI to make it work. I am not fond of macros, but maybe in this case it is OK:

#define InitializePluginComponent(component_t) { if (!_contentLoaded) { _contentLoaded = true; winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx://TestLibrary/TestLibrary/" L#component_t L".xaml" }; winrt::Windows::UI::Xaml::Application::LoadComponent(*this, resourceLocator, winrt::Windows::UI::Xaml::Controls::Primitives::ComponentResourceLocation::Nested); } }

Using the above you can build your app. And someone else can take your SDK and develop UI plug-ins which can be deployed without rebuilding the original app again.

axodox avatar Jan 11 '23 16:01 axodox

Great! But is there any way how to do this in C# with System.Reflection.Assembly? Because I don't know how should I replicate this in C# :(

Pietro228 avatar Jan 14 '23 18:01 Pietro228

Great! But is there any way how to do this in C# with System.Reflection.Assembly? Because I don't know how should I replicate this in C# :(

I think there should be - at least for UWP and WinUI 2 (somebody should verify WinUI 3). You could start with @williamfigtree's repo which shows how to load an assembly in C#, of course with UWP you would have separate packages, but that would work the same way as with C++. Then implement PluginXamlMetadataProvider in C# the same way as I did above (you might need to do this in a separate project, as with C++ we had an issues that it did not work when it was in the same project / assembly as the host). You should use the activator to instantiate the XamlMetadataProvider of the plug-in and add it to the provider's list, this should happen before instantiating the control. If you use UWP and multiple packages you would need to modify InitalizeComponent code as well to use the proper URI.

axodox avatar Jan 15 '23 12:01 axodox

@axodox Just wondering if you have tested

  • Does the dependency properties in the UserControls work?
  • Also have you been able to use x:Uid in XAML to display information from the resource file? I suppose it has something to do with the pri files and a separate story.

I tried in a C# application, the above points are not working, however I am able to place a UserControl within a XAML of another UserControl. We were not able to do that before your work around. Thanks to you we would be able to simplify the code a little bit.

harvinders avatar Jan 15 '23 14:01 harvinders

@harvinders We have not tested those yet, I will definitely look into the dependency properties next week.

axodox avatar Jan 15 '23 15:01 axodox

@Pietro228 in addition to what axodox's mentioned my repo has a powershell script that builds a WinUI 3 library as an optional package invoked from the post build target and a user control with the modified InitializeComponent code. None of it is beautiful but it might help you get started.

williamfigtree avatar Jan 16 '23 00:01 williamfigtree