Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Getting the App’s Startup Assembly in WPF Designer?


:P
On this page:

Ok, once again I’m soliciting some audience participation as I’ve hit a dead end with a markup extension I’m working with in WPF.

I’m working on a localization markup extension that provides localized resource content using a simple markup extension with some additional features beyond what x:Static bindings provide. Specifically the ability to use a different resource manager, provide default values and the ability to use string based types for automatic type conversion. Yes, I know this has been done before and in fact my extension is based on some previous code found here from Christian Moser. I basically simplified this code and added a few additional features that I need for one of my projects.

The markup extension works great at runtime. It also works great in the designer as long as I host the extension class in the same project as the resources I binding against because I can easily find the ExecutingAssembly that I assume to hold application’s resources that will be used for binding.

The problem is that in order to create a ResourceManager I need to know which assembly the resources live in. A ResourceManager always requires an assembly reference (and resourceSet name) in its constructor to find the appropriate resources. Sounds easy enough, except I have been utterly stumped to find the Startup assembly (the WPF EXE) when running in the WPF designer.

The problem comes in when using the designer AND when trying to isolate the MarkupExtension into its own assembly/project. By moving the extension into another project I can no longer rely on Assembly.GetExecutingAssembly() to figure out the assembly where resources are coming from. Now at runtime it’s fairly easy to work around this by using Assembly.GetEntryAssembly() or even walking the control tree to find the main document and it’s underlying type and assembly.

However, in the WPF designer the actual Document is not walkable (all Parent refs are null), and GetEntryAssembly() throws an exception. Looking in the debugger while inside of the markup extension, reveals that the callstack goes straight presentation design time classes – the main WPF Exe doesn’t show up in the callstack (although the assembly IS loaded in the AppDomain.GetAssemblies()).

When running the designer too there’s no user code executing as far as I can tell, so even explicit type assignment in app.xaml.cs’s startup constructor doesn’t work.

I’m stumped. Right now the only way I can figure this out is to force the markup extension to run in the same assembly that contains the resources.

To give you an idea on what I’m doing, here’s a quick overview. The markup extension implements the required ProvideValue() method which delegates to ProvideValueInternal(). At the top of this method I try to retrieve the requested resource manager (which is caches internally) for the requested (or default) resource set.

/// <summary>
/// Internal value retrieval
/// </summary>
/// <returns></returns>
private object ProvideValueInternal()
{
    // Get a cached resource manager for this resource set
    ResourceManager resMan = this.GetResourceManager(this.ResourceSet);
    object localized = null;

}

GetResourceManager() then tries to retrieve a cached resource manager or create a new one. And this where the problem starts:

/// <summary>
/// Retrieves a resource manager for the appropriate ResourceSet
/// By default the 'global' Resource
/// </summary>
/// <param name="resourceSet"></param>
/// <returns></returns>
private ResourceManager GetResourceManager(string resourceSet)
{
    // At design time try to get the default assembly
    if (ResExtension.Settings.DefaultResourceAssembly == null)
        ResExtension.Settings.FindDefaultResourceAssembly();

    if ( string.IsNullOrEmpty(resourceSet) )                
        return DefaultResourceManager ?? null;
    
    if (ResExtension.Settings.ResourceManagers.ContainsKey(resourceSet))                
        return ResExtension.Settings.ResourceManagers[resourceSet];

    ResourceManager man = new ResourceManager(resourceSet, ResExtension.Settings.DefaultResourceAssembly);
    ResExtension.Settings.ResourceManagers.Add(resourceSet, man);
    return man;
}

The Settings object is static and holds all the ‘global’ configuration settings like the default assembly and default resource manager. The code basically checks whether the static Settings.DefaultResourceAssembly property is set and if it isn’t it will try to get it (once).

Currently this implementation is rigged to look at the current executing assembly:

/// <summary>
/// This method tries to find the strongly typed Resource class in a project
/// so the designer works properly.
/// 
/// THIS CODE ASSUMES THE MAKKUP EXTENSION RUNS IN THE SAME ASSEMBLY THAT
/// ALSO CONTAINS RESOURCES. THIS IS DONE FOR THE DESIGNER ONLY. I HAVE
/// NOT BEEN ABLE TO FIGURE OUT A WAY TO GET A REFERENCE TO THE STARTUP
/// ASSEMBLY IN THE DESIGNER. IF YOU FIGURE OUT A WAY PLEASE CONTACT ME.
/// </summary>
/// <returns></returns>
internal bool FindDefaultResourceAssembly()
{
    // Assume the Document we're called is in the same assembly as resources
    Assembly ass = Assembly.GetExecutingAssembly();

    // Search for Properties.Resources in the Exported Types (has to be public!)
    Type ResType = ass.GetExportedTypes().Where(type => type.FullName.Contains(".Properties.Resources")).FirstOrDefault();
    if (ResType == null)
        return false;

    ResourceManager resMan = ResType.GetProperty("ResourceManager").GetValue(ResType, null) as ResourceManager;

    this.DefaultResourceAssembly = ass;
    this.DefaultResourceManager = resMan;
}  

which works both at runtime and inside of the WPF designer as long as both the extension and the resources live in the same assembly.

The question is how to replace the GetExecutingAssembly call with something that will get me a reference to the main WPF EXE’s assembly. It works as is as long as I compile the markup extension into the same assembly as the resources. But otherwise no luck.

