Siêu thị PDFTải ngay đi em, trời tối mất

Thư viện tri thức trực tuyến

Kho tài liệu với 50,000+ tài liệu học thuật

© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

Pro ASP.NET MVC Framework phần 8 docx
PREMIUM
Số trang
56
Kích thước
16.3 MB
Định dạng
PDF
Lượt xem
1755

Pro ASP.NET MVC Framework phần 8 docx

Nội dung xem thử

Mô tả chi tiết

This permits an elegant way of unit testing your model binding. Unit tests can run the

action method, supplying a FormCollection containing test data, with no need to supply a

mock or fake request context. It’s a pleasingly “functional” style of code, meaning that the

method acts only on its parameters and doesn’t touch external context objects.

Dealing with Model-Binding Errors

Sometimes users will supply values that can’t be assigned to the corresponding model proper￾ties, such as invalid dates, or text for int properties. To understand how the MVC Framework

deals with such errors, consider the following design goals:

• User-supplied data should never be discarded outright, even if it is invalid. The

attempted value should be retained so that it can reappear as part of a validation error.

• When there are multiple errors, the system should give feedback about as many errors

as it can. This means that model binding cannot bail out when it hits the first problem.

• Binding errors should not be ignored. The programmer should be guided to recognize

when they’ve happened and provide recovery code.

To comply with the first goal, the framework needs a temporary storage area for invalid

attempted values. Otherwise, since invalid dates can’t be assigned to a .NET DateTime prop￾erty, invalid attempted values would be lost. This is why the framework has a temporary

storage area known as ModelState. ModelState also helps to comply with the second goal:

each time the model binder tries to apply a value to a property, it records the name of the

property, the incoming attempted value (always as a string), and any errors caused by the

assignment. Finally, to comply with the third goal, if ModelState has recorded any errors, then

UpdateModel() finishes by throwing an InvalidOperationException saying “The model of type

typename was not successfully updated.”

So, if binding errors are a possibility, you should catch and deal with the exception—for

example,

public ActionResult RegisterMember()

{

var person = new Person();

try

{

UpdateModel(person);

// ... now do something with person

}

catch (InvalidOperationException ex)

{

// Todo: Provide some UI feedback based on ModelState

}

}

This is a fairly sensible use of exceptions. In .NET, exceptions are the standard way to

signal the inability to complete an operation (and are not reserved for critical, infrequent, or

CHAPTER 11 ■ DATA ENTRY 375

10078ch11.qxd 3/26/09 12:13 PM Page 375

“exceptional” events, whatever that might mean).2 However, if you prefer not to deal with an

exception, you can use TryUpdateModel() instead. It doesn’t throw an exception, but returns a

bool status code—for example,

public ActionResult RegisterMember()

{

var person = new Person();

if(TryUpdateModel(person))

{

// ... now do something with person

}

else

{

// Todo: Provide some UI feedback based on ModelState

}

}

You’ll learn how to provide suitable UI feedback in the “Validation” section later in this

chapter.

■Note When a certain model property can’t be bound because the incoming data is invalid, that doesn’t

stop DefaultModelBinder from trying to bind the other properties. It will still try to bind the rest, which

means that you’ll get back a partially updated model object.

When you use model binding implicitly—i.e., receiving model objects as method parame￾ters rather than using UpdateModel() or TryUpdateModel()—then it will go through the same

process but it won’t signal problems by throwing an InvalidOperationException. You can

check ModelState.IsValid to determine whether there were any binding problems, as I’ll

explain in more detail shortly.

Model-Binding to Arrays, Collections, and Dictionaries

One of the best things about model binding is how elegantly it lets you receive multiple data items

at once. For example, consider a view that renders multiple text box helpers with the same name:

Enter three of your favorite movies: <br />

<%= Html.TextBox("movies") %> <br />

<%= Html.TextBox("movies") %> <br />

<%= Html.TextBox("movies") %>

Now, if this markup is in a form that posts to the following action method:

public ActionResult DoSomething(List<string> movies)

{

// ...

}

376 CHAPTER 11 ■ DATA ENTRY

2. When you run in Release mode and don’t have a debugger attached, .NET exceptions rarely cause any

measurable performance degradation, unless you throw tens of thousands of exceptions per second.

10078ch11.qxd 3/26/09 12:13 PM Page 376

then the movies parameter will contain one entry for each corresponding form field.

Instead of List<string>, you can also choose to receive the data as a string[] or even an

IList<string>—the model binder is smart enough to work it out. If all of the text boxes were

called myperson.Movies, then the data would automatically be used to populate a Movies col￾lection property on an action method parameter called myperson.

Model-Binding Collections of Custom Types

So far, so good. But what about when you want to bind an array or collection of some custom

type that has multiple properties? For this, you’ll need some way of putting clusters of related

input controls into groups—one group for each collection entry. DefaultModelBinder expects

you to follow a certain naming convention, which is best understood through an example.

Consider the following view template:

