ubsicap / paratext_demo_plugins

Sample code to demonstrate how to create a Paratext plugin
2 stars 4 forks source link

Question: Setting up binding redirects #2

Open gerfen opened 2 years ago

gerfen commented 2 years ago

I'm building a Paratext plug-in and I'm having issues with loading the correct assembly due to conflicting nuget packages. I can see from fuslogvw, that that a certain assembly is not being loaded. I am able to fix the issue by adding the binding redirect to the Paratext.exe.config file.

My question is this... Does the plug-in architecture allow me to specify a binding redirect in a plug-in specific assembly config file? Or is the Paratext.exe.config my only opportunity to resolve binding conflicts?

Thanks!

tombogle commented 2 years ago

The plug-in architecture does not (and can not) allow a plugin-in specific binding redirect because that mechanism is handled by the .Net Framework and the config file it uses is based on the executable that initiates the process. Since plugins run in the same process as Paratext, they can't have their own binding redirects. That said, Paratext does attempt to load DLLs that plugins depend on from the location where the plugin is installed. As long as the DLLs are strong-named, it is possible to load two different versions. I think that Paratext loads strong-named versions of all/most of the DLLs it depends on, so if your plugin also uses a strong-named DLL, you should be okay. If you are loading a string-named DLL and still having trouble, please indicate what package it is (and what version), and I can look further to see what might be going wrong.

gerfen commented 2 years ago

@tombogle Thank you for the explanation. I was able to work around my issue by utilizing the code below:


 public override void OnAddedToParent(IPluginChildWindow parent, IWindowPluginHost host, string state)
 {
         var currentDomain = AppDomain.CurrentDomain;
         currentDomain.AssemblyResolve += FailedAssemblyResolutionHandler;
          try
          {
                ... code which causes the bind error
          }
          finally
          {
              currentDomain.AssemblyResolve -= FailedAssemblyResolutionHandler;
          }
}
private Assembly FailedAssemblyResolutionHandler(object sender, ResolveEventArgs args)
 {
        // Get just the name of assembly without version and other metadata
        var truncatedName = new Regex(",.*").Replace(args.Name, string.Empty);

         // Load the most up to date version
         var assembly = Assembly.Load(truncatedName);
         return assembly;
}

Hopefully this information will be useful for others who may suffer from the same issue.

tombogle commented 2 years ago

I wonder if this might cause issues for Paratext (or for your plugin, or for another plugin)? See this article for a description of the potential problem and information about how to check if you are introducing a likely problem. If the DLLs are both strong-named, then I think this won't be a problem, but if that is the case, I would think that Paratext should not have thrown an exception. If you can tell me which DLL it is and which version of the DLL you are loading, that might help to give some clarity. If we have left open a loop-hole in Paratext where a plugin can load a DLL in a way that adversely affects Paratext or other plugins but might not be able to be detected as having been caused by the plugin, we probably need to think about how to prevent that, as it could make it really hard to track down bugs.

gerfen commented 2 years ago

The AssemblyResolve event is raise for Microsoft.Owin 2.0.2.0, which should be strongly named. The version referenced in the plugin project is 4.2.1.0. When I debugged the code, this was the only assembly triggering the AssemblyResolve event. I can share a project which repro's the issue, if that would be helpful.

FoolRunning commented 2 years ago

@gerfen, I think a project that demonstrates the problem would be helpful.

gerfen commented 2 years ago

@tombogle and @FoolRunning

I finally had a chance to put together a repro project. I created this fork: https://github.com/gerfen/paratext_demo_plugins. I added three new projects and a new solution

Projects

  1. WebApiPlugin
  2. WebApiPlugin.Common
  3. WebApiPluginTests

Solution

  1. WebApiUnitTests

After building the ParatextDemoPlugins solution and activating the WebApiPlugin via Paratext, you can run the unit tests via the WebApiUnitTests solution. Both tests expect Paratext to be running with the WebApiPlugin activated.

GetCurrentVerseTest tests the WebApi while ReceiveMessageTest tests that SignalR is working. The ReceiveMessageTest has a time out of 15 seconds to allow you to click the "Send Messasge" button as shown below:

image

Please note that this test will always fail when the timeout for the test fires but you should see output similar to this:


Xunit.Sdk.TestTimeoutException
Test execution timed out after 15000 milliseconds
   at Xunit.Sdk.XunitTestInvoker.InvokeTimeoutTestMethodAsync(Object testClassInstance) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\XunitTestInvoker.cs:line 123
   at Xunit.Sdk.TestInvoker`1.<RunAsync>b__47_0() in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:line 206
   at Xunit.Sdk.ExceptionAggregator.RunAsync[T](Func`1 code) in C:\Dev\xunit\xunit\src\xunit.core\Sdk\ExceptionAggregator.cs:line 107

7503b99d-349d-46e4-b598-1d924c687b66 - Can you hear me?
57e53053-6b68-4667-bc3b-3f7d37ef4de4 - Can you hear me?
2be54704-e1e3-4ba9-8bc0-f8af7d8def8b - Can you hear me?
747df970-410f-46b7-adbc-864bad77e5ef - Can you hear me?
c2d3c030-3fc0-480d-ac4f-8d32fd23249d - Can you hear me?
999005a4-8f28-4bad-9f61-ffddd461ba18 - Can you hear me?
aa4e8ff5-9065-4c87-bb3b-ac4df585c799 - Can you hear me?
0f3bd7e8-b354-4196-94b3-b662a366704c - Can you hear me?

In the MainView class, you can turnoff the failed assembly resolution handler - FailedAssemblyResolutionHandler by remarking lne 137:

currentDomain.AssemblyResolve += FailedAssemblyResolutionHandler;

Please let me know if I can provide any more details.

Thank you for looking into this issue!

FoolRunning commented 2 years ago

@gerfen Thanks, we'll definitely look into this. However, we are all currently at a week-long conference, so it might take a while to get back to you.

jwickberg commented 2 years ago

@gerfen I got your copy of the plugin demos and it seems to be working after I made one correction to your test code (I'm using VS 2019 and it didn't like the new way namespaces can be done) and one correction to the Paratext.exe.config file that wasn't allowing the newer version of the embedded plugin DLL to load (I'll make a correction to the Paratext).

I have an update of the plugin interface in progress and committed a change to the demos to use the updated plugin API version (2.0.17, which isn't release yet). I probably should have done this change on a branch and merged it when the API work is complete (still learning about plugin code).

Are you only getting the error when the FailedAssemblyResolutionHandler isn't used?

gerfen commented 2 years ago

@jwickberg Thanks for looking into my issue.

Without the FailedAssemblyResolutionHandler or adding binding redirects to the Paratext.exe.config file, I cannot get the plug-in to work.