I’ve tried a bunch of different things here:

  • Explicitly setting the Static Properties in Code
    Doesn’t work because WPF doesn’t run any code. So app.xaml.cs doesn’t fire – there’s no global ‘entry point’ that
    the designer runs AFAIK.
  • Experimented around with GetCallingAssembly, GetExecutingAssembly, GetStartupAssembly
    None of these worked as in the designer all but GetExecutingAssembly throw.
  • Used Configuration Value to hold the assembly name
    Didn’t work because WPF Designer doesn’t read AppSettings
  • Tried walking the Control Tree up to the Document and grab type
    Failed as in the WPF Designer all Parent references are null. No walking for you!

Anyway, I’m out of ideas so I’m throwing it out here.

Does anybody see a way to get at the default assembly in the WPF designer? Or am I approaching this in the wrong manner and there’s an easier way to assign the assembly generically?

If attached the extension’s .cs file in case anybody’s interested or wanting to experiment.

Source: ResExtension.zip

Posted in WPF  

The Voices of Reason


 

Jason Haley
June 10, 2009

# Interesting Finds: June 10, 2009

Interesting Finds: June 10, 2009

Rick Strahl
June 10, 2009

# re: Getting the App&rsquo;s Startup Assembly in WPF Designer?

Thanks to Thomas Levesque on StackOverflow (http://stackoverflow.com/questions/963051/finding-the-startup-assembly-at-design-time-in-wpf/974737#974737) who provided a partial solution to the problem by checking for a subclass of System.Windows.Application in the loaded assembly list which works in the designer. It doesn't address all use cases - specifically when working in a control library, but it works for the most common scenario of working of working in an application.

here's my adapted code for FindDefaultResourceAssembly:

/// <summary>
/// This method tries to find the assembly of resources and the strongly 
/// typed Resource class in a project so the designer works properly.
/// 
/// It's recommended your application ALWAYS explicitly initializes the
/// DefaultResourceAssembly and DefaultResourceManager in LocalizationSettings.
/// 
/// When running in the designer this code tries to find the System.Windows.Application 
/// </summary>
/// <returns></returns>
internal bool FindDefaultResourceAssembly()
{            
    Assembly asm = null;

    try
    {
        if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))            
        {
            // in designer find the Windows.Application instance
            // NOTE: will not work in control projects but better than nothing
            asm = (
                   from a in AppDomain.CurrentDomain.GetAssemblies()
                   where a.EntryPoint != null &&
                         a.GetTypes().Any(t => t.IsSubclassOf(typeof(System.Windows.Application)))
                   select a
                  ).FirstOrDefault();
        }
        else
        {
            // Do Private reflection on FrameworkElement
            FrameworkElement element = _targetObject as FrameworkElement;
            if (element != null)
            {
                object root = element.GetType().GetProperty("InheritanceParent",
                            BindingFlags.Instance | BindingFlags.NonPublic)
                            .GetValue(element, null);
                asm = root.GetType().Assembly;
            }
        }
    }
    catch 
    { /* eat any exceptions here; */ }            
       
    // Assume the Document we're called is in the same assembly as resources            
    if (asm == null)
        asm = Assembly.GetExecutingAssembly();

    // Search for Properties.Resources in the Exported Types (has to be public!)
    Type ResType = asm.GetExportedTypes()
                                .Where(type => type.FullName.Contains(".Properties.Resources"))
                                .FirstOrDefault();
    if (ResType == null)
        return false;

    ResourceManager resMan = ResType.GetProperty("ResourceManager")
                                                     .GetValue(ResType, null) as ResourceManager;

    LocalizationSettings.Current.DefaultResourceAssembly = asm;
    LocalizationSettings.Current.DefaultResourceManager = resMan;

    return true;
}

Rick Strahl
June 10, 2009

# re: Getting the App&rsquo;s Startup Assembly in WPF Designer?

As a side note: It appears that the WPF designer runs in partial trust which is why the private reflection used for the non-Designer case above fails. Further any Reflection exceptions thrown in code don't fall through to an exception handler - but the code simply aborts. This applies to the private reflection call, but also for things like Assembly.GetStartupAssembly() - code never hits the exception handler.

Richard Deeming
June 16, 2009

# re: Getting the App&rsquo;s Startup Assembly in WPF Designer?

I couldn't find the equivalent C# page, but the VB.NET page provides a reasonable explanation as to why you can't catch the SecurityException in the FindDefaultResourceAssembly method:

"In partial trust situations, such as an application hosted on a network share, Try...Catch...Finally will not catch security exceptions that occur before the method containing the call is invoked. ... In such a partial trust situation, you would need to put the Process.Start statement in a separate [method]. The initial call to the [method] will fail, allowing Try...Catch to catch it before the [method] containing Process.Start is launched and the security exception produced."

http://msdn.microsoft.com/en-us/library/fk6t46tz.aspx

Basically, when you have a failed link-demand, the calling method (FindDefaultResourceAssembly) will bail without letting you catch the exception. However, the method that called it (GetResourceManager) will be able to catch the exception.

# Graphic Design West Palm Beach

Details are lacking and Microsoft is unlikely to comment, but my guess is that this has more to do with the software implementation around Windows RSS Platform and where/ how that design concept will evolve and emerge in the future (Windows Live, SharePoint Server 2007) than with RSS in particular.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024