Reputation: 1345
I've seen a number of similar questions to this, but none seem to have an answer that gets me any further than I am already.
I have a ViewModel that contains some complex types that represent certain stock UI controls and have some validation logic attached. I need to get these to render to a page based on an EditorTemplate/DisplayTemplate and also need the properties to bind properly on submission of the form so that the developer can use the view model within their controllers etc.
Simple example of this is
public class TestViewModel
{
public HierarchyField MyField {get; set; }
}
At it's simplest, I need the developer to be able to create an editor for this view model with a simple
@Html.EditorForModel()
And for any editable parts of the HierarchyField, fields to be set up within a custom EditorTemplate for that type.
I've gotten as far as allowing this to render, by using a custom MetaDataProvider and overriding IsComplexType which, although not something I'm entirely comfortable with, does do the job. Details of that solution are here (http://blogs.msdn.com/b/stuartleeks/archive/2010/04/01/collections-and-asp-net-mvc-templated-helpers-part-3.aspx?Redirected=true)
I'm now stuck trying to get the model binding to update the property on submission of the form and to call a method on the HierarchyField class to perform a little custom validation and update the ModelState based on the result of that call.
I first created a subclass of DefaultModelBinder that had logic within the BindProperty override to check whether the property being bound was of this type. All good so far. Problem comes when I want to call the instance method that takes the intended value and attempts validation of that value. The BindProperty method gives me an instance of PropertyDescriptor which allows me to find the metadata about my property, and a BindingContext with a ValueProvider that allows me to read the value, however, there's not an instance of my complex type returned from:
bindingContext.ValueProvider.GetValue(propertyDescriptor.Name + ".Value")
Where propertyDescriptor.Name + ".Value" equates to the fact that in my EditorTemplate I have the following:
@Html.TextBoxFor(model => model.Value)
Instead of the type, because I've obviously only got a reference to the Value property in the fields that are posted back, I get a String[]. I'm assuming, to get this to work, I will need to register a ValueProvider of some sort for HierarchyField, but ideally I'm looking for something a little more generic, as I have a number of these types to set up, all of which subclass a common abstract type (which all have a property called 'Value' which returns a string representation of their value).
If I'm honest, this whole process seems so painful that I feel like I'm missing something really simple to do this. Unfortunately, I do need this to be generic as there are dozens of these fields, and I do need this model, or something similar for the fields to handle their validation as much of this cannot be done with DataAnnotation validation attributes. I cannot create custom code that's tied to a specific view model and I'd prefer to minimise code that's tied to a specific field, prefering something that applies to the base class (FieldBase) and any of its subclasses.
Happy to post additional code if anyone needs to see it.
Upvotes: 1
Views: 6320
Reputation: 4178
I've been using complex types and custom rendering in a few projects without any problems. Maybe I've misunderstood your question but I thought I can post a simplified working example (MVC4) and maybe it'll help you on the way.
In my example the complex type is defined as:
[ComplexType]
public class AccessType
{
public bool ReadAccess { get; set; }
public bool EditAccess { get; set; }
}
The model is defined as:
public class UserAccess
{
[Key]
public Int32 UserId { get; set; }
public AccessType AccessType { get; set; }
}
I've created the following display template in Views\Shared\DisplayTemplates named AccessType.cshtml to handle the rendering of the complex type when it's being displayed.
@model [path to complex type].AccessType
@Html.LabelFor(m => m.EditAccess,"Read access")
@Html.DisplayFor(m => m.ReadAccess)
@Html.LabelFor(m => m.EditAccess, "Edit access")
@Html.DisplayFor(m => m.EditAccess)
I've created the following edit template in Views\Shared\EditorTemplates named AccessType.cshtml to handle the rendering of the complex type when it's being edited.
@model [path to complex type].AccessType
@Html.LabelFor(m => m.ReadAccess,"Read access")
@Html.CheckBoxFor(m => m.ReadAccess)
@Html.LabelFor(m => m.EditAccess, "Edit access")
@Html.CheckBoxFor(m => m.EditAccess)
To the view files for the model using this complex type I've made the following changes:
To the _CreateOrEdit.cshtml file I've added this line to render the complex type part:
@Html.EditorFor(m => m.AccessType)
...to Delete.cshtml and Details.cshtml I've added:
@Html.DisplayFor(m => m.AccessType)
...and to Index.cshtml I've added:
@Html.DisplayFor(_ => item.AccessType)
That was all I needed to render and to use the complex type.
If your problem is more complex then please add some more code to illustrate.
Upvotes: 6