简体   繁体   中英

ASP.NET MVC - Model's metadata used from model object type instead of declared model of the View

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.

I do not believe the difference between Editor() and EditorFor() is a bug. I think this is because EditorFor() has access to the type information of the strongly typed model, whereas Editor has to use reflection to get the type, which returns the actual type and not the interface type used.

I think what you are seeing is that the strongly typed versions have more context and can get at the interface information rather than simply having to call GetType() .

Validation is working correctly because you told the model binder to bind to MyModelBinder which binds to MyModelImplementation. Validation is based on the method you are posting to and the arguments it takes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM