How do I make EditorFor conditionally readonly?

24,073

Solution 1

Today I had to deal with this issue as I had to dynamically set a "readonly" attribute to a Html.TextBoxFor element. I ended up writing the following helper method which allowed me to workaround the issue while keeping a DRY approach:

/// <summary>
/// Gets an object containing a htmlAttributes collection for any Razor HTML helper component,
/// supporting a static set (anonymous object) and/or a dynamic set (Dictionary)
/// </summary>
/// <param name="fixedHtmlAttributes">A fixed set of htmlAttributes (anonymous object)</param>
/// <param name="dynamicHtmlAttributes">A dynamic set of htmlAttributes (Dictionary)</param>
/// <returns>A collection of htmlAttributes including a merge of the given set(s)</returns>
public static IDictionary<string, object> GetHtmlAttributes(
    object fixedHtmlAttributes = null,
    IDictionary<string, object> dynamicHtmlAttributes = null
    )
{
    var rvd = (fixedHtmlAttributes == null)
        ? new RouteValueDictionary()
        : HtmlHelper.AnonymousObjectToHtmlAttributes(fixedHtmlAttributes);
    if (dynamicHtmlAttributes != null)
    {
        foreach (KeyValuePair<string, object> kvp in dynamicHtmlAttributes)
            rvd[kvp.Key] = kvp.Value;
    }
    return rvd;
}

It can be used in the following way:

var dic = new Dictionary<string,object>();
if (IsReadOnly()) dic.Add("readonly", "readonly");
Html.TextBoxFor(m => m.Name, GetHtmlAttributes(new { @class="someclass" }, dic))

The code is quite self-explanatory, however I also explained the underlying logic in this post on my blog.

Solution 2

EDIT: MVC 5

Controller

ViewBag.Readonly=true;//false

View

@Html.EditorFor(model => model.Quantity, ViewBag.Readonly ? (object)new { htmlAttributes = new { @readonly = "readonly", @class = "form-control" }} : new { htmlAttributes = new { @class = "form-control" } })

Solution 3

It's very simple. Do it like this.

@if((bool)ViewBag.Readonly)
{
    @Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { @class = "form-control", @readonly = "readonly" } })
}
else
{
    @Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { @class = "form-control" } })
}

Solution 4

In case you have many places in your view with such logic, I think using the library FluentDataAnnotations can keep you code clean and clear.

If you are familiar with FluentValidator, it's very similar using.

In your case all the logic will moved from a view to model annotation class. In you will have only @Html.EditorFor(model => model.Quantity)

It even allows to use model properties in conditions e.g. this.When(model => !model.AllowEditPhone, () => { this.For(m => m.Phone).SetReadOnly(false); });

Here the NuGet package that requires ASP.NET MVC 5. (Support for ASP.NET Core are in progress.)

Solution 5

By writing a helper method the DRY principal can be respected.

    using System.Web.Mvc.Html;

    public static MvcHtmlString Concat(this MvcHtmlString first, params MvcHtmlString[] strings)
    {
        return MvcHtmlString.Create(first.ToString() + string.Concat(strings.Select(s => ( s == null ? "" : s.ToString()))));
    }

    public static MvcHtmlString ConditionalEditFor<TModel,TValue>(this HtmlHelper<TModel> helper, bool EditCondition, Expression<Func<TModel, TValue>> Expression)
    {
        helper.ConditionalEditFor(EditCondition,Expression,false);
    }

    public static MvcHtmlString ConditionalEditFor<TModel, TValue>(this HtmlHelper<TModel> helper, bool EditCondition, Expression<Func<TModel, TValue>> Expression, bool IncludeValidationOnEdit)
    {
        if (EditCondition)
        {
            if (!IncludeValidationOnEdit)
                return EditorExtensions.EditorFor<TModel, TValue>(helper, Expression);
            else
                return EditorExtensions.EditorFor<TModel, TValue>(helper, Expression).Concat(ValidationExtensions.ValidationMessageFor<TModel, TValue>(helper, Expression));
        }
        else
        {
            return DisplayExtensions.DisplayFor<TModel, TValue>(helper, Expression);
        }
    }

then in your view:

add a conditional statement to determine readonly e.g.

@{bool IsReadOnly = YourCondition;}

@Html.ConditionalEditFor(!IsReadOnly/*condition*/, model => model.YourProperty,true /*do validation*/)

you can then add whatever other overrides you want.

Share:
24,073
Jordan
Author by

Jordan

Updated on May 06, 2021

Comments

  • Jordan
    Jordan about 3 years

    I have this line of code:

    @Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { @class = "form-control", @readonly = "readonly" } })
    

    I have a variable in my view data dictionary called Readonly. How do I make Quantity read-only if ViewBag.Readonly is true and not read only if it is false?

    Simple thing, but the combination of Razor with HTML (which is ancient) makes otherwise simple things impossible.

    Edits:

    I don't want to use an if statement. That is a last resort because it violates DRY which I have been severely burned many times in the past for not following.

    The line I have above does work insofar as it makes the text box read-only. I need to make this conditional based upon my view state.

    Solution:

    I've used the following. It still is a DRY violation, but it reduces it to one line.

    @Html.EditorFor(model => model.Quantity, new { htmlAttributes = ViewBag.Readonly ? (object)new { @class = "form-control", @readonly = "htmlsucks" } : (object)new { @class = "form-control" } })