Chapter 9

Model Binding

WHAT’S IN THIS CHAPTER?

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

Please note that all the code examples in this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com on the Download Code tab.

Model binding was introduced in ASP.NET MVC. Model binding made it easier to bring the data submitted by the client into a format that’s suitable for use and validation on the server. Model binding was baked into the ASP.NET MVC 1.0 framework. When ASP.NET MVC 2.0 was released, a new system called “extensible model binding” was introduced. This gave developers more power and flexibility over how the client data is data-bound on the server.

Prior to ASP.NET 4.5 this system was available only in ASP.NET MVC. However, in ASP.NET 4.5 model binding can also be used for building ASP.NET Web Forms applications. The model binding system in ASP.NET4.5 Web Forms is based on the “extensible model binding” system.

In the last chapter you learned how data binding happens with data controls. In this chapter, you look at how you can do data binding with model binding and discover the benefits of using model binding.

MODEL BINDING

Model binding serves two purposes. It provides a way to fetch values from the client and bind them to a model. It also provides a validation mechanism whereby, once the model is bound, you can run validation rules to determine if the model is valid before saving the model. Model binding in ASP.NET Web Forms brings the power of model binding architecture to the controls and provides a system that allows developers to easily integrate patterns, such as PRG (Post-Redirect-Get), Repository, and so on, more easily. It also makes the application code cleaner and makes the application more unit-testable.

Model binding hooks into the existing data-bound controls. You can use model binding to extract the values from the control so that the client values can be used for inspection at the server, and the controls can bind to the values returned from the model binding system. Model binding works with existing data-bound controls so you can use the concepts you learned from the last chapter.

For example, imagine that you have a web page where a user can enter a person’s detail using a DetailsView control. When a user enters the details and submits the form to the server, the model binding system extracts the values from the DetailsView control so you can perform some validation operations on the server. If the validation is successful, then you can save the person’s detail in the database. In this case, the person is the model that the model binding system interacts with. A model can be either an ADO.NET Entity Framework model or an Entity Framework Code First model.


NOTE You can learn more about these models in Chapter 11.

Let’s look at some of the common data operations using model binding.

Selecting Data

Begin by creating a new Web Forms page and use a GridView control to select and filter customer data. Listing 9-1 shows how you can configure the GridView control to retrieve data using model binding. The control uses two new properties — ItemType and SelectMethod.

LISTING 9-1: GridView using model binding

<asp:GridView ID="GridView1" runat="server" ItemType="Customer" AllowPaging="true" 
AllowSorting="true" PageSize="2" SelectMethod="SelectCustomers"></asp:GridView>

LISTING 9-2: Example of the model binding Select method

VB

Dim _context As New CustomerContext()
    Public Function SelectCustomers() As IEnumerable(Of Customer)
        Return _context.Customer.AsEnumerable()
End Function

C#

CustomerContext _context = new CustomerContext();
    public IEnumerable<Customer> SelectCustomers()
    {       
        return _context.Customer.AsEnumerable();
    }

In Listing 9-2, the Select method is returning a list of Customers. As shown in this listing we first instantiate CustomerContext, which is used when you are using Entity Framework Code First. When you instantiate a CustomerContext, you can access all the Customers and can perform any LINQ queries to filter the result as shown later in this section.

Paging

The GridView control has built-in paging and sorting features. Using model binding, you can take advantage of these features by returning an IQueryable<T> from the model binding Select method. Listing 9-3 shows you how to enable paging and sorting. To get paging and sorting, you need to enable paging and sorting on the GridView control as shown in Listing 9-1.

LISTING 9-3: Paging and sorting using model binding

VB

Private _context As New VB.CustomerContext()
    Public Function SelectCustomers() As IQueryable(Of VB.Customer)
        Return _context.Customer.AsQueryable()
End Function

C#

CustomerContext _context = new CustomerContext();     
    public IQueryable<Customer> SelectCustomers()    
    {                  
        return _context.Customer.AsQueryable();        
    }

In this listing you will notice that the Select method is returning an IQueryable instead of IEnumerable. GridView can automatically wire up sorting and paging functionality when IQueryable<T> is returned from the model binding Select method and sorting/paging is enabled on the GridView control.

