Unable to cast object of type 'Castle.Proxies.

2019-06-20 04:22发布

问题:

I've recently discovered the very useful design time attributes from Blend for a WPF component, which (among other things) allows you to set a DataContext only at design time. Awesome!

Combined with the DesignInstance attribute, you can set a type to be automatically created and bound to during design time, allowing you to use the Visual Studio Designer with some context as to what your WPF component will actually look like at run time. Its really nice, and I wish it hadn't taken me so long to discover.

Obviously, because I'm here and not living it up in programmer heaven I've encountered a problem while using these design time attributes.

I've created a design time wrapper around one my ViewModels, which has a parameterless constructor (so it can be created by the designer). Inside its constructor, it uses NSubstitute to mock out all of the dependencies injected into the ViewModel it inherits from.

Using this design time class in the designer results in an error much like the following:

Unable to cast object of type 'Castle.Proxies.XProxy' to type 'X'.

(With the X being replaced with one of my injected dependencies).

You can use the following minimal set of code to reproduce the problem.

Create a WPF Application in VS2013 targeting .NET Framework 4.5.1 (it might happen in previous versions too, I don't know) with the following files in it.

View.xaml

<Page 
    x:Class="DesignTimeNSubstituteIssue.Views.View"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:DesignTimeNSubstituteIssue_Views_DesignTime="clr-namespace:DesignTimeNSubstituteIssue.Views.DesignTime"
    mc:Ignorable="d" 
    d:DataContext="{d:DesignInstance Type=DesignTimeNSubstituteIssue_Views_DesignTime:DesignTimeViewModel, IsDesignTimeCreatable=True}">
    <Grid>
        <TextBlock Text="{Binding Message, FallbackValue=Design_Time_Message_Failed_Using_Fallback}"/>
    </Grid>
</Page>

ViewModel.cs

using DesignTimeNSubstituteIssue.Services;

namespace DesignTimeNSubstituteIssue.ViewModels
{
    public class ViewModel
    {
        public ViewModel(XDependency dependency)
        {
            _Dependency = dependency;
        }

        private readonly XDependency _Dependency;
        public string Message { get; protected set; }
    }
}

DesignTimeViewModel.cs

using DesignTimeNSubstituteIssue.Services;
using DesignTimeNSubstituteIssue.ViewModels;
using NSubstitute;

namespace DesignTimeNSubstituteIssue.Views.DesignTime
{
    public class DesignTimeViewModel : ViewModel
    {
        public DesignTimeViewModel()
            : base(Substitute.For<XDependency>())
        {
            Message = "This is a Design Time message.";
        }
    }
}

XDependency.cs

namespace DesignTimeNSubstituteIssue.Services
{
    public interface XDependency
    {

    }
}

Compile, close and reopen the solution and open View.xaml in the designer. It will work just fine. Then, close the designer, rebuild solution and open View.xaml in the designer again, and you will get the following error:

Unable to cast object of type 'Castle.Proxies.XDependencyProxy_1' to type 'DesignTimeNSubstituteIssue.Services.XDependency'.

When this error occurs, the designer stops using the specified DesignTimeViewModel, and falls back to having no DataContext at all.

The only way to fix this is to close and reopen the solution.

I suspect I know what is happening, but I don't know why its happening or how to fix it.

I think that on the first compile the designer is obtaining a reference to the assembly and caching it. When the second compile occurs, the assembly is rebuilt and is mostly the same, but the NSubstitute proxy is regenerated with a new suffix (like Castle.Proxies.XDependencyProxy_2 or something) which wasn't in the first assembly, so the designer doesn't know that that proxy actually implements the XDependency interface. This is purely conjecture on my part.

I can create a workaround by not using NSubstitute, and manually mocking the dependencies, but I'm interested to see if someone can shed some light on the subject.

回答1:

It looks as though both my original project AND minimal reproduction project do not version the assembly correctly, meaning the designer doesn't know that it needs to reload the assembly (because the new one is exactly the same version as the old one). Changing the assembly version to include an autogenerated version number appears to fix the problem.