Closed michaelcsikos closed 7 years ago
OK, more strange behaviour. If I leave out the very first MessageBox
displaying "Before principal set", the rest works in TestPrincipal1
. The "After principal set" message displays correctly.
private void ThisWorks()
{
Csla.ApplicationContext.User = new Csla.Security.UnauthenticatedPrincipal();
MessageBox.Show("After"); // Displays
}
private void ThisThrows()
{
MessageBox.Show("Before");
Csla.ApplicationContext.User = new Csla.Security.UnauthenticatedPrincipal();
MessageBox.Show("After"); // SerializationException
}
I have updated to CSLA 4.6.603, but the behaviour is the same.
I don't know what's going on, but I have a couple thoughts.
First, it is most likely that your code is using the default application context, which relies on the current thread for the current principal. Different host environments manage (or don't manage) the current principal in different ways. It could be that Word is doing something odd/different.
Second, some host environments (most notably unit testing frameworks) run some code in one appdomain and other code in another appdomain. This means the thread crosses appdomain boundaries, which means that things like the current thread's principal object gets serialized/deserialized across that boundary.
If the other side of the boundary (often the host) may not have access to the DLL containing the type. I don't think that's what you are seeing though. But it is also the case that the BinaryFormatter sometimes can't find the assembly, even though it is in memory. This happens in IE for example. In that case you need to use a workaround to force the deserialization process to successfully find the assembly.
It has been a long time since I've thought about that workaround code, so I don't have a quick pointer to it (it is just a few lines of code). But it was in the old NetRun utility from CSLA .NET 1.x. Try searching the CSLA codebase for "serialization workaround" and see if you can find it.
Thanks for your reply, Rocky.
In ThisAddIn_Startup
I am doing the following:
Csla.ApplicationContext.ContextManager = new Csla.ApplicationContextManager();
SerializationWorkaround.Init();
We have been using the SerializationWorkaround
code for almost 10 years because of problems with AutoCAD.
If I call the following method before any UI code, the problem seems avoidable:
public static void ApplicationContextUserWorkaround()
{
Csla.ApplicationContext.User = Csla.ApplicationContext.User;
}
We had a similar problem when using CSLA in application extension modules of Autodesk products (e.g. AutoCAD or Revit). All assemblies are located in the same folder, but some dependent assemblies could not be loaded into the AppDomain of the host application when accessed in "server-side" methods of the local data portal.
Our solution is to handle the "AppDomain.CurrentDomain.AssemblyResolve" event by a custom "AssemblyResolver" to load the "missing" assemblies from the folder all assemblies are located in:
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (!args.Name.StartsWith("Csla")) return null;
//if (!args.Name.StartsWith("AnotherMissingAssembly")) return null;
var parts = args.Name.Split(new string[] { "," }, 2, StringSplitOptions.None);
var dllFileName = parts[0] + ".dll";
Assembly resolvedAssembly = null;
var targetFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), dllFileName);
if (File.Exists(targetFile))
{
resolvedAssembly = Assembly.LoadFrom(targetFile);
}
// Return null when DLL is not found.
// This will tell the .NetFramework to try other AssemblyResolve handlers.
return resolvedAssembly;
}
static void StartExtensionApplication(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve +=
new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
This post in a Autodesk forum pointed in the right direction: http://thebuildingcoder.typepad.com/blog/2014/05/rvtva3c-assembly-resolver.html This post in stackoverflow was helpful, too: http://stackoverflow.com/questions/1373100/how-to-add-folder-to-assembly-search-path-at-runtime-in-n...
Thanks for the reply. Turns out the ApplicationContextUserWorkaround
doesn't always work, no big surprise. Every referenced DLL is sandboxed by the Office apps into separate folders at runtime with an __AssemblyInfo__.ini
file. This makes it difficult to provide a path to Assembly.LoadFrom
in the AssemblyResolve
event handler. The path ends up something like
C:\Users\Michael\AppData\Local\assembly\dl3\JRAQQOT2.V1D\0DOV0CQ0.9XC\9da4093d\00226750_5600d301\Csla.DLL
I have attached a test solution which demonstrates some of the issues I am experiencing. It has a very basic custom identity, a basic business object which reads from a generated XML file, and a form with three buttons. It's using:
SerializationWorkaround
,Csla.ApplicationContext.PropertyChangedModes.Windows
, andCsla.Windows.ApplicationContextManager
If you set the WindowsUI project as the StartUp project and run it, each button in the main form should succeed, with no exceptions being thrown.
Set the WordAddIn project as the StartUp project and run it. Word starts and the same form is displayed as the add-in loads. The first button succeeds, but the next two buttons throw exceptions. See the Debug Output for details, or set a breakpoint.
The same is true for the OutlookAddIn project.
As others have pointed out earlier in the thread, this is caused/related to .NET failing to resolve assemblies. The AssemblyResolve
event is probably the answer, I just haven't been able to figure it out.
I am open to any suggestions. This is a commercial project I'm working on and this problem is currently a showstopper. Any help would be greatly appreciated.
Kind regards Michael
After some investigations, I found a kind of work around. It works (all 3 test routines, sync and async) for me at the CslaOffice sample (WordAddIn with Office 2010):
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
// SerializationWorkaround (handling the AppDomain.AssemblyResolve event ) does not help here.
//SerializationWorkaround.Init();
Csla.ApplicationContext.PropertyChangedMode = Csla.ApplicationContext.PropertyChangedModes.Windows;
Csla.ApplicationContext.ContextManager = new Csla.Windows.ApplicationContextManager();
// Work around for not receiving a 'SerializationException' in VSTO AddIn anymore:
// 1. We have to force initialization of ConfigurationManager and
System.Configuration.ConfigurationManager.GetSection("ANY DUMMY TEXT");
// 2. We have to set UnauthenticatedPrincipal explicitly after setting the Csla.ApplicationContextManager.
Csla.ApplicationContext.User = new Csla.Security.UnauthenticatedPrincipal();
var form = new FormMain();
form.Show();
}
The reason for this behaviour in VSTO AddIns together with hints to the work around are discussed in two posts of the old CSLA Forum (it is really valuable that we have still access to it):
"VSTO and CSLA 2.0 Error" http://forums.lhotka.net/forums/t/1128.aspx?PageIndex=2
"Tests FAIL running in VS2012, but PASS in VS2010" http://forums.lhotka.net/forums/p/11545/55404.aspx
I hope this work around helps you out.
Kind regards Ingo
Ingo, wow, thank you! This problem has had me in a holding pattern for the last week; I've had to delay a new update.
Yesterday, I posted this question on Stack Overflow. If you have an account and would care to paste your answer there, I will mark it as the answer. https://stackoverflow.com/questions/45955078/c-sharp-serializationexception-in-office-2013-2016-vsto-add-ins-with-csla
Thanks again for your help.
I've come across another scenario where Ingo's workaround doesn't seem to work. It's simply a click event for a button in the ribbon:
private void MessageThrows(object sender, RibbonControlEventArgs e)
{
MessageBox.Show("About to set user to UnauthenticatedPrincipal. " +
"Check Debug Output to see exception.",
"Before UnauthenticatedPrincipal");
Csla.ApplicationContext.User = new Csla.Security.UnauthenticatedPrincipal();
try
{
MessageBox.Show("The user has been set to UnauthenticatedPrincipal.",
"After UnauthenticatedPrincipal");
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
The attached zip demonstrates the issue in Outlook and Word.
What I've found is that before any UI is displayed by any ribbon control event handler, I need to re-run the workaround code, and reassigning the current user is sufficient. It looks ridiculous, but it works.
/// <summary>
/// Microsoft Office add-ins can experience
/// <code>System.Runtime.Serialization.SerializationException</code>
/// "Type is not resolved for member Csla.Security.UnauthenticatedPrincipal".
/// Calling this method before showing any UI works around the problem.
/// </summary>
public static void ExceptionWorkaround(bool setUnauthenticatedPrincipal = false)
{
// 1. Force initialisation of ConfigurationManager
ConfigurationManager.GetSection("Dummy");
// 2. Set User explicitly
if (setUnauthenticatedPrincipal)
Csla.ApplicationContext.User = new UnauthenticatedPrincipal();
else
Csla.ApplicationContext.User = Csla.ApplicationContext.User;
}
Good that it helped you.
In old CSLA forum, user "MikeGoatly" explained the work around in "Tests FAIL running in VS2012, but PASS in VS2010":
... When ConfigurationManager is initialized it reads from AppDomain.Evidence, which causes a stack walk - if you have placed a custom principal on the thread this ends up being de-serialized in the original AppDomain, which fails with the SerializationException you're seeing.
The fix is a little dirty, but if you make a call to ConfigurationManager.GetSection("ANY DUMMY TEXT") prior to your test then things should start working again. ...
There are still scenarios where all of these workarounds fail. I have loads of these exceptions logged on a client's PC. My only option seems to be installing CSLA in the Global Assembly Cache.
A more resilient solution could be to rely on a neutral (build-in) type like GenericPrincipal/ClaimsPrincipal. These types are serializable. Unfortunately, at present they can not be serialized by CSLA MobileFormatter without beeing derived. There is some activity at the CslaClaimsPrincipal and serialization of ClaimsPrincipal subject: https://github.com/MarimerLLC/csla/issues/496 https://github.com/MarimerLLC/cslaforum/issues/422
However, in VSTO AddIns it seems to be better to avoid working with a custom (derived) Principal/Identity class at all.
A last resort could be to set the principal to a GenericPrincipal before the call from the VSTO AddIn AppDomain returns to the main AppDomain (MS-Office application). The challenge here is to find the correct entry/exit point in the VSTO AddIn. (Referring to the gist of a post in the old forum, proposed by Rocky)
By installing Csla.dll in the GAC it seems to solve the problem. No workarounds are required at all. It's a bit of a last resort, but so far it looks like the only reliable solution.
I am getting some weird behaviour trying to set
Csla.ApplicationContext.User
to a newCsla.Security.UnauthenticatedPrincipal
or my own custom principal inside a Word add-in. I am using theCsla.ApplicationContextManager
. All of this works perfectly in a standalone form.As you can see from the following test method, the principal is an
UnauthenticatedPrincipal
at the start, and the message box displays correctly. After setting the current user to a newUnauthenticatedPrincipal
I canDebug.WriteLine
the current user, but anything displaying aMessageBox
, or evennew Form().ShowDialog()
throws:System.Runtime.Serialization.SerializationException: Type is not resolved for member 'Csla.Security.UnauthenticatedPrincipal,Csla, Version=4.6.500.0, Culture=neutral, PublicKeyToken=93be5fdc093e4c30'.
It gets even stranger, though. If I set the user inside a form, it works:
This has been driving me nuts today. I hope someone can shed some light on it.
Kind regards Michael