EXCLUDING A PROPERTY FROM SCAFFOLDING

Một phần của tài liệu Giáo trình lập trình ASP.NET Apress pro ASP NET MVC3 framework pre release (Trang 507 - 519)

If we want to exclude a property from the generated HTML, we can use the ScaffoldColumn attribute. Whereas the HiddenInput attribute includes a value for the property in a hidden input elementeven though the user cannot see or edit it, the ScaffoldColumn attribute allows us to mark a property as being off-limits for the scaffolding process. Here is an example of the attribute in use:

public class Person { [ScaffoldColumn(false)]

public int PersonId { get; set; } ...

When the scaffolding helpers see the ScaffoldColumn attribute, they skip over the property entirely – no hidden input elements will be created and no details of this property will be included in the generated HTML. The appearance of the generated HTML will be the same as if we had used the HiddenInput attribute, but no value will be returned for the property during a form submission – this has an effect on model binding, which we discuss later in the chapter. The ScaffoldColumn attribute doesn’t have an effect on the per-property helpers, such as EditorFor.

If we call @Html.EditorFor(m => m.PersonId) in a view, then an editor for the PersonId property will be generated, even when the ScaffoldColumn attribute is present.

Using Metadata for Labels

By default, the Label, LabelFor, LabelForModel and EditorForModel helpers use the names of properties as the content for the label elements they generate. For example, if we render a label like this:

@Html.LabelFor(m => m.BirthDate)

the HTML element that is generated will be as follows:

<label for="BirthDate">BirthDate</label>

Of course, the names we give to our properties are often not what we want to be displayed to the user. To that end, we can apply the DisplayName attribute from the

System.ComponentModel namespace, passing in the value we want as a parameter – this is demonstrated by Listing 8.

Listing 8. Using the DisplayName attribute to define a label

public class Person {

[HiddenInput(DisplayValue=false)]

public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [Display(Name="Date of Birth")]

public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }

When the label helpers render a label element for the BirthDate property, they will detect the Display attribute and use the value of the Name parameter for the inner text, like this:

<label for="BirthDate">Date of Birth</label>

The helpers also recognize the DisplayName attribute, which we can apply in the same way – this attribute has the advantage of being able to be applied to classes, which allows us to use the Html.LabelForModel helper. Listing 9 shows the attribute being used on the Person class itself.

Listing 7. Using the DisplayName attribute on a class [DisplayName("Person Details")]

public class Person {

[HiddenInput(DisplayValue=false)]

public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [Display(Name="Date of Birth")]

public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; }

public bool IsApproved { get; set; } public Role Role { get; set; } }

We can apply the DisplayName attribute to properties as well, but we tend to use this attribute only for model classes. Once we have applied this attribute, if we call this helper:

@Html.LabelForModel()

the following HTML is generated:

<label for="">Person Details</label>

Using Metadata for Data Values

We can also use metadata to provide instructions about how a model property should be displayed – this is how we resolve the problem with our birth date property including a time.

The attribute we need to this is DataType, as shown in Listing 8.

Listing 8. Using the DataType attribute public class Person {

[HiddenInput(DisplayValue=false)]

public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [DataType(DataType.Date)]

[Display(Name="Date of Birth")]

public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }

The DataType attribute takes a value from the DataType enumeration as a parameter – in the example we have specified the DataType.Date value, which causes the templated helpers to render the value of the BirthDate property as a date without the associated time. You can see the effect of this in Figure 7.

Figure 7. Using the DataType attribute

The most useful values of the DataType enumeration are described in Table 3.

Table 3. The values of the DataType enumeration

Value Description

DateTime Displays a date and time (this is the default behavior for System.DateTime values) Date Displays the date portion of a DateTime.

Time Displays the time portion of a DateTime. Text Displays a single line of text

MultilineText renders the value in a textarea element

Password Displays the data so that individual characters are masked from view Url Displays the data as a URL (using an HTML a element)

EmailAddress Displays the data as an email address (using an a element with a mailtohref) The effect of these values depends on the type of the property that they are associated with and the helper we are using. For example, the MultilineText value will lead those helpers that create editors for properties to create an HTML textarea element, but will be ignored by the display helpers. This makes sense – the textarea element allows the user to edit a value, which doesn’t make sense when we are displaying the data in a read-only form. Equally, the Url value only has an effect on the display helpers, which render an HTML a element to create a link.

Using Metadata to Select a Display Template

As their name suggests, templated helpers use display templates to generate HTML. The template that is used is based on the type of the property being processed and the kind of helper being used. We can use the UIHint attribute to specify the template we want to use to render HTML for a property, as shown in Listing 9.

Listing 9. Using the UIHint attribute public class Person {

[HiddenInput(DisplayValue=false)]

public int PersonId { get; set; } [UIHint("MultilineText")]

public string FirstName { get; set; } public string LastName { get; set; } [DataType(DataType.Date)]

[Display(Name="Date of Birth")]

public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }

In the listing, we specified the MultilineText template, which renders an HTML textarea element for the FirstName property when used with one of the editor helpers, such as EditorFor or EditorForModel. The set of built-in templates that the MVC framework includes is shown in Table 4.

Table 4. The built-in MVC framework view templates

Value Effect (Editor) Effect (Display)

Boolean Renders a checkbox for bool values. For nullable bool? values, a select element is created with options for True, False and Not Set.

As for the editor helpers, but with the addition of the disabled attribute which renders read-only HTML controls.

Collection Renders the appropriate template for each of the elements in an IEnumerable

sequence. The items in the sequence do not have to be of the same type.

As for the editor helpers.

Decimal Renders a single-line textbox input element and formats the data value to display two decimal places.

Renders the data value formatted to two decimal places.

EmailAddress Renders the value in a single-line textbox

input element. Renders a link using an HTML a element

and an href attribute that is formatted as a mailto URL.

HiddenInput Creates a hidden input element. Renders the data value and creates a hidden input element.

Html Renders the value in a single-line textbox

input element. Renders a link using an HTML a element.

MultilineText Renders an HTML textarea element which

contains the data value. Renders the data value.

Object See explanation below. See explanation below.

Password Renders the value in a single-line textbox input element so that the characters are not displayed but can be edited.

Renders the data value – the characters are not obscured.

String Renders the value in a single-line textbox

input element. Renders the data value.

Text Identical to the String template Identical to the String template Url Renders the value in a single-line textbox

input element.

Renders a link using an HTML a element.

The inner HTML and the href attribute are both set to the data value.

Caution Care must be taken when using the UIHint attribute. We will receive an exception if we select a template that cannot operate on the type of the property we have applied it to – for example, applying the Boolean template to a string property.

The Object template is a special case – it is the template used by the scaffolding helpers to generate HTML for a view model object. This template examines each of the properties of an object and selects the most suitable template for the property type. The Object template takes metadata such as the UIHint and DataType attributes in account.

Applying Metadata to a Buddy Class

It isn’t always possible to apply metadata to an entity model class – this is usually the case when the model classes are generated automatically, as is sometimes the case with ORM tools such as the Entity Framework (although not the way that we used the Entity Framework in the

SportsStore application). Any changes that we apply to automatically generated classes, such as applying attributes, will be lost the next time that the classes are updated by the tool.

The solution to this problem is to ensure that the model class is defined as partial and to create a second partial class that contains the metadata. Many tools that generate classes

automatically create partial classes by default – this includes the Entity Framework. Listing 10 shows the Person class modified such that it could have been generated automatically – there is no metadata and the class is defined as partial.

Listing 10. A partial model class public partial class Person { public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }

The next step is to define another partial Person class that will correspond to the one shown in Listing 10. The previous class is off- limits to us – any changes that we make will be lost when the class is regenerated, so we create a second partial Person class to give us

something we can make persistent additions to. When the compiler builds our application, the two partial classes are combined. The partial class that we can modify is shown in Listing 11.

Listing 11. Defining the metadata buddy class [MetadataType(typeof(PersonMetadataSource))]

public partial class Person { }

Partial classes must have the same name and be declared in the same namespace – and, of course, be declared using the partial keyword. The key addition for the purposes of metadata is the MetadataType attribute – this allows us to associate the buddy class with the Person class by passing the type of the buddy class as a parameter.

In the listing, we have specified that the metadata for the Person class can be found in a class called PersonMetadataSource, which is shown in Listing 12.

Listing 12. Defining the metadata class class PersonMetadataSource {

[HiddenInput(DisplayValue=false)]

public int PersonId { get; set; }

[DataType(DataType.Date)]

public DateTime BirthDate { get; set; } }

The buddy class only needs to contain properties that we want to apply metadata to – we don’t have to replicate all of the properties of the Person class, for example. In the listing, we have used the HiddenInput attribute to hide the PersonId property and the DataType attribute to ensure that the BirthDate property is displayed correctly.

Working with Complex Type Parameters

The scaffolding templating process relies on the Object template that we described in the previous section – each property is inspected and a template is selected to render HTML to represent the property and its data value.

You may have noticed that not all of the properties have been rendered when we have used the scaffolding helpers EditorForModel and DisplayForModel – in particular, the

HomeAddress property has been ignored. This happens because the Object template only operates on simple types – in particular those types which can be parsed from a string value using the GetConvertor method of the System.Component.TypeDescriptor class. This includes the intrinsic C# types such as int, bool, or double , and plus many common framework types including Guid and DateTime.