Filtering

Often, you’ll need to filter the results that you want to show the users. You don’t want to return all the records of the table. In the last chapter, you learned how to filter data using the Select parameters in the SqlDataSource control. Filtering can be achieved in model binding by using ValueProviders and ValueProvider attributes. Listing 9-4 shows how you can filter data using values from QueryString. In this example, adding a query string such as ?ID=5 returns only records where the ID is 5.

LISTING 9-4: Filtering using QueryString

VB

Public Function SelectCustomers(<System.Web.ModelBinding.QueryString> id As 
  System.Nullable(Of Integer)) As IEnumerable(Of VB.Customer)
        If id.HasValue Then
            Return _context.Customer.Where(Function(c) c.ID = id).AsEnumerable()
        Else
            Return _context.Customer.AsEnumerable()
        End If
End Function

C#

public IEnumerable<Customer> SelectCustomers([System.Web.ModelBinding.QueryString] int? id)
    {
        if(id.HasValue)
            return _context.Customer.Where(c => c.ID == id).AsEnumerable();
        else
            return _context.Customer.AsEnumerable();    
    }

When you pass in ID=5 in the QueryString, the model binding system takes this value and provides it as a parameter to the Select method. Since we are using Entity Framework Code First, we can write a LINQ query to filter the list of customers whose ID is 5 and return the result to the GridView control.

Using Value Providers

Listing 9-4 shows how to use the QueryString value provider attribute to filter the results. This attribute tells the model binding system to bind a value from the query string to the id parameter at run time. The model binding system performs any type conversion as needed.

The value provider attributes eventually get the value from ValueProviders and tell the model binding system which ValueProvider to use. Table 9-1 shows the value provider attributes that you can use from the framework.

TABLE 9-1

VALUE PROVIDER ATTRIBUTES DESCRIPTION
Form The value is retrieved from the Form collection.
Control The value is retrieved from the specified control.
QueryString The value is retrieved from the QueryString collection.
Cookie The value is retrieved from the Cookie collection.
Profile The value is retrieved from the Profile collection.
RouteData The value is retrieved from the RouteData collection.
Session The value is retrieved from the Session collection.

Filtering Using Control

This section looks at an advanced example where you want to filter a result based on a value coming from another server control. You might often find the need to filter values based on a drop-down list, for example. Listing 9-5 shows how you can use the Control value provider attribute to retrieve the value from the drop-down list control. It then uses that value to filter the results and display them in a GridView control.

LISTING 9-5: Filtering using Control

<asp:DropDownList ID="DropDown1" runat="server" ItemType="Customer"
                SelectMethod="SelectCustomersForDropDownList" AppendDataBoundItems="true" 
                  AutoPostBack="true"
                DataTextField="ID" DataValueField="ID">
</asp:DropDownList>
<asp:GridView ID="GridView1" runat="server" ItemType="Customer"
                SelectMethod="SelectCustomers">
</asp:GridView>

VB

Public Function SelectCustomersForDropDownList() As IEnumerable(Of VB.Customer)
        Return _context.Customer.AsEnumerable()
    End Function
    Public Function SelectCustomers(<System.Web.ModelBinding.Control> DropDown1 As 
      System.Nullable(Of Integer)) As IEnumerable(Of VB.Customer)
        If DropDown1.HasValue Then
            Return _context.Customer.Where(Function(c) c.ID = DropDown1).AsEnumerable()
        Else
            Return _context.Customer.AsEnumerable()
        End If
End Function

C#

public IEnumerable<Customer> SelectCustomers([System.Web.ModelBinding.Control] int? DropDown1)
    {
        if (DropDown1.HasValue)
            return _context.Customer.Where(c => c.ID == DropDown1).AsEnumerable();
        else
            return _context.Customer.AsEnumerable();
    }
public IEnumerable<Customer> SelectCustomersForDropDownList()
    {
        return _context.Customer.AsEnumerable();
    }

In this listing, when you select a value from the DropDownList control, the GridView gets bounded to the selected value of the DropDownList control. The model binding system takes in the selected value (ID in this case) of the DropDownList control and passes it as an argument to the SelectCustomers method using the Control value provider.

