I have interface of a model declared, with class implementing it:
public interface IMyModel
{
[Range(1, 1000)]
[Display(Name = "ModelProp From Interface")]
int MyIntProperty { get; set; }
IMySubModel SubModel { get; }
}
public interface IMySubModel
{
[Range(1,1000)]
[Display(Name = "SubModelProp From Interface")]
int MyIntSubProperty { get; set; }
}
I also have model implementation, with different Metadata:
public class MyModelImplementation:IMyModel
{
[Display(Name = "ModelProp From Class")]
[Range(1, 15)]
public int MyIntProperty
{
get;
set;
}
public IMySubModel SubModel { get; set; }
public MyModelImplementation()
{
SubModel = new MySubModelImplementation();
}
}
public class MySubModelImplementation: IMySubModel
{
[Display(Name = "SubModelProp From Class")]
[Range(1, 15)]
public int MyIntSubProperty
{
get;
set;
}
}
I have View, where I use this model interface:
@model MvcApplicationModelInterface.Models.IMyModel
@using (Html.BeginForm())
{
<p>Using Lambda Expression:</p>
@Html.DisplayNameFor(m=>m.MyIntProperty)
@Html.EditorFor(m=>m.MyIntProperty)
@Html.ValidationMessageFor(m=>m.MyIntProperty)
<br/>
@Html.DisplayNameFor(m=>m.SubModel.MyIntSubProperty)
@Html.EditorFor(m=>m.SubModel.MyIntSubProperty)
@Html.ValidationMessageFor(m=>m.SubModel.MyIntSubProperty)
<br/>
<p>Using String Expression:</p>
@Html.DisplayName("MyIntProperty")
@Html.Editor("MyIntProperty")
@Html.ValidationMessage("MyIntProperty")
<br/>
@Html.DisplayName("SubModel.MyIntSubProperty")
@Html.Editor("SubModel.MyIntSubProperty")
@Html.ValidationMessage("SubModel.MyIntSubProperty")
<br/>
@Html.ValidationSummary(true)
<br/>
<input type="submit" name="btnSubmit" value="btnSubmit" />
}
And I have controller that does proper binding and initialization of the model:
[HttpGet]
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
var model = new MyModelImplementation();
model.MyIntProperty = 111;
model.SubModel.MyIntSubProperty = 222;
return View(model);
}
[ActionName("Index"), HttpPost]
public ActionResult Save([ModelBinder(typeof(MyModelBinder))]IMyModel model)
{
return View(model);
}
public class MyModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return new MyModelImplementation();
}
}
The problem:
If in controller's action returns view with NOT NULL model (return View(new MyModelImplementation(){...});), results I see on the screen are:
However if controller's action returns results with null model (return View(null);), then results are:
As you can see, the behavior for Lambda html helpers and string expression helpers is different, and none of those is consistent at all and some behavior looks like a bug:
- (expected) For Lambda helpers DisplayName is taken from IMyModel
- (unexpected) For String expression all metadata is taken from MyModelImplementation class (Another words, if you use Html.DisplayNameFor(m=>m.MyIntProperty) - it shows metadata from Interface (model type Declared in view), however if you use Html.DisplayName("MyIntProperty") it uses metadata of model.GetType()).
- (unexpected) ALL Validation rules and strings are ALWAYS taken from MyModelImplementation (model.GetType()) metadata, instead of declared model type (IMyModel).
- (expected) For string expression helper Model's property is taken from Interface only in case model passed to a view is NULL
- (unexpected) For string expression helper SubModel's property metadata is not retrieved/respected at all, in case model passed to view is NULL
Question:
What is the best workaround for this ASP.NET MVC Bug/Feature? How to force Html extensions always use metadata from model type Declared in a View by default? I tried to use MetadataTypeAttribute, but in this case the one who implements model will get freedom of overwritting original metadata specified in interface, which I don't want to allow. So I'm rather looking for some custom ModelMetadataProvider implementation. Also some metadata attributes are not respected in case of MetadataType, for example RequiredAttribute.