The result of this policy is that scaffolding is not recursive – given an object to process, a scaffolding templated view helper will only generate HTML for simple property types and will ignore any properties which are themselves complex objects. Although it can be inconvenient, this is a sensible policy – the MVC framework doesn’t know how our model objects are created and if the Object template was recursive, then we could easily end up triggering our ORM lazy-loading feature, which would lead us to read and render every object in our underlying database. If we want to render HTML for a complex property, we have to do it explicitly, as demonstrated by the view shown in Listing 13.

Listing 13. Dealing with a complex-typed property

@model MVCApp.Models.Person

@{

ViewBag.Title = "Index";

}

<h4>Person</h4>

<div class="column">

@Html.EditorForModel()

</div>

<div class="column">

@Html.EditorFor(m => m.HomeAddress)

</div>

We can use the EditorForModel to deal with the simple properties of our view model object and then use an explicit call to Html.EditorFor to generate the HTML for the

HomeAddress property. The effect of rendering this view is shown in Figure 8.

Figure 8. Explicitly dealing with complex type properties

The HomeAddress property is typed to return an Address object and we can apply all of the same metadata to the Address class as we did to the Person class. The Object template is invoked explicitly when we use the EditorFor helpers on the HomeAddress property and so all of the metadata conventions are honored.

Customizing the Templated View Helper System

We have shown you how to use metadata to shape the way that the templated helpers render data – but this is the MVC framework, so there are some advanced options that let us customize the templated helpers entirely. In the following sections, we’ll show you how to can supplement or replace the built-in support to create very specific results.

Creating a Custom Editor Template

One of the easiest ways of customizing the templated helpers is to create a custom template – this allows us to render exactly the HTML we want. As an example, we are going to create a

custom template for the Role property in our Person class. This property is typed to be a value from the Role enumeration, but the way that this is rendered by default is problematic. Listing 14 shows a simple view which uses the templated helpers to render HTML for this property.

Listing 14. A view that operates on the Person.Role property.

@model MVCApp.Models.Person

<p>

@Html.LabelFor(m => m.Role):

@Html.EditorFor(m => m.Role)

</p>

<p>

@Html.LabelFor(m => m.Role):

@Html.DisplayFor(m => m.Role)

</p>

This is a very simple view – we render labels, an editor and a display for the Role property. The output from rendering this view is shown in Figure 9.

Figure 9. Rendering templates for the Person.Role property

The label and the display templates are fine, but we don’t like the way that the editor is rendered. There are only three values defined in the Role enumeration (Admin, User and Guest), but the HTML that has been generated allows the user to provide an arbitrary value for the property – this is far from ideal. We could, of course, use the Html.DropDownListFor helper, but this isn’t ideal either, because we don’t want to have to duplicate it manually wherever a Role editor is needed.

Instead, we are going to create a custom editor view – which in essence means creating a partial view in a particular location (we described partial views in Chapter 15). We first need to create a folder called EditorTemplates in the ~/Views/Shared folder of our project. We then right click on the new folder and select Add View from the pop-up menu. Set the name of the view to be Role, check the option to create a strongly-typed view and set the model class to be Role, and check the option to create a partial view, s shown in Figure 10.

Figure 10. Creating a partial view to be used as a template

Once we have created the new partial view, we can add standard Razor syntax to generate the editor we require. There are lots of different ways to create the HTML – the simplest is to use a mix of static HTML elements and Razor tags, as shown in Listing 15.

Listing 15. A custom view editor template

@model Role

<select id="Role" name="Role">

@foreach (Role value in Enum.GetValues(typeof(Role))) {

<option value="@value" @(Model == value ? "selected=\"selected\"" : "")>@value </option>

}

</select>

This view creates an HTML select element and populates it with an option element for each value in the Role enumeration. We check to see if the value we have been passed matches the current element in the foreach loop so that we can set the selected attribute correctly.

When we render an editor for this property, our partial view will be used to generate the HTML. Figure 11 shows the effect of our partial view on the Html.EditorForModel helper. It doesn’t matter which of the built-in editor helpers we use – they will all find and use our Role.cshtml template and use it.

Figure 11. The effect of a custom editor template

The name of the template corresponds to the type of the parameter, not the its name - our custom template will be used for any property that is of the Role type. As a simple example, Listing 16 contains a simple model class that uses the Role enumeration.

Listing 16. A simple model class that uses the Role enumeration public class SimpleModel {

public string Name { get; set; } public Role Status { get; set; } }

The effect of the Html.EditorForModel helper applied to a SimpleModel object is shown in Figure 12 – we can see that the Role.cshtml template has been used to render an editor for the Status property.

Figure 12. Rendering an editor for a Role property

Một phần của tài liệu Giáo trình lập trình ASP.NET Apress pro ASP NET MVC3 framework pre release (Trang 507 - 519)

Tải bản đầy đủ (PDF)

(603 trang)