Inserting Data

Recall that, in ASP.NET 4.5, data-bound controls were updated to work with model binding. Now you will take a look at how you can use model binding to insert a record. You can use the DetailsView control to insert a record. On the control, you have to set a new property called InsertMethod, which can be called on the page. Listing 9-6 shows the insert method.

LISTING 9-6: Using the insert method with model binding

<asp:DetailsView runat="server" ItemType="Customer" SelectMethod="SelectCustomers"
        InsertMethod="InsertCustomer" AutoGenerateInsertButton="true" AllowPaging="true">
</asp:DetailsView>

VB

Public Sub InsertCustomer(customer As VB.Customer)
        _context = New VB.CustomerContext()
        If ModelState.IsValid Then
            _context.Customer.Add(customer)
        End If
End Sub

C#

public void InsertCustomer(Customer customer)
    {
        _context = new CustomerContext()
        if(ModelState.IsValid)
        {
            _context.Customer.Add(customer);
        }
    }

If you take a closer look at InsertMethod, you will notice that the input to this method is a type called Customer. This is the type of the model that the DetailsView control was bound to. (This was specified via the ItemType property of the control.)

When you insert a value, the model binding system takes the values from the DetailsView control and populates the customer type model so the customer model can be used at the server for any kind of validation. Once the InsertMethod is called, you can perform a check to see if there were any validation errors in the model binding system. If everything was successful, then you can add this new Customer to the Customer collection and save it to the database.

Updating Data

The binding approach that we saw while inserting a record works with simple cases. However, the data control will often not be able to provide values for each member of the model, either because those members are relationship objects or they are not rendered in the control. In these cases, it’s best to take in the primary key, load the model from the data storage, and tell the model binding system to bind the values from the data control to the model. Listing 9-7 shows how the update method would work for updating a record.

LISTING 9-7: Model binding update with PrimaryKey as the parameter

<asp:DetailsView runat="server" ItemType="Customer" SelectMethod="SelectCustomers"
        UpdateMethod="UpdateCustomer" AutoGenerateEditButton="true" DataKeyNames="ID" 
        AllowPaging="true">
</asp:DetailsView>

VB

    Private _context As New VB.CustomerContext()
 
    Public Function SelectCustomers() As VB.Customer
        Return _context.Customer.First()
    End Function
 
Public Sub UpdateCustomer(id As Integer)
        _context = New VB.CustomerContext()
        Dim customer = _context.Customer.Where(Function(c) c.ID = id).First()
        TryUpdateModel(customer)
        If ModelState.IsValid Then
                
        End If
End Sub

C#

CustomerContext _context = new CustomerContext();    
 
    public Customer SelectCustomers()
    {
        return _context.Customer.First();
    }
 
    public void UpdateCustomer(int id)
    {
        _context = new CustomerContext();
        var customer = _context.Customer.Where(c => c.ID == id).First();
        TryUpdateModel(customer);
        if(ModelState.IsValid)
        {
            
        }
    }

Listing 9-7 showed how you can update a record. All data-bound controls have a property called DataKeyNames that uniquely identifies a record. This property has the names of the primary key fields. When the DetailsView control does an insert operation, the model binding system populates the parameter in the update method with the value of the DataKeyNames property. This parameter (id), which now holds the value of the primary key (ID), can be used to retrieve the particular record which was being updated by the user. When you call TryUpdateModel, the model binding system updates the values of the customer model with the ones specified by the user when he/she updated the record.


NOTE Just as you perform an update operation by setting the UpdateMethod property on the control, you can set the DeleteMethod to perform a delete operation, on the record.

Validating Records in a Model Binding System

In the majority of cases, you would want to validate a record for some custom business logic before saving the record in the database. Model binding makes this scenario very easy to implement and results in a much cleaner implementation. If you are using business validation rules with ObjectDataSource, there is no easy way to propagate exceptions from the business layer back to the page. You end up throwing an exception in the business layer, which you catch in the control, and then you display a custom error message. This makes the code really messy.

