0

i have a problem to cast ViewModel Class with abstract class from external assembly.

i have 2 dlls and one executable:

one Abstract class into dll Cockpit.RUN.Common.dll, this Dll is referenced: in the other project of dll Cockpit.RUN.ViewModels and in the project of executable Cockpit.RUN.Test, but Cocpit.RUN.ViewModels is not referenced in the project of executable, because its loaded dynamically

The abstrac class: (dll)

using Caliburn.Micro;

namespace Cockpit.RUN.Common
{
    public abstract class PluginModel : PropertyChangedBase
    {
        public abstract double Width { get; set; }
        public abstract double Height { get; set; }
        public abstract double Left { get; set; }
        public abstract double Top { get; set; }

        private double zoomfactorfrompluginmodel;
        public double ZoomFactorFromPluginModel
        {
            get => zoomfactorfrompluginmodel;
            set
            {
                zoomfactorfrompluginmodel = value;
                NotifyOfPropertyChange(() => ZoomFactorFromPluginModel);
            }
        }
        public string NameUC;
    }
}

second dll containing viewmodels Cockpit.RUN.ViewModels.dll i have lot of plugins differents in this dll: Push_ViewModel, Switch_ViewModel....

i derive each class with the abstract class for example for the Push_ViewModel:

using Cockpit.RUN.Common;
using System.Windows;
using System.Windows.Input;

namespace Cockpit.RUN.ViewModels
{
    public class PushButton_ViewModel : PluginModel
    {

        public PushButton_ViewModel(params object[] settings)
        {
            NameUC = (string)settings[2];
            Layout = new LayoutPropertyViewModel();
        }

        public override double Left
        {
            get => Layout.UCLeft;
            set => Layout.UCLeft = value;
        }
        public override double Top
        {
            get => Layout.UCTop;
            set => Layout.UCTop = value;
        }
        public override double Width
        {
            get => Layout.Width;
            set => Layout.Width = value;
        }
        public override double Height
        {
            get => Layout.Height;
            set => Layout.Height = value;
        }                         
    }
}

one thing again, this plugin dll is loaded dynamically during the bootstrapper

    protected override IEnumerable<Assembly> SelectAssemblies()   
    {                                                                                                                                  
        var assemblies = new List<Assembly>();
        assemblies.AddRange(base.SelectAssemblies());
        assemblies.Add(Assembly.LoadFile(@"J:\ProjetC#\Cockpit-master\Cockpit.RUN.Test\bin\Debug\Cockpit.RUN.ViewModels.dll")); 
        assemblies.Add(Assembly.LoadFile(@"J:\ProjetC#\Cockpit-master\Cockpit.RUN.Test\bin\Debug\Cockpit.RUN.Views.dll")); 
        return assemblies;   
    }  

so the problem is when i want to cast each ViewModels with the abstract base PulginModel, my result is null:

        using Cockpit.RUN.Common;
                    :
                    :
        public BindableCollection<PluginModel> MyCockpitPlugins { get; set; }

                    :
                    :

        model = "Cockpit.RUN.ViewModels.PushButton_ViewModel, Cockpit.RUN.ViewModels";
        var typeClass = Type.GetType(model); // its ok 
        var viewmodel = resolutionRoot.TryGet(typeClass, param); // Its ok i have an instance of PushButton_ViewModel

        var v = viewmodel as PluginModel; //-> not ok its null
        var w = (PluginModel)viewmodel; //-> not ok its error

        // So i cant display my  list of views associated 
        MyCockpitPlugins.Add(v);

Do I have done something wrong?

assembly loaded at break debug before casting:

ScreenPrint of processes

and the result of typeof and GetType:

typeof(PluginModel) gives {Name = "PluginModel" FullName = "Cockpit.RUN.Common.PluginModel"}