<% using(Html.BeginForm("RegisterPersons", "Home")) { %>

<h2>First person</h2>

<div>Name: <%= Html.TextBox("people[0].Name") %></div>

<div>Email address: <%= Html.TextBox("people[0].Email")%></div>

<div>Date of birth: <%= Html.TextBox("people[0].DateOfBirth")%></div>

<h2>Second person</h2>

<div>Name: <%= Html.TextBox("people[1].Name")%></div>

<div>Email address: <%= Html.TextBox("people[1].Email")%></div>

<div>Date of birth: <%= Html.TextBox("people[1].DateOfBirth")%></div>

...

<input type="submit" />

<% } %>

Check out the input control names. The first group of input controls all have a [0] index

in their name; the second all have [1]. To receive this data, simply bind to a collection or array

of Person objects, using the parameter name people—for example,

public ActionResult RegisterPersons(IList<Person> people)

{

// ...

}

Because you’re binding to a collection type, DefaultModelBinder will go looking for groups

of incoming values prefixed by people[0], people[1], people[2], and so on, stopping when it

reaches some index that doesn’t correspond to any incoming value. In this example, people

will be populated with two Person instances bound to the incoming data.

It works just as easily with explicit model binding. You just need to specify the binding

prefix people, as shown in the following code:

public ActionResult RegisterPersons()

{

var mypeople = new List<Person>();

UpdateModel(mypeople, "people");

// ...

}

CHAPTER 11 ■ DATA ENTRY 377

10078ch11.qxd 3/26/09 12:13 PM Page 377

■Note In the preceding view template example, I wrote out both groups of input controls by hand for clarity.

In a real application, it’s more likely that you’ll generate a series of input control groups using a <% for(...)

{ %> loop. You could encapsulate each group into a partial view, and then call Html.RenderPartial() on

each iteration of your loop.

Model-Binding to a Dictionary

If for some reason you’d like your action method to receive a dictionary rather than an array or

a list, then you have to follow a modified naming convention that’s more explicit about keys

and values—for example,

<% using(Html.BeginForm("RegisterPersons", "Home")) { %>

<h2>First person</h2>

<input type="hidden" name="people[0].key" value="firstKey" />

<div>Name: <%= Html.TextBox("people[0].value.Name")%></div>

<div>Email address: <%= Html.TextBox("people[0].value.Email")%></div>

<div>Date of birth: <%= Html.TextBox("people[0].value.DateOfBirth")%></div>

<h2>Second person</h2>

<input type="hidden" name="people[1].key" value="secondKey" />

<div>Name: <%= Html.TextBox("people[1].value.Name")%></div>

<div>Email address: <%= Html.TextBox("people[1].value.Email")%></div>

<div>Date of birth: <%= Html.TextBox("people[1].value.DateOfBirth")%></div>

...

<input type="submit" />

<% } %>

When bound to a Dictionary<string, Person> or IDictionary<string, Person>, this

form data will yield two entries, under the keys firstKey and secondKey, respectively. You

could receive the data as follows:

public ActionResult RegisterPersons(IDictionary<string, Person> people)

{

// ...

}

Creating a Custom Model Binder

You’ve learned about the rules and conventions that DefaultModelBinder uses to populate

arbitrary .NET types according to incoming data. Sometimes, though, you might want to

bypass all that and set up a totally different way of using incoming data to populate a particu￾lar object type. To do this, implement the IModelBinder interface.

For example, if you want to receive an XDocument object populated using XML data from

a hidden form field, you need a very different binding strategy. It wouldn’t make sense to let

378 CHAPTER 11 ■ DATA ENTRY

10078ch11.qxd 3/26/09 12:13 PM Page 378

DefaultModelBinder create a blank XDocument, and then try to bind each of its properties, such

as FirstNode, LastNode, Parent, and so on. Instead, you’d want to call XDocument’s Parse()

method to interpret an incoming XML string. You could implement that behavior using the

following class, which can be put anywhere in your ASP.NET MVC project.

public class XDocumentBinder : IModelBinder

{

public object BindModel(ControllerContext controllerContext,

ModelBindingContext bindingContext)

{

// Get the raw attempted value from the value provider

string key = bindingContext.ModelName;

ValueProviderResult val = bindingContext.ValueProvider[key];

if ((val != null) && !string.IsNullOrEmpty(val.AttemptedValue)) {

// Follow convention by stashing attempted value in ModelState

bindingContext.ModelState.SetModelValue(key, val);

// Try to parse incoming data

string incomingString = ((string[])val.RawValue)[0];

XDocument parsedXml;

try {

parsedXml = XDocument.Parse(incomingString);

}

catch (XmlException) {

bindingContext.ModelState.AddModelError(key, "Not valid XML");

return null;

}

// Update any existing model, or just return the parsed XML

var existingModel = (XDocument)bindingContext.Model;

if (existingModel != null) {

if (existingModel.Root != null)

existingModel.Root.ReplaceWith(parsedXml.Root);

else

existingModel.Add(parsedXml.Root);

return existingModel;

}

else

return parsedXml;

}

// No value was found in the request

return null;

}

}

CHAPTER 11 ■ DATA ENTRY 379

10078ch11.qxd 3/26/09 12:13 PM Page 379

Tải ngay đi em, còn do dự, trời tối mất!