The benefit of the model binding system is that it cleanly separates binding from validation. This means you can use custom validation very easily. The validation errors thrown from the model binding system are displayed through a validation summary, so this makes it easy to customize the UI and helps maintain clean code.

One benefit of enabling model binding in ASP.NET Web Forms is that you can plug in different validation mechanisms. For example, you can use data annotation attributes, which enable you to add attributes to signify some metadata about your model. The model binding system can use that metadata during validation. Listing 9-8 shows you how to tell the model binding system that the FirstName field is required.

LISTING 9-8: Adding data annotations

VB

<Required> _
        Public Property FirstName() As String
            Get
                Return m_FirstName
            End Get
            Set(value As String)
                m_FirstName = value
            End Set
End Property

C#

[Required()]
public string FirstName { get; set; }

In this case, if you do not enter a value for the FirstName field, the call to TryUpdateModel() will return false. If you have a ValidationSummary control on the page, then you will get the error message displayed in the validation summary. Listing 9-9 shows how you can configure the ValidationSummary control to show errors from the model binding system. The other way to check for any validation errors is to check the ModelState property of the page. This property is populated by the model binding system in case any errors happened during model binding.

LISTING 9-9: Configuring the ValidationSummary control to show errors from the model binding system

<asp:ValidationSummary runat="server" ShowModelStateErrors="true" />

Separating Business Logic from the page

So far you have been calling the model binding methods on the page. Although this is the more common way to do it, it does result in a lot of code in the page code-behind. The page has to deal with UI logic and business logic. In the last chapter with ObjectDataSource, the business logic was in a separate class and the page code dealt only with the UI. Listing 9-10 shows how you can do this in model binding. You have to tell the model binding system where to load the model binding methods. This is done by overriding the OnCallingDataMethods method of the data-bound control. The model binding system will then instantiate the repository class and will look for the methods in the class. This approach leads to a much cleaner application logic that’s more easily testable.

LISTING 9-10: Separating business logic from the page

VB

Protected Sub GridView1_CallingDataMethods(sender As Object, e As CallingDataMethodsEventArgs)
        e.DataMethodsObject = New VB.CustomerRepository()
End Sub

C#

protected void GridView1_CallingDataMethods(object sender, CallingDataMethodsEventArgs e)
{
        e.DataMethodsObject = new CustomerRepository();
}

USING STRONGLY TYPED CONTROLS

ASP.NET 2.0 Web Forms introduced the concept of templated controls. Templates allow you to customize the markup emitted from server controls and are typically used with data-binding expressions. This section looks at the improvements that have happened in ASP.NET 4.5 to make data binding and HTML encoding easier.

In ASP.NET 2.0 one-way data binding was accomplished with the Eval() and Bind() helpers. These helpers do a late binding to the data. Listing 9-11 shows how the Eval() helper is used.

LISTING 9-11: Data-binding helpers

<asp:FormView ID="editCustomer" runat="server">
    <ItemTemplate>
        <div>
            First Name:<%# Eval("FirstName") %>            
        </div>
    </ItemTemplate>
</asp:FormView>

One drawback of this approach is that since these expressions are late bound, you have to pass in a string to represent the property name. This means you do not get IntelliSense for member names, support for code navigation (like Go To Definition), or compile-time checking support.

When a control is strongly typed, it means that you can declare which type of data the control is going to be bound to, by way of a new property called ItemType. When you set this property, the control will have two new properties for the bind expressions — Item and BindItem.

Item is equivalent to Eval(), whereas BindItem is equivalent to Bind(). Listing 9-12 shows an example of strongly typed controls.

LISTING 9-12: Strongly typed controls

<asp:FormView ID="editCustomer" runat="server" ItemType="Customer" 
  SelectMethod="SelectCustomer" >
    <ItemTemplate>
        <div>
            First Name:<%# Item.FirstName %>            
        </div>
    </ItemTemplate>
</asp:FormView>

If you are in Visual Studio and you type BindItem, as shown in Listing 9-12, you will get IntelliSense for all the properties in the model Customer, as shown in Figure 9-1. If you make a mistake while typing, you will get an error in Visual Studio.

FIGURE 9-1

image

EXTENDING MODEL BINDING

