Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding conflict between a property named Title in my Model and View.Title in my View (in MVC)

My Model contains a property named Title, and in my Create view I set the page title using ViewBag.Title.

This creates the following problem: the form generated by Html.Editor will display the text from ViewBag.Title, instead of the model's Title value.

The only workaround I have found is first calling Html.Editor, and then setting the View.Title.

Does anyone have a better solution?

Edit 1: I am using MVC 3.

Edit 2: This is my DisplayTemplates/Object.cshtml:

@model dynamic
@using Iconum.VS10CS040.Library.Web.MVC3.Helpers

@if (ViewData.TemplateInfo.TemplateDepth > 1) {
    <span class="editor-object simple">@ViewData.ModelMetadata.SimpleDisplayText</span>
} else {
    foreach (var prop in ViewData.ModelMetadata.Properties.Where(
            pm => 
                pm.ShowForEdit 
                && !ViewData.TemplateInfo.Visited(pm)      
                && pm.ModelType != typeof(System.Data.EntityState)
                && !pm.IsComplexType             
            )
        ) 
        {
        if (prop.HideSurroundingHtml) {
            <text>@Html.Editor(prop.PropertyName)</text>
        } else {
            string css = "";
            if (prop.Model != null && prop.Model.GetType() != null)
            {
                css += " " + prop.Model.GetType().ToString().ToLower().Replace('.', '-');
            }
            if (prop.DataTypeName != null)
            {
                css += " " + prop.DataTypeName.ToLower();
            }
            if (prop.IsRequired && prop.ModelType.FullName != "System.Boolean")
            {
                css += " required";
            }

            <div class="editor-container @css">
                 <div class="editor-label">
                    @if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
                    {
                        // Use LabelWithForThatMatchesTheIdOfTheInput instead of Label because of a bug (fixed in MVC 3)
                       @Html.LabelWithForThatMatchesTheIdOfTheInput(prop.PropertyName)
                    }
                    @if (prop.IsRequired && prop.ModelType.FullName != "System.Boolean")
                    {
                        @Html.Raw(" <span class=\"required\">*<span>");
                    }
                </div>
                <div class="editor-field">
                    @* This the line that causes my problem *@
                    @Html.Editor(prop.PropertyName) 
                    @Html.ValidationMessage(prop.PropertyName)
                </div>
            </div>
        }
        } //foreach

    // Loop though all items in the Model with an TemplateHint (UIHint)
    foreach (var prop in ViewData.ModelMetadata.Properties.Where(
           pm => pm.ShowForEdit
           && !ViewData.TemplateInfo.Visited(pm)
           && pm.ModelType != typeof(System.Data.EntityState)
           && !pm.IsComplexType
           && pm.TemplateHint != null
           && (
            pm.TemplateHint == "jWYSIWYG0093"
            ||
            pm.TemplateHint == "jQueryUIDatepicker"
            ||
            pm.TemplateHint == "CKEditor"
           )
           )
       )
    {
        // TODO: check for duplicate js file includes
        @Html.Editor(prop.PropertyName, prop.TemplateHint + "-Script")
    }    

}
like image 325
Frank van Eykelen Avatar asked Dec 07 '10 09:12

Frank van Eykelen


People also ask

How does model binding works in model view controller?

How Model Binding Works. Model binding is a simplistic way to correlate C# code with an HTTP request. The model binding applies to transforming the HTTP request data in the query's form string and form collection of the action method parameters. We can consider these parameters to be primitive type or complex type.

What is bind property in MVC?

Here, you will learn about to bind a model object to an action method parameters in the ASP.NET MVC application. The model binding refers to converting the HTTP request data (from the query string or form collection) to an action method parameters. These parameters can be of primitive type or complex type.

How do I bind a model to view in MVC core?

How does model binding work in ASP.NET Core MVC. In an empty project, change Startup class to add services and middleware for MVC. Add the following code to HomeController, demonstrating binding of simple types. Add the following code to HomeController, demonstrating binding of complex types.


2 Answers

I would recommend using EditorFor instead of Editor.

Html.EditorFor(x => x.Title)

instead of:

Html.Editor("Title")

This way not only that the view takes advantage of your view model but it behaves as expected in this case.

Example with ASP.NET MVC 3.0 RTM (Razor):

