Shell settings pass a simple string

Category: visual studio lsextensibility

Question

LaurentzT on Wed, 27 Feb 2013 15:18:05


Hi,

I'm trying to pass a string to my shell but the weird thing is that is only works once. I always have to rebuild the hole solution to let it work again (one more time). If I don't do that I get this error:

InvalidOperationException: A member must have at least one accessor.

StackTrace:

   at System.ComponentModel.Composition.ReflectionModel.LazyMemberInfo.GetAccessors()
   at System.ComponentModel.Composition.ReflectionModel.ReflectionExtensions.ToReflectionMember(LazyMemberInfo lazyMember)
   at System.ComponentModel.Composition.ReflectionModel.ReflectionMemberExportDefinition.ToReflectionMember()
   at System.ComponentModel.Composition.ReflectionModel.ReflectionMemberExportDefinition.ToExportingMember()
   at System.ComponentModel.Composition.ReflectionModel.ReflectionComposablePart.GetExportingMember(ExportDefinition definition)
   at System.ComponentModel.Composition.ReflectionModel.ReflectionComposablePart.GetExportingMemberFromDefinition(ExportDefinition definition)
   at System.ComponentModel.Composition.ReflectionModel.ReflectionComposablePart.<RequiresActivation>b__9(ExportDefinition definition)
   at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
   at System.ComponentModel.Composition.ReflectionModel.ReflectionComposablePart.RequiresActivation()
   at System.ComponentModel.Composition.ReflectionModel.ReflectionComposablePart.GetInstanceActivatingIfNeeded()
   at System.ComponentModel.Composition.ReflectionModel.ReflectionComposablePart.NotifyImportSatisfied()
   at System.ComponentModel.Composition.ReflectionModel.ReflectionComposablePart.Activate()
   at System.ComponentModel.Composition.Hosting.ImportEngine.PartManager.TryOnComposed()
   at System.ComponentModel.Composition.Hosting.ImportEngine.TrySatisfyImportsStateMachine(PartManager partManager, ComposablePart part)
   at System.ComponentModel.Composition.Hosting.ImportEngine.TrySatisfyImports(PartManager partManager, ComposablePart part, Boolean shouldTrackImports)
   at System.ComponentModel.Composition.Hosting.ImportEngine.SatisfyImports(ComposablePart part)
   at System.ComponentModel.Composition.Hosting.CompositionServices.GetExportedValueFromComposedPart(ImportEngine engine, ComposablePart part, ExportDefinition definition)
   at System.ComponentModel.Composition.Hosting.CatalogExportProvider.GetExportedValue(ComposablePart part, ExportDefinition export, Boolean isSharedPart)
   at System.ComponentModel.Composition.Hosting.CatalogExportProvider.CatalogExport.GetExportedValueCore()
   at System.ComponentModel.Composition.Primitives.Export.get_Value()
   at System.ComponentModel.Composition.ExportServices.GetCastedExportedValue[T](Export export)
   at System.ComponentModel.Composition.ExportServices.<>c__DisplayClassa`1.<CreateStronglyTypedLazyOfT>b__7()
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.LazyInitValue()
   at System.Lazy`1.get_Value()
   at Microsoft.VisualStudio.ExtensibilityHosting.VsExportProviderService.TryGetExportedValue[T](VsExportProvisionScope scope, VsExportSharingPolicy policy, String contractName, T& value)
   at Microsoft.VisualStudio.ExtensibilityHosting.VsExportProviderService.TryGetExportedValue[T](VsExportProvisionScope scope, String contractName, T& value)
   at Microsoft.VisualStudio.ExtensibilityHosting.VsExportProviderService.TryGetExportedValue[T](String contractName, T& value)
   at CosmopolitanExtension.Presentation.Shells.CosmopolitanShell..ctor()

on this line:

if (!VsExportProviderService.TryGetExportedValue<string>("ShellSettings.ShellLanguageString", out translations))

Code in Shell:

 public CosmopolitanShell()
        {
            InitializeComponent();
            string translations;
            if (!VsExportProviderService.TryGetExportedValue<string>("ShellSettings.ShellLanguageString", out translations))
            {
                Helper.Translations = "";
            }
            else
            {
                Helper.Translations=translations;
            }
            

            INavigationScreen defaultScreen = this.GetDefaultScreen(ServiceProxy.Instance.NavigationViewModel.DefaultItem);
            if (null != defaultScreen && null != defaultScreen.ExecutableObject)
            {
                // Upon starting the application, the "CanRun" information for screens to determined asynchronously.
                // It is possible that the shell begins to load before the "CanRun" information is determined for
                // the default screen. If that is the case (CanExecuteAsync will be false), then subscribe a property
                // changed event handler and open the default screen if its CanExecuteAsync property ever becomes true.
                if (defaultScreen.ExecutableObject.CanExecuteAsync)
                {
                    ExecuteShowDefaultScreenCommand(defaultScreen.ExecutableObject);
                }
                else
                {
                    this.DefaultScreenExecutable = defaultScreen.ExecutableObject;
                }
            }
        }