While ASP.NET Web Forms has always been a great web development framework, it has been a bit painful to customize and extend the framework. However, the Model Binding system is built in such a way that you can easily customize and extend the model binding system to match your development scenarios. In this section you will learn about customizing value providers and model binders. In the end you will look at extending the ModelDataSource to control the workings of the model binding system.

Custom Value Providers

So far you have looked at the ValueProviders and ValueProvider attributes. These provide basic support for specifying where the model binding system should fetch the value from. However, there are cases when you really don’t know where the value might come from, or you want to write your application so you have a fallback mechanism — you want to allow the value to come from Form collection or QueryString collection. In this case, you can write your own value provider attribute and value provider to take care of this scenario. Listing 9-13 shows what such a custom value provider and value provider attribute would look like.

LISTING 9-13: Implementing a custom value provider and attribute

VB

Public Class AggregateValueProvider
        Implements IValueProvider
        Implements IUnvalidatedValueProvider
        Private ReadOnly _valueProviders As New List(Of IUnvalidatedValueProvider)()
 
        Public Sub New(modelBindingExecutionContext As ModelBindingExecutionContext)
            _valueProviders.Add(New FormValueProvider(modelBindingExecutionContext))
            _valueProviders.Add(New QueryStringValueProvider(modelBindingExecutionContext))
        End Sub
 
        Public Function ContainsPrefix(prefix As String) As Boolean Implements 
          IValueProvider.ContainsPrefix
            Return _valueProviders.Any(Function(vp) vp.ContainsPrefix(prefix))
        End Function
 
        Public Function GetValue(key As String) As ValueProviderResult Implements 
          IValueProvider.GetValue
            Return GetValue(key, False)
        End Function
 
        Public Function GetValue(key As String, skipValidation As Boolean) As 
          ValueProviderResult Implements IUnvalidatedValueProvider.GetValue
            Return _valueProviders.[Select](Function(vp) vp.GetValue(key, 
              skipValidation)).LastOrDefault()
        End Function
End Class
 
Public Class AggregateValueAttribute
        Inherits ValueProviderSourceAttribute
        Public Overrides Function GetValueProvider(modelBindingExecutionContext As 
          ModelBindingExecutionContext) As IValueProvider
            Return New AggregateValueProvider(modelBindingExecutionContext)
        End Function
End Class

C#

public class AggregateValueProvider : IValueProvider, IUnvalidatedValueProvider
{
    private readonly List<IUnvalidatedValueProvider> _valueProviders = new 
      List<IUnvalidatedValueProvider>();
 
    public AggregateValueProvider(ModelBindingExecutionContext modelBindingExecutionContext)
    {
        _valueProviders.Add(new FormValueProvider(modelBindingExecutionContext))
        _valueProviders.Add(new QueryStringValueProvider(modelBindingExecutionContext));
    }
 
    public bool ContainsPrefix(string prefix)
    {
        return _valueProviders.Any(vp => vp.ContainsPrefix(prefix));
    }
 
    public ValueProviderResult GetValue(string key)
    {
        return GetValue(key, false);
    }
 
    public ValueProviderResult GetValue(string key, bool skipValidation)
    {
        return _valueProviders.Select(vp => vp.GetValue(key, skipValidation))
            .LastOrDefault();
    }
}
 
public class AggregateValueAttribute : ValueProviderSourceAttribute
{
    public override IValueProvider GetValueProvider(ModelBindingExecutionContext 
      modelBindingExecutionContext)
    {
        return new AggregateValueProvider(modelBindingExecutionContext);
    }
}

This listing shows a value provider and a value provider attribute that will use this provider. The value provider adds a Form and QueryString value provider as its sources. This means that when the model binding system calls into this custom value provider, the custom value provider will first check the Form collection to retrieve the value, and if there is no value found, then the QueryString collection will be checked. Listing 9-14 shows how you can use this custom value provider in your application.

LISTING 9-14: Using a custom value provider

VB

Private _context As New VB.CustomerContext()
    Public Function SelectCustomers(<VB.AggregateValue> id As System.Nullable(Of Integer)) 
      As IEnumerable(Of VB.Customer)
        If id.HasValue Then
            Return _context.Customer.Where(Function(c) c.ID = id).AsEnumerable()
        Else
            Return _context.Customer.AsEnumerable()
        End If
    End Function

