9

I'm looking to use attributes to mark view model properties as readonly so that the view fields are read only in the rendered view. Applying System.ComponentModel.DataAnnotations.EditableAttribute appears to be the exact attribute I need but it does not appear to work i.e. textbox fields are still editable. I've looked all around and can find no answers and only a few related questions. The editable attribute as applied below does not work when the view is rendered.

[Display(Name = "Last Name")]
[Editable(false, AllowInitialValue = true)]
public string LastName { get; set; }

I can achieve the readonly behaviour using a view helper function like this but my preference is to use an attribute on the model property.

@functions {
    object getHtmlAttributes()
    {
    if (@ViewBag.Mode == "Edit")
    {
      return new {style = "width:100px;background:#ff6;", @readonly = "readonly"};
    }

    return new { style = "width:100px;" };  
}
} 

@Html.TextBoxFor(model => model.FirstName, getHtmlAttributes())

Other attributes work perfectly ok including custom validation attributes. Can you tell me if the data annotations editable attribute works in this context, should just work as applied above or is there something else that needs to be done? Thanks.

Steven Chalk
  • 311
  • 1
  • 2
  • 9
  • On the create view we want users to be able to provide initial values for all fields but once in edit view some of these fields need to become readonly. – Steven Chalk Apr 13 '12 at 15:15

5 Answers5

5

The EditableAttribute documentation states:

The presence of the EditableAttribute attribute on a data field indicates whether the user should be able to change the field's value.

This class neither enforces nor guarantees that a field is editable. The underlying data store might allow the field to be changed regardless of the presence of this attribute.

Unfortunately, this means that the use of this attribute doesn't have any effect on validation in MVC. This feels wrong but it makes sense if you think about what it would take to implement in the MVC framework. For instance, in a typical "Edit" view, the user does an initial GET request where the Model is populated (usually from a DB record) and given to the View to be rendered to the user. Then the user makes some edits and then submits the form. Submitting the form causes a new instance of the Model to be constructed from the POST parameters. It would be very difficult for the validators to ensure the field has the same value in both object instances, because one of the instances (the first one from the GET request) has already been disposed of.

Well if the Attribute has no functionality, why even bother to use it?

My best guess is that they expect developers to use it in their code to show intent. More practically, you can also write your own custom code to check for the presence of this Attribute...

AttributeCollection attributes = TypeDescriptor.GetAttributes(MyProperty);
if (attributes[typeof(EditableAttribute)].AllowEdit)
{
   // editable
}
else
{
   // read-only
}

Also keep in mind that these DataAnnotation attributes are not only for MVC applications, they can be used for many different types of applications. Even though MVC doesn't do anything special with this Attribute, other frameworks have implemented functionality/validation for this attribute.

Jesse Webb
  • 43,135
  • 27
  • 106
  • 143
  • This class neither enforces nor guarantees that a field is editable. This is the bit that confused me when I posted the question. You're right to say that using editable and readonly attributes is only indicative and you have to look for and then render you view accordingly. I've recently spent some more time on this issue and think I've now nailed it. See answer below. – Steven Chalk Nov 29 '12 at 19:53
  • @StevenChalk - Glad to see you've found a solution! I have incorporated a similar solution to yours in my project. – Jesse Webb Nov 29 '12 at 21:40
2

Just semi-solved this issue myself.

[HiddenInput(DisplayValue=true)]

The field is displayed but not editable.

panzerblitzer
  • 184
  • 3
  • 12
  • 1
    It is worth mentioning that the `HiddenInputAttribute` is in the `System.Mvc` namespace. [Here is the doc.](http://msdn.microsoft.com/en-us/library/system.web.mvc.hiddeninputattribute.aspx) If you are annotating your business model classes instead of using ViewModels, it doesn't make sense to make your business Model dependent on the MVC framework. If, on the other hand, you are using ViewModels with annotations, this Attribute will work without any additional code. – Jesse Webb Nov 29 '12 at 21:45
1

Do you have a different create scenario? Any particular reason you are allowing an initial value? I ask because the documentation says:

Because you typically want both properties to contain the same value, the AllowInitialValue property is set to the value of AllowEdit in the class constructor.

I'm thinking if you set it to false and don't explicitly declare the AllowInitialValue it will work.

Terry
  • 14,099
  • 9
  • 56
  • 84
  • What the documentation is saying is that AllowInitialValue will be set to the same value as AllowEdit unless you explicitly set AllowInitialValue in the attribute declaration. – Steven Chalk Apr 13 '12 at 15:21
  • Ya I gathered that, was trying to say that if you keep it as false it should work. Does it work when it's set to false? I see now that you have 2 scenarios but just curious if at least one of them works. – Terry Apr 13 '12 at 15:27
  • I used the editable attribute initially without applying the AllowInitialValue and also with AllowInitialValue explicitly set to false. No combination appears to work. – Steven Chalk Apr 13 '12 at 15:37
1

Found that the use of Editable instead Readonly on the model works exactly the same.

[ReadOnly(true)] //or
[Editable(false)]
public string Name { get; set; }

This syntax does work when interrogating a property attribute on the view itself. Also works when attribute is Editable(true)

@if (ViewData.ModelMetadata.Properties.Where(p => p.PropertyName == "Name").First().IsReadOnly)
{ 
    @Html.TextBoxFor(model => model.Name, new { style = "width:190px; background-color: #ffffd6", @readonly =  "readonly" })
} 
else
{
    @Html.TextBoxFor(model => model.Name, new { style = "width:190px; " })
}

Using an editor template here a simple string template:

@model String
@{
IDictionary<string, object> htmlAttributes = new Dictionary<string, object>();
if (ViewData.ModelMetadata.IsReadOnly) //this will be looking at the actual property not the complete model
{
htmlAttributes.Add("style", "width:100px; background-color:#ffffd6");
htmlAttributes.Add("readonly", "readonly");
@Html.TextBox("", Model, htmlAttributes)
}
Steven Chalk
  • 311
  • 1
  • 2
  • 9
  • 2
    The problem with that approach is ReadOnly has a defined purpose in mvc - It prevents binding on a particular field. Its not a UI related field but a model binding related field. By storing other info in it, you are piggy backing on a separate purpose. Just my .02 :) – Adam Tuliper Sep 19 '13 at 06:09
0

don't know if you've figured this out yet, but we use

System.ComponentModel.ReadOnlyAttribute

usage

[ReadOnly(true)]
Steven
  • 860
  • 6
  • 24