LightSwitch application, Application.vb:

Private Sub Application_Initialize()
            '
            ShellExtension.Presentation.Shells.ShellLanguageString = "Test"
            '
        End Sub

How is it possible that it only works once and all the other times throw me that error.

Same thing when I deploy it. It works the first time the applications start and the second time it stays on the loading screen... :(

Replies

LaurentzT on Wed, 27 Feb 2013 15:59:13


Just tried it with a new shell and a new LightSwitch application and got the same problem... I just don't understand it and it drives me crazy!

LaurentzT on Wed, 27 Feb 2013 16:36:37


Ok found where the error came from my ShellSettings should be a class and not a Module.

But how and when can I fill my dictionary with values from the database (I need access to Application.DataWorkspace) and I need to know my current user. Here is my class:

Public Class ShellSettings

        <Export("ShellSettings.ShellLanguageDictionary")> _
        Public Property ShellLanguageDictionary() As Dictionary(Of String, String)
            Get
                Return _shellLanguageDictionary
            End Get
            Set(ByVal value As Dictionary(Of String, String))
                _shellLanguageDictionary = value
            End Set
        End Property
        Private _shellLanguageDictionary As Dictionary(Of String, String) = New Dictionary(Of String, String)()
    End Class


LaurentzT on Wed, 27 Feb 2013 17:37:00


So now I ask my application to fill the dictionary but I HAVE to use BeginInvoke witch is Async... Is there a way to know when its finnished? Because now my dictionary is empty...

ShellSettings:

Imports System.ComponentModel.Composition

Namespace LightSwitchApplication
    Public Class ShellSettings

        <Export("ShellSettings.ShellLanguageDictionary")> _
        Public Property ShellLanguageDictionary() As Dictionary(Of String, String)
            Get
                Return Application.Current.GetLanguageDictionary()
            End Get
            Set(ByVal value As Dictionary(Of String, String))
                _shellLanguageDictionary = value
            End Set
        End Property
        Private _shellLanguageDictionary As Dictionary(Of String, String) = New Dictionary(Of String, String)()
    End Class
End Namespace

Application

 Public Function GetLanguageDictionary() As Dictionary(Of String, String)
            Dim languageDictionary As Dictionary(Of String, String) = New Dictionary(Of String, String)()
            Me.Details.Dispatcher.BeginInvoke(
       Sub()
           Dim dataWorkspace = CreateDataWorkspace()

           If Debugger.IsAttached Then
               For Each item As GlobalTranslation In dataWorkspace.ApplicationData.GlobalTranslations
                   languageDictionary.Add(item.Code, item.Code + "NL")
               Next
           Else
               For Each item As ViewGlobalTranslation In dataWorkspace.JellycoeData.ViewGlobalTranslations
                   If (item.Translation_Language = 1) Then
                       languageDictionary.Add(item.Code, item.Value)
                   End If
               Next
           End If
       End Sub)
          

            Return languageDictionary
        End Function

LaurentzT on Thu, 28 Feb 2013 09:05:19


Found my problem. will blog soon about: How to translate a LightSwitch app!

Jan Van der Haegen on Thu, 28 Feb 2013 09:16:41


Hey Laurentz!

Seems like you had quite the adventure yesterday!

Regarding your initial question: MEF uses reflection by default, and I believe (but don't remember where I got this information) that the .NET reflection classes have some kind of cache on your local disk.  This is probably why you ran into weird "it works only once" issues.  

I ran into this kind of behavior when doing my own reflection in the constructor of metadata attributes that were put on MEF export attributes (in other words: pretty messed up hacking fun), but never in supported situations like this one (which resembles what I did too: http://janvanderhaegen.wordpress.com/2011/07/28/adding-settings-to-your-custom-lightswitch-shell-extension/ )  

Annnyways, glad you found a workaround, let's move on to the next problem...  To summarize: you're forced to do something async, but you're not happy with that.  

Option a: the caller cannot continue working without the data in that dictionary...  In that case, it's ok to block the thread.  It's an ugly workaround, but create a bool _IsInitialized, and have your async code set that bool to true when you're done.  Just before the "return languageDictionary" statement, write a loop that sleeps the thread (and thus blocks it) until the bool is set to true.

Option  b: return the empty dictionary, but meanwhile fetch the data asynchronously like you're already doing.  Once the async code has finished, throw an event.  You'll have to export your ShellSettings instance instead of just the dictionary, so that the caller can subscribe to your "DictionaryLoaded" event & refresh.  If you need an example: I do this in my "icons" sample: (http://code.msdn.microsoft.com/silverlight/Adding-icons-to-the-01a828ff ) with the "IResourceProvider" interface.

Option c: tell your boss to hire me as a LS consultant so we can hack on this together ;-)

Take care, and keep rocking LS!

Jan