C#

CustomerContext _context = new CustomerContext();    
    public IEnumerable<Customer> SelectCustomers([AggregateValue] int? id)
    {
        if(id.HasValue)
        return _context.Customer.Where(c => c.ID == id).AsEnumerable();
        else
            return _context.Customer.AsEnumerable();    
    }

As shown in this listing, you can use your custom value provider by specifying a custom value provider attribute that you defined for your own value provider. The AggregateValue attribute works the same way as other value provider attributes such as QueryString. The model binding system calls into the AggregateValue attribute to bind the value of the parameter id. The AggregateValue attribute calls into AggregateValueProvider to get the value of the parameter id.

Custom Model Binders

The implementation of model binding in ASP.NET 4.5 provides support for different kinds of data types. Although the basic implementation takes care of binding to the commonly used data types, there are cases when the model binding system cannot bind to a specific data type. In this case you can write your own model binder and plug it into the model binding system. Whenever the model binding tries to bind a model of this type, your custom model binder will then be called to fetch and save the values and populate the model.

Consider, for example, if you needed to change the behavior of how the out-of-the-box model binders bind the data to a model (such as for DateTime types). You want to store the date and time separately or in a different locale format. You can achieve this by implementing your own custom model binder. Because the implementation of the Web Forms model binding system is based on extensible model binding, making this change is really easy. Listing 9-15 shows a custom implementation of a model binder.

To implement a custom model binder, you have to implement a provider and a binder.

LISTING 9-15: Implementing a custom model binder

VB

Public Class MyDateTimeBinder
        Implements IModelBinder
        Public Function BindModel(modelBindingExecutionContext As ModelBindingExecutionContext,
          bindingContext As ModelBindingContext) As Boolean Implements IModelBinder.BindModel
            Dim valueProviderResult = bindingContext.ValueProvider.GetValue
              (bindingContext.ModelName)
            Dim inputdate = If(valueProviderResult IsNot Nothing, 
              valueProviderResult.AttemptedValue, Nothing)
            Dim newDate As New DateTime()
            Dim success As Boolean = DateTime.TryParse(inputdate, CultureInfo.GetCultureInfo
              ("en-GB"), DateTimeStyles.None, newDate)
            bindingContext.Model = newDate
            Return bindingContext.Model IsNot Nothing
        End Function
End Class
   
Public Class MyDateTimeProvider
        Inherits System.Web.ModelBinding.ModelBinderProvider
        Public Overrides Function GetBinder(modelBindingExecutionContext As 
          ModelBindingExecutionContext, bindingContext As ModelBindingContext) As IModelBinder
            If bindingContext.ModelType = GetType(DateTime) Then
                Return New MyDateTimeBinder()
            End If
            Return Nothing
       End Function
End Class

C#

public class MyDateTimeBinder : IModelBinder
{
    public bool BindModel(ModelBindingExecutionContext modelBindingExecutionContext, 
      ModelBindingContext bindingContext)
    {
        var valueProviderResult = bindingContext.ValueProvider.GetValue
          (bindingContext.ModelName);
        var inputdate = valueProviderResult != null ? valueProviderResult.AttemptedValue 
          : null;        
        DateTime dt = new DateTime();
        bool success = DateTime.TryParse(inputdate, CultureInfo.GetCultureInfo("en-GB"), 
          DateTimeStyles.None, out dt);
        bindingContext.Model = dt;  
        return bindingContext.Model != null;
    }
}
public class MyDateTimeProvider : System.Web.ModelBinding.ModelBinderProvider
{
    public override IModelBinder GetBinder(ModelBindingExecutionContext 
      modelBindingExecutionContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType == typeof(DateTime))
            return new MyDateTimeBinder();
        return null;
    }
}

When the model binding system tries to bind data to a model, it looks through a list of registered providers to see which provider can find a binder to bind a value for a specific type. The registered providers are called in order. You can register your provider as the first one if you are not implementing a generic solution and are not concerned about the fallback behavior in case it can’t find a binder for a particular type. Listing 9-16 shows how you can register the model binder with the application.