viewmodel.GetType().BaseType gives {Name = "PluginModel" FullName = "Cockpit.RUN.ViewModels.PluginModel"}
Frenchy
  • 16,386
  • 3
  • 16
  • 39
  • 1
    How do you make sure that the assembly, where the class `PluginModel` is, is the same for the executable as well as the one referenced by the "Cockpit.RUN.ViewModels.dll"? Check with https://stackoverflow.com/questions/458362/how-do-i-list-all-loaded-assemblies which assemblies are loaded and in which version. It might be possible that the `PluginModel` class used by your "Cockpit.RUN.ViewModels.dll" file isn't/wasn't the same as the `PluginModel` reference in your executable. Compare `viewmodel.GetType().BaseType` against `typeof(PluginModel);` to see if they reference the same class. – Progman Aug 25 '19 at 12:42
  • @progman i have just one class definition of PluginModel, and this dll is referenced in other projects of the solution. and i have checked loaded assemblies i dont see problem... – Frenchy Aug 25 '19 at 12:51
  • Use interfaces? – ikwillem Aug 25 '19 at 13:02
  • @progman i have edited the question and show the result of TypeOf and GetType() – Frenchy Aug 25 '19 at 13:07
  • 1
    @Frenchy Can you check with "Make object ID" in the debug session or by other means that the two types returned are in fact the actual same `System.Type` object and not just different type objects, which reference two different type instance of a class named "PluginModel" by random? Also check additional debug information in the debug session about these two types to verify that they reference the same class from the same assembly in the same version and build time. Please include the full error message/exception you get when you try to cast your object with `var w = (PluginModel)viewmodel;`. – Progman Aug 25 '19 at 13:15
  • @ikwillem i need abstract class...its not easy to change with interface for my problem.. i have to do more tests but i dont think the problem is here....between interfaces vs abstract class.. – Frenchy Aug 25 '19 at 13:16
  • @Progman my exception error is (i have translated in english) :System.InvalidCastException : 'Impossible to do a cast of type object 'Cockpit.RUN.ViewModels.PushButton_ViewModel' to 'Cockpit.RUN.Common.PluginModel'.' i am looking for Make object id... – Frenchy Aug 25 '19 at 13:28
  • You are trying to use different assemblies which have little knowledge of each other. This is why you need interfaces, they separate implementation. You can just add interfaces then switch from a collection of "PluginModels" to a collection of "IPluginModels". I'll post a solution later. – ikwillem Aug 25 '19 at 14:03
  • @Progman thanks for showing the way... my dll was never updated because a ghost busy was in effect dunno why.... great idea the ID object... – Frenchy Aug 25 '19 at 15:52

1 Answers1

1

The interfaces (dll):

public interface IPlugin
{
    ICallbacks Callbacks { get; set; }

    string NameUC { get; set; }

    double Width { get; set; }
    double Height { get; set; }
    double Left { get; set; }
    double Top { get; set; }

    double ZoomFactorFromPluginModel { get; set; }
}
public interface ICallbacks
{
    void SomeCallback();
}

Your implementation:

public class PushButton_ViewModel : PluginModel, IPlugin
{
    // How was this going to work? Who makes the instance?
    //public PushButton_ViewModel(params object[] settings)
    //{
    //    NameUC = (string)settings[2];
    //    Layout = new LayoutPropertyViewModel();
    //}

    public ICallbacks Callbacks { get; set; }

    public override double Left
    {
        get => Layout.UCLeft;
        set => Layout.UCLeft = value;
    }
    public override double Top
    {
        get => Layout.UCTop;
        set => Layout.UCTop = value;
    }
    public override double Width
    {
        get => Layout.Width;
        set => Layout.Width = value;
    }
    public override double Height
    {
        get => Layout.Height;
        set => Layout.Height = value;
    }                         
}

Then initialize it

 Type t = Type.GetType("PushButton_ViewModel");
 object obj = FormatterServices.GetUninitializedObject(t);
 IPlugin instance = obj as IPlugin;
 instance.NameUC = "the name";
 instance.Callbacks = new CallbacksModel();
ikwillem
  • 1,044
  • 1
  • 12
  • 24
  • thanks for your answer, but i cant apply that on my project directly...my instance is built by the resolutionRoot.TryGet() method (Ninject). but i keep that in my mind...So my problem is resolved, the cause is busy system...dunno why...but after reboot all seems ok now.. – Frenchy Aug 25 '19 at 15:55
  • Clean your solution more often, that might have helped too. I don't like the injection solutions, I think that's why they have interfaces, which you learned on page 2 of the c# introduction (after "class"). Don't get me wrong, I like that it is possible, but actually using it in normal projects is overdoing it. Also I think you are overthinking my solution, implementing interfaces goes on top of your existing code, do it the right way and you should not have to change any existing code (except for your constructors ;) – ikwillem Aug 25 '19 at 17:13