I have a WPF application that makes use of the MVC pattern with generics. The three legs of the MVC pattern are implemented in common base classes. This MVC pattern proved to be so useful that I moved the base classes into a common utility assembly so that multiple applications could leverage the functionality and everything continued to work just fine. Lately, one particular application had a lot of functionality in common with all of the views that would not be appropriate for the other applications so I inserted a middleman level of derivation into which this application specific common functionality could live without duplicating in each of the application views.
This extra layer to the inheritance train is simple but talking about it is a bit fuzzy in terminology. To reduce the fuzziness, think of the layers as derived, middleman and base. Up at the top is the XAML/CS code that specifies a visual element. This is the derived layer. Classes here make use of a middleman classes. The middleman classes are defined and live in the application. Multiple visual elements are derived from this middleman class. The middleman classes are, in turn, derived from a base classes which are in a common utility assembly. Multiple applications consume this utility assembly and make use of the MVC classes defined within.
So, this gives us the application visual element which is derived from a middleman View which is derived from a base View which is derived from a WPF UserControl.
Here's where the failure comes into play. If the application directly inherits its visual elements from the common utility View then all is well. However, if the derived View inherits from the middleman View then VS 2012's XAML parser becomes confused. The code still builds and runs but the parser at design time cannot properly deal with the XAML script. At design time the XAML parser displays bogus errors like "Error 1 The type 'View`1' does not support direct content." Also, VS 2012 crashes several times a day.
My question is simple - is there something wrong with how I've implemented the arrangement or is the multiple levels of class derivation more than VS 2012's XAML parser is capable of?
Here's how the broken code is constructed. At the tip of the iceberg is the derived View. It uses generics to pass its associated Controller class down to the base View and it also defines the derived Model it wants to bind its own elements to.
<mvc:View x:Class="WpfDerivedMvcTest.Main.MainView"x:TypeArguments="local:MainController" 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:mvc="clr-namespace:WpfDerivedMvcTest.MVC"
xmlns:local="clr-namespace:WpfDerivedMvcTest.Main"
mc:Ignorable="d"
d:DesignHeight="480" d:DesignWidth="800"><UserControl.Resources><local:MainModel x:Key="Model" /></UserControl.Resources><Grid><Label Content="Main View" /></Grid></mvc:View>The important things to note about the above script are:
- The view is derived from a <application>.View class which is in turn derived MVC.View which is derived from a UserControl.
- Declaration of the Controller class to support the generic Controller class specification in the View's constructor
- Declaration of the Model and assigning it a key name. The Controller will gain access to the Model's properties when it needs to update something. The View's elements are bound to properties in the Model.
In the above case all of the referenced classes reside in the same folder and are derived from classes in an MVC folder that resides in the application.
The CSharp code for the View looks like this:
// Bunch of usings snipped. . . .
using WpfDerivedMvcTest.MVC;
namespace WpfDerivedMvcTest.Main
{
public partial class MainView : View<MainController> {
public MainView()
{
InitializeComponent();
}
}
}The most important things about the above is the generic argment and the specification of the base class View. This is the local View, not the one in the Utility assembly.
And the following is the View middleman base class implementation.
namespace WpfDerivedMvcTest.MVC
{
public class View<T> : Common.MVC.View<T>
where T : Common.Interfaces.IController, new()
{
}
}All of the extra stuff has been snipped to leave the problematic portion of the code.
As I said, this class is in the application's MVC folder. It uses generics so that it doesn't need to know about the class type of the Controller. This is possible because the generic argument is derived from an interface which is what the 'where T " garbage is all about. Anyway, the View's base class is in the utility assembly in the Common.MVC namespace.
using Common.Interfaces;
namespace Common.MVC
{
public class View<T> : UserControl
where T : IController, new()
{
// ********************* Class properties ******************************** //
protected T Controller
{
get { return _Controller; }
set { _Controller = value; }
}
private T _Controller;
// ********************* Class constructors ****************************** //
public View()
{
// Make the controller and let it know what View it is associated with.
Controller = new T();
this.Loaded += View_Loaded;
this.IsVisibleChanged += View_IsVisibleChanged;
}
// Many things snipped.
}
}OK, things are getting a bit more interesting now. This base class does not know what the Controller's actual class is but it does know that the Controller supports a particular interface. When the View is constructed it takes the opportunity to create the Controller object based upon the generic argument. The View passes a reference to itself to the Controller so that the controller can have some limited access to the View object. For brevity's sake a number of the details have been snipped but the important stuff is still there.
Now let's look at the derived Controller and its inheritance chain.
using Common.Interfaces;
using WpfDerivedMvcTest.MVC;
using WpfDerivedMvcTest.Main;
namespace WpfDerivedMvcTest.Main
{
public class MainController : Controller<MainModel>
{
// Many things snipped.
}
}The above is the Controller that is defined in the application. This is the class that the XAML and the derived view knows about. There is a small bit of magic here in that the Controller has been given the responsibility of explicitly knowing about the MainModel's class which it passes down to its own base class. The derived Model was declared as a resource of the derived View so the code does not need to explicitly instantiate the Model object.
The derived Controller comes from the application's MVC middleman Controller class which is, of course, itself derived from the Controller in the common utility assembly.
namespace WpfDerivedMvcTest.MVC
{
public class Controller<T> : Common.MVC.Controller<T>, Common.Interfaces.IController
{
// ********************* Class properties ******************************** //
// Snipped. There were a bunch of properties that
// were in common of all of the application controllers.
// ********************* Class members *********************************** //
// More snipping. There were a number of functions
// common to all of the application controllers.
}
} The important thing about the above middleman Controller is that it passes the generic argument on to the base Controller class in the common utility assembly and that the Controller itself conforms to a specific interface.
And this is the root class from which all Controllers are derived. It lives in the common utility assembly.
using Common.Interfaces;
namespace Common.MVC
{
public class Controller<T> : IController
{
// ********************* Class properties ******************************** //
// Much snipping of base properties in common to
// all controllers like the Model and View.
// ********************* Class constructors ****************************** //
/// <summary>
/// Default constructor. This is required so that the designer mode XAML
/// files can still display properly.
/// </summary>
public Controller()
{
}
/// <summary>
/// Run time constructor.
/// </summary>
/// <param name="view">View associated with this Controller.</param>
/// <param name="modelName">Resource key name of the model as it is known in the XAML file.</param>
public Controller( UserControl view, string modelName )
{
// snipped.
}
// ********************* Class members *********************************** //
// Snipped. There were a number of virtual
// methods and utility methods used by
// the derived classes.
}
}Likewise, there is a derived Model that is in the application which is derived from a base class that is in the application. This base class is in turn derived from a base Model that lives in the utility assembly.
Here's an example of the top level model.
using WpfDerivedMvcTest.MVC;
namespace WpfDerivedMvcTest.Main
{
public class MainModel : Model
{
}
}The middleman model looks like this:
namespace WpfDerivedMvcTest.MVC
{
public class Model : Common.MVC.Model
{
// Various properties that the View binds to
// are snipped.
}
}Finally, down in the base Model class, common properties and the OnPropertyChanged notification are handled.
namespace Common.MVC
{
public class Model : INotifyPropertyChanged
{
// ********************* INotifyPropertyChanged ************************** //
public event PropertyChangedEventHandler PropertyChanged;
protected void SendPropertyChanged( string property )
{
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( property ) );
}
}
// Bunch of utility things, thread checking and
// whatnot snipped for brevity.
}
}
OK, so with the exception of the generics there is not a lot of magic going on here. None the less when the full derivation chain is in place the VS 2012 XAML parser has problems. By simply skipping the middleman layer and deriving from the base utility MVC
classes everything works great. Any ideas where the problem is coming from?Richard Lewis Haggard