Model:

public class MyViewModel
{
    public string Title { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.Title = "ViewBag title";
        ViewData["Title"] = "ViewData title";
        var model = new MyViewModel
        {
            Title = "Model title"
        };
        return View(model);
    }
}

View:

@model AppName.Models.MyViewModel
@{
    ViewBag.Title = "Home Page";
}

@Html.EditorFor(x => x.Title)

@{
    ViewBag.Title = "Some other title";
}

So no matter how much we try to abuse here the editor template uses the correct model title (which is not the case if we used Html.Editor("Title")).

like image 138
Darin Dimitrov Avatar answered Oct 27 '22 03:10

Darin Dimitrov


As suggested by the other answers, using EditorFor instead of Editor seems to work around the problem. However, using EditorFor requires knowledge of the model type and property type at compile-time, which isn't the case for Object.cshtml.

You can still do this by building up and calling the correct generically-constructed EditorFor method using reflection. The code to do this is really messy, so here are some re-usable extension methods to do it for you.

Use them like this in Object.cshtml where prop is an instance of ModelMetadata like in the question:

@Html.DisplayFor(prop)
@Html.LabelFor(prop)
@Html.EditorFor(prop)
@Html.ValidationMessageFor(prop)

Here are the extension methods:

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace ASP
{
    public static class NonStronglyTypedStronglyTypedHtmlHelpers
    {
        public static MvcHtmlString DisplayFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
        {
            return StronglyTypedHelper(html, h => h.DisplayFor, prop);
        }

        public static MvcHtmlString EditorFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
        {
            return StronglyTypedHelper(html, h => h.EditorFor, prop);
        }

        public static MvcHtmlString LabelFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
        {
            return StronglyTypedHelper(html, h => h.LabelFor, prop);
        }

        public static MvcHtmlString ValidationMessageFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
        {
            return StronglyTypedHelper(html, h => h.ValidationMessageFor, prop);
        }

        private static MvcHtmlString StronglyTypedHelper(HtmlHelper html, Func<HtmlHelper<object>, GenericHelper<object>> accessMethod, ModelMetadata prop)
        {
            var constructedMethod = MakeStronglyTypedHelper(html, accessMethod, prop);
            var genericPropertyExpression = MakePropertyExpression(prop);
            var typedHtmlHelper = MakeStronglyTypedHtmlHelper(html, prop.ContainerType);

            return (MvcHtmlString)constructedMethod.Invoke(null, new object[] { typedHtmlHelper, genericPropertyExpression });
        }

        private static MethodInfo MakeStronglyTypedHelper(HtmlHelper html, Func<HtmlHelper<object>, GenericHelper<object>> accessMethod, ModelMetadata prop)
        {
            var objectTypeHelper = new HtmlHelper<object>(html.ViewContext, html.ViewDataContainer, html.RouteCollection);
            var runMethod = accessMethod(objectTypeHelper);
            var constructedMehtod = runMethod.Method;
            var genericHelperDefinition = constructedMehtod.GetGenericMethodDefinition();
            return genericHelperDefinition.MakeGenericMethod(prop.ContainerType, prop.ModelType);
        }

        private static object MakeStronglyTypedHtmlHelper(HtmlHelper html, Type type)
        {
            var genericTypeDefinition = typeof(HtmlHelper<>);
            var constructedType = genericTypeDefinition.MakeGenericType(type);
            var constructor = constructedType.GetConstructor(new[] { typeof(ViewContext), typeof(IViewDataContainer), typeof(RouteCollection) });
            return constructor.Invoke(new object[] { html.ViewContext, html.ViewDataContainer, html.RouteCollection });
        }

        private static LambdaExpression MakePropertyExpression(ModelMetadata prop)
        {
            var propertyInfo = prop.ContainerType.GetProperty(prop.PropertyName);
            var expressionParameter = Expression.Parameter(prop.ContainerType);
            var propertyExpression = Expression.MakeMemberAccess(expressionParameter, propertyInfo);
            return Expression.Lambda(propertyExpression, expressionParameter);
        }

        private delegate MvcHtmlString GenericHelper<TModel>(Expression<Func<TModel, object>> expression);
    }
}
like image 44
Sam Avatar answered Oct 27 '22 02:10

Sam