LISTING 9-16: Registering a custom model binder provider

VB

System.Web.ModelBinding.ModelBinderProviders.Providers.Insert(0, NEW MyDateTimeProvider())

C#

System.Web.ModelBinding.ModelBinderProviders.Providers.Insert(0, new MyDateTimeProvider());

Custom ModelDataSource

At the heart of the implementation, the model binding system in Web Forms is based on data controls. The implementation uses the extensible model binding and the controls architecture. Model binding is implemented as a data source called ModelDataSource, which implements IDataSource. This is the same pattern followed by other ObjectDataSource objects. For example, there is also a ModelDataSourceView object, which has the logic of executing select, insert, update, and delete calls from the data controls.

This means that this implementation is fully extensible. If you want to override the behavior of how the select calls happen, then you can write your own ModelDataSource and ModelDataSourceView objects and plug them into the model binding system. This is useful if you want to extend the model binding system to make scenarios such as master-detail binding easier to implement.

Listing 9-17 shows how you can implement custom ModelDataSource and ModelDataSourceView objects. This custom implementation returns only the first three rows in the table, but it can be easily expanded for more complex scenarios.

LISTING 9-17: Implementing custom ModelDataSouce

VB

Public Class MyModelView
        Inherits ModelDataSourceView
        Private ReadOnly _owner As MyModelDataSource
 
        Public Sub New(owner As MyModelDataSource)
            MyBase.New(owner)
            _owner = owner
        End Sub
        Protected Overrides Function ExecuteSelect(arguments As DataSourceSelectArguments) 
          As IEnumerable
            Dim _context As New CustomerContext()
            Return _context.Customer.Take(3).AsEnumerable()
        End Function
End Class
 
Public Class MyModelDataSource
        Inherits ModelDataSource
        Private _view As MyModelView
 
        Public Sub New(dataControl As Control)
 
            MyBase.New(dataControl)
        End Sub
        Public Overrides ReadOnly Property View() As ModelDataSourceView
            Get
                If _view Is Nothing Then
                    _view = New MyModelView(Me)
                End If
                Return _view
            End Get
        End Property
End Class

C#

public class MyModelView : ModelDataSourceView
{
        private readonly MyModelDataSource _owner;
 
        public MyModelView(MyModelDataSource owner)
            : base(owner)
        {
            _owner = owner;
        }
        protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
        {
            CustomerContext _context = new CustomerContext();
            return _context.Customer.Take(3).AsEnumerable();
            //return _context.Customer.Distinct<Customer>().AsEnumerable();
        }
}
 
public class MyModelDataSource : ModelDataSource
{
        private MyModelView _view;
 
        public MyModelDataSource(Control dataControl)
            : base(dataControl)
        {
 
        }
        public override ModelDataSourceView View
        {
            get
            {
                if (_view == null)
                {
                    _view = new MyModelView(this);
                }
                return _view;
            }
}

Listing 9-18 shows how you can use this custom ModelDataSource in your application. On the data control, you can override the ModelDataSource that the control should use by overriding an event called OnCreatingModelDataSource.

LISTING 9-18: Calling the custom ModelDataSource

VB

Protected Sub GridView1_CreatingModelDataSource(sender As Object, e As 
  CreatingModelDataSourceEventArgs)
        e.ModelDataSource = New VB.MyModelDataSource(DirectCast(sender, GridView))
    End Sub

C#

protected void GridView1_CreatingModelDataSource(object sender, 
  CreatingModelDataSourceEventArgs e)
    {
        e.ModelDataSource = new CS.MyModelDataSource((GridView)sender);
    }

SUMMARY

The model binding system is the next step in the data-binding story for Web Forms. It uses the power and flexibility of the extensible model binding system and combines it with the power of data controls, which makes application development easy. This also leads to a cleaner implementation, which supports paradigms such as unit testing and IOC containers.

Model binding makes data access more code focused and allows you to reuse the data annotation attributes across ASP.NET. You can use value providers to ease filtering scenarios. Lastly, you saw how you can customize and extend the model binding system for custom binding and selection, which makes this framework very easy to adapt.