Wednesday, April 29, 2009

Keep scroll position on ASP.NET page when using Master Pages

In ASP.NET you can force the browser to scroll back to the position it was prior to a postback. To enable this just add MaintainScrollPositionOnPostBack=”True” to your page declaration. This would look something like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" 
Inherits="MyApp.WebForm4" MaintainScrollPositionOnPostBack=”True”%>
This is great and all, but this doesn’t work with Master Pages. This is because Master Pages are really more like User Controls than a real page. Master Pages don’t have a Page declaration like above. Instead they have something similar, but instead of the word Page, it uses the word Master. It doesn’t support the MaintainScrollPositionOnPostBack property. What to do now? Option 1: Global Change If you want the change to affect all the pages in your application, you can put it in the web.config in the Pages element like the following.
<pages maintainScrollPositionOnPostBack="True">
Option 2: Page Level Change On any page (even ones that use Master Pages), you can add this line to your Page_Load event. Page.MaintainScrollPositionOnPostBack = true; Option 3: Hybrid

You can combine the two options above. The setting in the web.config becomes the default. Then on any particular page you can add the one line to your Page_Load event to change the default behavior.

 

Tuesday, April 28, 2009

AJAX ReorderList breaks when using EntityDataSource entity that has a Navigation Property

After some digging, I figured out the reason why the ReorderList from the AJAX Control Toolkit stopped working for me. When I use the ReorderList with the SqlDataSource it works fine. When I use it with the EntityDataSource it works also. Well sort of. It works fine if the object that you are binding to do not have a Navigation Property in ADO.NET Entity Data Model.

If the object does, you will not receive an error when you reorder items in the ReorderList, but it will not work either. The reason is that the control is not completely robust / completed. If figured this out by changing my reference to the the AjaxControlToolkit.dll that is in the source code version of the Ajax Control Toolkit Sample Application. This allowed me to step through the code. There I saw code that “swallowed” the exception and thus never reported it to the calling method. This is why there is no error, but it is not working either.

Here is the InnerException that I found when I stepped through the ReorderList code:

"Error while setting property 'ICAContract': 'This property descriptor does not support the SetValue method.'." 

   at System.Web.UI.WebControls.EntityDataSourceUtil.SetAllPropertiesWithVerification(EntityDataSourceWrapper entityWrapper, Dictionary`2 changedProperties, Boolean overwrite) 
   at System.Web.UI.WebControls.EntityDataSourceView.InstantiateEntityFromViewState(EntityDataSourceWrapper entityWrapper, IDictionary mergedKeysAndOldValues) 
   at System.Web.UI.WebControls.EntityDataSourceView.ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) 
   at System.Web.UI.DataSourceView.Update(IDictionary keys, IDictionary values, IDictionary oldValues, DataSourceViewOperationCallback callback)

Here is the code from ReorderList.cs file.

try{…}
catch (Exception ex)
{
    System.Diagnostics.Debug.Fail(ex.ToString());
    //TODO WHY ARE SWALLOWING THIS EXCEPTION!!!
}

I have to conclude that ReorderList needs to handle error properly, and that potentially the ADO.NET Entity Framework may need some more work. I can’t confirm that, but I do know that I have had to implement several work arounds as noted in these blog entries, and that the experience has been buggy at best.

Monday, April 27, 2009

AJAX ReorderList Example for Adding and Editing Items using the EntityDataSource

This blog entry is very similar to my entry AJAX ReorderList Example for Adding and Editing Items using the SqlDataSource. I highly recommend you read it first to understand the fixes and enhancements I made, since it is the same logic for the EntityDataSource I show here. The functionality is the same, but the difference is that this example show how to use the EntityDataSource (part of the ADO.NET Entity Framework) instead of the SqlDataSource.

The code is very similar to what I did for the SqlDataSource. However, there is some changes that needed to be done as well. The biggest one is that the ReorderList when used with the EntityDataSource requires a DataBind() call after all commands except Edit and Update, and requires special logic for the Update command.

The big change is that there is now a RequiresReorderListDataBind Boolean that I added. This flag is set to true on the initial page load, and then set based on the command that is executed. In turn, when the control is rendered, if RequiresReorderListDataBind is true then DataBind() is called in the PreRender event. You could also call the DataBind() in the appropriate command events such as OnInsertCommand, OnDeleteCommand. However, the Update command needs to call the UpdateItem() and then call DataBind() earlier in the cycle so we put it in the OnItemCommand event. Also, you can’t but the DataBind() call in OnItemCommand for the Insert and Delete. For this reason, I have the logic in the particular events that I do. I wanted to put everything in the OnItemCommand event, but it didn’t work. :(

This example assumes you have a table in your database. Here is the SQL you can use to create one.

CREATE TABLE [dbo].[TestTable1](
    [intID] [int] IDENTITY(1,1) NOT NULL,
    [strName] [varchar](50) NOT NULL,
    [strLink] [varchar](50) NOT NULL,
    [intOrder] [int] NOT NULL,
CONSTRAINT [PK_TestTable1] PRIMARY KEY CLUSTERED
(
    [intID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

You will need to add a ADO.NET Entity Data Model (just like you add any other file to your web site). Select the database and table you created above. I called my Model MyModel and my entities MyEntities.

Here is the contents of the .aspx file.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="EntityDataSourceTest.aspx.cs" Inherits="EntityDataSourceTest" %>

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<%@ Register assembly="System.Web.Entity, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" namespace="System.Web.UI.WebControls" tagprefix="asp" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>OrderedList AJAX</title>
    <style type="text/css">
        .ajaxOrderedList li
        {
            list-style:none;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" ScriptMode="Release" />       
        <div class="ajaxOrderedList">
          <ajaxToolkit:ReorderList ID="ReorderList1" runat="server"
                AllowReorder="True"
                PostBackOnReorder="True"
                SortOrderField="intOrder"
                DataKeyField="intID"
                DataSourceID="entityDSItems"
                ItemInsertLocation="End"
                onitemreorder="ReorderList1_ItemReorder"
                onitemcommand="ReorderList1_ItemCommand"
                onprerender="ReorderList1_PreRender"
                ShowInsertItem="True">
                <ItemTemplate>
                    &nbsp;
                    <asp:HyperLink ID="HyperLink1" runat="server" Text='<% #Eval("strName") %>' NavigateUrl='<%# Eval("strLink") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Edit" Text="Edit" />
                    <asp:LinkButton ID="LinkButton3" runat="server" CommandName="Delete" Text="Delete" />
                </ItemTemplate>
                <DragHandleTemplate>
                    <asp:Panel ID="dragHandle" runat="server"
                        style="height: 20px; width: 20px; border: solid 1px black; background-color: Red; cursor: pointer;"
                        Visible="<%# ShowDragHandle %>">
                        &nbsp;
                    </asp:Panel>   
                </DragHandleTemplate>
                <ReorderTemplate>
                    <div style="width: 300px; height: 20px; border: dotted 2px black;">
                        &nbsp;
                    </div>
                </ReorderTemplate>
                <InsertItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text="Name">
                    </asp:Label><asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'></asp:TextBox><br />
                    <asp:Label ID="Label2" runat="server" Text="Link"></asp:Label>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>'></asp:TextBox><br />
                    <asp:Button ID="btnInsert" runat="server" Text="Add Link" CommandName="Insert" />
                </InsertItemTemplate>
                <EditItemTemplate>
                    <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'/>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>' />
                    <asp:TextBox ID="txtOrder" runat="server" Text='<%# Bind("intOrder") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Update" Text="Update" />
                    <asp:LinkButton ID="LinkButton2" runat="server" CommandName="Cancel" Text="Cancel" />                     
                </EditItemTemplate>
            </ajaxToolkit:ReorderList>
            <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label>
        </div>
        <asp:EntityDataSource ID="entityDSItems" runat="server"
            ConnectionString="name=MyEntities"
            ContextTypeName="MyModel.MyEntities"
            DefaultContainerName="MyEntities"
            EnableDelete="True"
            EnableInsert="True"
            EnableUpdate="True"
            EntitySetName="TestTable1"
            EntityTypeFilter="TestTable1"
            OrderBy="it.intOrder ASC"
            StoreOriginalValuesInViewState="False">
        </asp:EntityDataSource>
    </form>
</body>
</html>

Here is the code-behind (.cs) file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;

public partial class EntityDataSourceTest : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Label3.Text = DateTime.Now.ToLongTimeString();
        if (!IsPostBack)
        {
            ShowDragHandle = true;
            RequiresReorderListDataBind = false;
        }
    }
     
    protected void ReorderList1_ItemReorder(object sender, AjaxControlToolkit.ReorderListItemReorderEventArgs e)
    {
        ShowDragHandle = true;
    }

    protected Boolean ShowDragHandle { get; set; }

    protected void ReorderList1_ItemCommand(object sender, AjaxControlToolkit.ReorderListCommandEventArgs e)
    {

        switch (e.CommandName)
        {
            case "Edit":
                ShowDragHandle = false;
                RequiresReorderListDataBind = false;
                break;

            case "Update":
                ShowDragHandle = true;
                ReorderList1.UpdateItem(ReorderList1.EditItemIndex);
                ReorderList1.DataBind();
                RequiresReorderListDataBind = false;
                break;

            // Cancel, Insert, Delete, and any unknown case
            default:
                ShowDragHandle = true;
                RequiresReorderListDataBind = true;
                break;
        }
    }
   
  
    private Boolean RequiresReorderListDataBind { get; set; }
    protected void ReorderList1_PreRender(object sender, EventArgs e)
    {
        if (RequiresReorderListDataBind)
        {
            ReorderList1.DataBind();
        }
    }
}

Tips

  • Be sure to set the ContextType property of the EntityDataSource as described here.
  • There are fewer issues when using a SqlDataSource as described here.
  • Be sure to set the OrderBy property of your EntityDataSource. An example is “it.intOrder ASC”.

AJAX ReorderList Example for Adding and Editing Items using the SqlDataSource

The AJAX Control Toolkit has some very powerful controls in it. The ReorderList is no exception. It basically allows users to drag and drop rows of the list around into any order the user desired. It also, allows the user to edit and add new rows as well.

It does support the SqlDataSource quite well. This blog entry will show how to use the SqlDataSource. However, it seems that does not support the EntityDataSource that well. In a later blog entry I will show how to use the EntityDataSource.

The ReorderList has some undesired behavior such as being able to reorder items when one of the rows is in Edit mode. This “feature” allows the following problem that shows when the items are reordered, after the postback, the same position (EditItemIndex) is the same which means if the item you were editing has a different position, another row (now in that same EditItemIndex) will be edited, not the row you were editing. I show you how to change (fix) this behavior.

 Most examples you find out there, except one that I can find don’t show how to do Edit on the list of items. This blog shows a pretty good implementation of using the SqlDataSource, so I would recommend looking here also. This is where I started and what my examples are based on. The only issue I found with his example is that after an reorder, edit will edit the wrong row. To fix this be sure to do the key extra steps.

There are some key extra steps you need to do to get Editing of the Reorder list to work, that you don’t necessarily have to do for the read only mode.

  1. PostBackOnReorder=”True”
  2. Don’t use the Update panel around the ReorderList (you can, but it won’t do any good).

This example assumes you have a table in your database. Here is the SQL you can use to create one.

CREATE TABLE [dbo].[TestTable1](
    [intID] [int] IDENTITY(1,1) NOT NULL,
    [strName] [varchar](50) NOT NULL,
    [strLink] [varchar](50) NOT NULL,
    [intOrder] [int] NOT NULL,
CONSTRAINT [PK_TestTable1] PRIMARY KEY CLUSTERED
(
    [intID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

This is an enhanced copy of the blog I noted earlier.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>OrderedList AJAX</title>
    
    <style type="text/css">
        .ajaxOrderedList li
        {
            list-style:none;
        }
    </style>
    
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" ScriptMode="Release" />        
       
        <div class="ajaxOrderedList">
          <ajaxToolkit:ReorderList ID="ReorderList1" runat="server"
            AllowReorder="True"
            PostBackOnReorder="True"
            SortOrderField="intOrder"
            DataKeyField="intID"
            DataSourceID="sqlDSItems"
            ItemInsertLocation="End" 
            onitemreorder="ReorderList1_ItemReorder" 
            onitemcommand="ReorderList1_ItemCommand">
            
                <ItemTemplate>
                    &nbsp;
                    <asp:HyperLink ID="HyperLink1" runat="server" Text='<% #Eval("strName") %>' NavigateUrl='<%# Eval("strLink") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Edit" Text="Edit" />
                    <asp:LinkButton ID="LinkButton3" runat="server" CommandName="Delete" Text="Delete" />
                </ItemTemplate>
                
                <DragHandleTemplate>
                    <asp:Panel ID="dragHandle" runat="server" 
                        style="height: 20px; width: 20px; border: solid 1px black; background-color: Red; cursor: pointer;" 
                        Visible="<%# ShowDragHandle %>">
                        &nbsp;
                    </asp:Panel>
                </DragHandleTemplate>
                
                <ReorderTemplate>
                    <div style="width: 300px; height: 20px; border: dotted 2px black;">
                        &nbsp;
                    </div>
                </ReorderTemplate>
                
                <InsertItemTemplate>
                    <asp:Label ID="Label1" runat="server" Text="Name"></asp:Label>
                    <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'></asp:TextBox><br />
                    
                    <asp:Label ID="Label2" runat="server" Text="Link"></asp:Label>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>'></asp:TextBox><br />
                    <asp:Button ID="btnInsert" runat="server" Text="Add Link" CommandName="Insert" />
                </InsertItemTemplate>
                
                <EditItemTemplate>
                    <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("strName") %>'/>
                    <asp:TextBox ID="txtLink" runat="server" Text='<%# Bind("strLink") %>' />
                    <asp:TextBox ID="txtOrder" runat="server" Text='<%# Bind("intOrder") %>' />
                    <asp:LinkButton ID="LinkButton1" runat="server" CommandName="Update" Text="Update" />
                    <asp:LinkButton ID="LinkButton2" runat="server" CommandName="Cancel" Text="Cancel" />                      
                </EditItemTemplate>
                
            </ajaxToolkit:ReorderList>
            
            <asp:Label ID="Label3" runat="server" Text="Label"></asp:Label>
            <asp:Button ID="Button1" runat="server" Text="Button" />
        </div>
                
        <asp:SqlDataSource ID="sqlDSItems" runat="server" ConnectionString="<%$ ConnectionStrings:MyConnectionString %>"
                SelectCommand="SELECT [intID], [strName], [strLink], [intOrder] FROM [TestTable1] ORDER BY [intOrder]"
                DeleteCommand="DELETE FROM [TestTable1] WHERE [intID] = @intID"
                InsertCommand="INSERT INTO [TestTable1] ([strName], [strLink], [intOrder]) VALUES (@strName, @strLink, @intOrder)"
                UpdateCommand="UPDATE [TestTable1] SET [strName] = @strName, [strLink] = @strLink, [intOrder] = @intOrder WHERE [intID] = @intID">
            <DeleteParameters>
                <asp:Parameter Name="intID" Type="Int32" />
            </DeleteParameters>
            <UpdateParameters>
                <asp:Parameter Name="strName" Type="String" />
                <asp:Parameter Name="strLink" Type="String" />
                <asp:Parameter Name="intOrder" Type="Int32" />
                <asp:Parameter Name="intID" Type="Int32" />
            </UpdateParameters>
            <InsertParameters>
                <asp:Parameter Name="strName" Type="String" />
                <asp:Parameter Name="strLink" Type="String" />
                <asp:Parameter Name="intOrder" Type="Int32" />
            </InsertParameters>
        </asp:SqlDataSource>
        
    </form>
</body>
</html>

I do however need some code-behind because I am using events to fix some of the issues I described above. Here is the code-behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using AjaxControlToolkit;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Label3.Text = DateTime.Now.ToLongTimeString();
        if (!IsPostBack)
        {
            ShowDragHandle = true;
        }
    }

    protected void ReorderList1_ItemReorder(object sender, ReorderListItemReorderEventArgs e)
    {
        ShowDragHandle = true;
    }

    protected Boolean ShowDragHandle { get; set; }

    protected void ReorderList1_ItemCommand(object sender, ReorderListCommandEventArgs e)
    {
        switch (e.CommandName)
        {
            case "Cancel":
            case "Insert":
            case "Delete":
            case "Update":
                ShowDragHandle = true;
                break;

            case "Edit":
                ShowDragHandle = false;
                break;
            
            default:
                break;
        }
    }
}

The code is pretty straight forward I think, but here is a bit of explanation to help you understand. The ShowDragHandle boolean is bound to the Panel that makes up the drag handle. It shows and hides based on this boolean. When the page if first loaded (non-postback), it is shown. All events except Edit set the ShowDragHandle to true.

I do have a Label called Label3 that is set to the current data-time on page load. This is there so that you can see when a postback occurs. No real reason to include this in your solution, it is really just to help see when a postback occurs.

Tips

  • Be sure that your select statement has an order by statement and is ordering by (ASCENDING) the same column you set the SortOrderField to.
  • If you need more information on installing the AJAX Control Toolkit in Visual Studio 2008 SP1, check of my blog entry.

Thursday, April 23, 2009

Installing AJAX Control Toolkit in Visual Studio 2008 SP1

I found it a bit frustrating to figure out what I needed and how to setup everything to use the AJAX Control Toolkit. Here is what I learned.

Since ASP.NET AJAX is built into ASP.NET 3.5 and ASP.NET 3.5.1(which is part of Visual Studio 2008 and Visual Studio 2008 SP1 respectively), you don’t need to download or install anything else when usual Visual Studio 2008 SP1, except the Toolkit itself. It is NOT included with any Visual Studio. You MUST download the toolkit itself.

Click here to go to the download page. It is important to get the correct version of the toolkit since it has one for each version of Visual Studio. In particular, there is a version for Visual Studio 2008 (Original Release) and another version for Visual Studio 2008 SP1. To download click one of the four links under the Downloads & Files section on the page. I recommend the AjaxControlToolkit-Framework3.5SP1-NoSource.zip link if you don’t need the source, and just want to use the toolkit. This is the kind of installation you would typically get with any other third party control library.

Figuring that out was the hard part for me. I’m sure there is some document out there that explains all this, but I only found clues in different places.

Now that you have the file, unzip it to a location on your hard drive. You will then need to run the installer. Depending on where you unzipped the file, the installer is located at a path similar to the following:

C:\AjaxControlToolkit-Framework3.5SP1-NoSource\AjaxControlExtender\AjaxControlExtender.vsi

The project template never shows up in Visual Studio, so I don’t know what the installer actually did. I would love to hear what other people experience.

Open Visual Studio 2008 SP1 and add a tab to the Toolbox for the AJAX Toolkit controls by right-clicking on the Toolbox .

Add the toolkit controls to the tab by right-clicking the area below the tab label. Choose the Choose Items…. menu item. Browse to the AjaxControlToolkit.dll assembly.

The DLL for controls that you will use is located in the SampleWebSite\Bin directory. The path should be similar to:

C:\AjaxControlToolkit-Framework3.5SP1-NoSource\SampleWebSite\Bin\AjaxControlToolkit.dll

The AJAX Control Toolkit controls are now available just like any other server control.

NOTE: If you don’t have a project open and the active file (in your main window) is a web page or user control, etc, you won’t see the controls because Visual Studio only shows the controls in the proper context like editing a web page.

References

Tuesday, April 21, 2009

The ContextType property of control 'MyEntityDataSource' must specify a data context.

Do you get something like the following?

The ContextType property of control 'MyEntityDataSource' must specify a data context.

It is likely because the ContextType property (of the EntityDataSource) must be specified in code when using ADO.NET Entity Framework (EF) and EntityDataSource.

I got this when I was using an EntityDataSource and trying to use Dynamic Data (GridView in this case). I forgot that with ADO.NET Entity Framework, the ContextType needs to be set in code for some reason. The best (and maybe only) place is in the Page_Init. Here is the code that will hopefully fix the issue for you also.

protected void Page_Init(object send, EventArgs e)
{
 dsReviewer.ContextType = typeof(DataModel.MyEntities);
 DynamicDataManager1.RegisterControl(gvReviewers);
}

Alternatively, you can set the ContextTypeName in code or using the Properties Window in the Visual Studio 2008. The important thing to remember is to include the namespace. So, in this case, you would set the ContextTypeName to DataModel.MyEntities

Note: The RegisterControl line is not required to fix this issue, but does fix another issue when using DynamicData with the ADO.NET Entity Framework (EF).

Executing custom business logic when saving (when using the ADO.NET Entity Framework)

ADO.NET Entity Framework allows you to use partial class implementations to extend the framework and entities to meet your needs. One thing you may need to do is add some custom business logic that is supposed to happen when an entity is saved. In this entry I will show one way to implement this.

The ObjectContext is responsible (among other things such as tracking changes) for saving changes from the entities in the ObjectContext to the database. This means that if we want to do something just before the save changes is called we need to do it in a proxy object or other wrapper like business object, or we can extend the ObjectContext. In this entry, I show how to extend the ObjectContext.

The ObjectContext has many events. One of them is the SavingChanges event. This is called just before the standard ObjectContext save changes code is executed. This means that if we wanted to set some default values such as DateCreated to the current date and time we can. All we have to do is find the object we want to modify, and set the property as desired.

One trick to using the partial class implementation of our ObjectContext object is that we can use the partial implementation of the OnContextCreated() method. This is called early on in the cycle and can be used for registering our handler for the SavingChanges event.

The code below shows an example of how this can be done.

public partial class MyEntities
{
 partial void OnContextCreated()
 {
  // Register the handler for the SavingChanges event.
  this.SavingChanges += new EventHandler(MyEntities_SavingChanges);
 }

 // fires before save is called by ObjectContext
 private void MyEntities_SavingChanges(object sender, EventArgs e)
 {
  foreach (ObjectStateEntry entry in ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added))
  {
   if (entry.Entity.GetType() == typeof(MyEntity))
   {
    MyEntity contract = entry.Entity as MyEntity;
    contract.DateCreated = DateTime.Now;
   }
  }
 }
}


For alternate ways of implementing this such as using a proxy, check out: http://msdn.microsoft.com/en-us/library/cc716714.aspx

For more info on customizing Entity Framework objects, check out:
http://msdn.microsoft.com/en-us/library/bb738612.aspx

For a good starting point and more information on this topic on the ADO.NET Entity Framework (EF), check out:
http://msdn.microsoft.com/en-us/library/bb399572.aspx
or  http://blogs.msdn.com/dsimmons/pages/entity-framework-faq.aspx or http://msdn.microsoft.com/en-us/data/aa937723.aspx

Wednesday, April 8, 2009

ASP.NET Dynamic Data Rocks!

I am quite impressed with ASP.NET Dynamic Data that is now available in Visual Studio 2008 Service Pack 1. When used with LINQ to SL or ADO.NET Entity Framework data models it allows for very rapid development that is much easier to maintain than Microsoft has offered. If you have not looked at it, I highly recommend you take a look. You can find all kinds of resources (including very good videos) here Once you watch the videos there are some guidelines that are good to know and understand as you start exploring Dynamic Data. Check this out. For a good next step, I recommend this page. I hope this gets you started with Dynamic Data. I am so excited about it. I hope it is truly as useful as the demos claim it to be.

Simple, but robust business logic using Metadata in ADO.NET Entity Framework

ADO.NET Entity Framework

Please check out my previous post called Finally, ASP.NET validation the way it should be that describes why and what ADO.NET Entity Framework does, and why you should use it. Here, I will be showing how to implement metadata for an entity and some of the great attributes that are available to specify your business logic in an easy to use manner.

If you have not already, you will need to have added the ADO.NET Entity Data Model to your project, and add the table you want to model to it.

Since, the Entity Data Model uses partial classes (one for the generated code, and one that you can optionally create) we can write all our custom logic in an our own entity. Let's assume you have an Entity called Person. Just create a new class called Person in the same project and namespace as your ADO.NET Entity Data Model. Then add the partial keyword and this will automatically wire your new partial class up to the generated partial class.

That would look something like:

public partial class Person {}

Next we need to create a class that describes the metadata we want to add for the Person class. I recommend putting it in the same file as the Person partial class you created earlier. That would look something like this, though the name is not important:

public class PersonMetaData.{}

Now, we need to tell the Entity Framework where to find the metadata for the Person table. To do this we use an attribute on our Person class like this:

[MetadataType(typeof(PersonMetaData))]
public partial class Person {}

Now, I can use attributes to describe my metadata. Here is an example of what your Person.cs file may look like.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;

using System.ComponentModel.DataAnnotations;

namespace DataModel
{

[MetadataType(typeof(PersonMetaData))]
public partial class Person {}

public class PersonMetaData
{
[Range(1,150)]
[DisplayFormat(DateFormatString = "{0:###}", ApplyFormatInEditMode=true)]
public object Age;

[DisplayFormat(DataFormatString="{0:yyyy-MM-dd hh:mm}", ApplyFormatInEditMode=true)]
[DataType(DataType.DateTime)]
public object Birthday{ get; set; }
}

}


There are lots of attributes that can be added to describe the meta data. You can even create your own (they are easy to do). The attributes, are located in the System.ComponentModel.DataAnnotations namespace (also its own assembly). It is interesting to note that this is not in the web namespace. This means it can be used in other presentation layers.

Here is a list of the available built-in attributes:

  • DataType - Allows you to specify that this field is a certain kind of data that is more specific than the database types. For example, you can specify that the data is an email or date only. See the DataType enumeration for a complete list of supported datatypes.
  • DisplayColumn - Applicable when a column in a table contains a foreign key, Dynamic Data infers the display value for that column from the referenced table. By default, the first column of the referenced table whose type is string (or text) is used. If you want to use a different column use this attribute to override this behavior.
  • DisplayFormat - Allows you to specify a format for the data. For example: "{0:C}" for currency, "{0:d}" or {0:yyyy-MM-dd}" for date
  • Range - Allows you to restrict teh value to a numeric range. For example, value must be between 1 and 100 or even a date range.
  • RegularExpression - Allows you to specify what regular expression the value must match in order to be considered valid. For example, @"^[a-zA-Z]*$" to allow only alpha characters
  • Required - Allows you to indicate that a field is required. Allows you to specify a custom error message if you prefer also.
  • ScaffoldColumn - Allows you to exclude a field from the scaffolding. This is useful for not including auto-generated primary key fields when using Entity Framework.
  • ScaffoldTable - Allows you to exclude or include tables from being scaffolded.
  • StringLength - Allows you to limit the number of characters in a field.
  • UIHint - Allows you to specify the user-defined control to use to display the data field when you are using Dyanmic Data controls. See http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.uihintattribute.aspx for an in-depth example.

    For more info see:
    http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx

Finally, ASP.NET validation the way it should be

Finally validation in the business logic, not the UI.

One of my biggest frustrations with ASP.NET all along has been that implementing simple validation and even complex validation has always been in the UI layer. Of course the validation must be presented using the UI layer, however the UI should not be determining what validators should or should not be used. This should be the responsibility of the business layer.

The database defines some of this business logic implicitly as well. For example, datatype, maximum field lengths, and numeric ranges (based on data type), required field or optional (nullable). This is necessary as these are rules that are both business rules and data rules.

In my mind, all validation should bubble up from a lower layer to a higher layer. The lowest layer being the database, then the data access layer, business layer (including business entities), and finally UI. If the logic is available at a lower layer it should be the default for higher layers.

There is validation that should not require much effort of the developer, but are also required on nearly every application I have ever written. These include at least the data rules. What this means is that if the database shows that the OrderDate column is required and is of datatype DateTime the developer should not have to hand code this validation logic. It should be something that is a given. It is a waste of time, a maintenance issue, and in general just not something that should have to be worried once it is defined in the database.

There is above that information that we know as the designer of the system that the database doesn't know. This is basically metadata. For example, it could be that the OrderDate is actually only a date field, not time information is important. In the database both are always stored, but we don't always care about both. We also may want to specify a format for the date to be used everywhere. We should be able to specify metadata about our columns that quickly and easily describes these attributes.

Custom validation on a particular column is also needed sometimes. Sometimes external sources need to be checked to determine the validity of a value. Or maybe it depends on user permissions, etc. In this case, it is important to be able to write this custom logic at the column (field) level, not in the UI. Ideally, it would be in the entity that represents that table. Any validation error should bubble up to the UI in this case as well.

Custom validation on multiple columns is also sometimes needed.  For example, maybe you want to be able to require that the StartDate is before the EndDate. Again, this should bubble up to the UI.

One final layer that should just know what to do is the UI. The UI is responsible for presenting this validation to the end user. We should be able to specify in one place that by default all datetime fields should use a Calendar control for example. Why should we have to rewrite and make that decision over and over again throughout the application. Again, if we change our mind in the future, this should be done in a single place, not everywhere we used the calendar in the UI.

What I have described to you is what I have been wanted from day one with ASP.NET. WebObjects (from the Java world) gave me this way back in 1996. Finally, ASP.NET has caught up, and done an excellent job of implementing it. The technologies you want to look at are ADO.NET Entity Framework and LINQ to SQL Framework. They both have what is needed to be the data and business layers. ADO.NET Entity Framework is a lot more flexible by not requiring direct 1-to-1 mapping of entity to table, is serializable, and still can be used with LINQ using LINQ to Entities. Even when you use both of those, the UI is still the problem because you have to keep it in sync. ASP.NET has the answer Dynamic Data to the rescue. You can use Dynamic Data to have the UI behave as I described throughout this discussion.

Now that you understand why these technologies are so cool here are some links to my blog entries that tackle some of these issues.

Friday, April 3, 2009

LINQ to SQL can't be serialized when using Windows Workflow Foundation (WF), but there is a solution.

OK, the statement is not strictly true. However, it is a lot more difficult than it needs to be. In my opinion the serialization for LINQ to SQL was poorly designed.

The first question might be, how do you know that? I don't really see an error in my workflow. One way is that the WorkflowTerminated event in Windows Workflow (WF) fires when your workflow runs. It should not fire under typical circumstances. If you look at the WorkflowTerminatedEventArgs parameter you will find your Exception hidden in layers of inner exceptions. Since, workflows are executed in an asynchronous manner (ASP.NET is, but threading is different), the line of code after your start workflow will not be stopped because the current thread didn't throw an exception. The thread where the workflow ran thew the exception (assumign that it wasn't already handled), and shows in the WorkflowTerminated event.

You will eventually find that you get erors like the following:

{"Type 'System.Data.Linq.ChangeTracker+StandardChangeTracker' in Assembly 'System.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable."}

or

EventArgs can't be serialized

or

other serialization error indicating that the object that you are passing to the workflow can't be serialized.

If you don't care about the road to misery of why LINQ to SQL is not the solution, just skip to the conclusion section.

To better understand what is going on, let's first look at what kind of serialization LINQ to SQL does support. It supports DataContract Serialization which is Unidirectional serialization. By default, when using SQLMetal or Visual Studio 2008 (VS2008) Object Relational Designer (O/R Designer) serialization is set to None. To support DataContract Serialization, you need to set the serialization property to Unidirectional. This will change the code generated to have DataContract and DataMember attributes generated. These attributes are used by the DataContractSerializer. I believe this is also the serialization used in Windows Communication Framework (WCF). For more information on this, check out
http://msdn.microsoft.com/en-us/library/bb546184.aspx .

There are also some issues with circular reference, but Unidirectional serialization is supposed to address this.
Here is a framework that attempts to address the issue.

There are other types of serialization that .Net supports such as XmlSerialization and BinarySerialization. BTW, viewstate doesn't use any of these. It has its own type of serialization called
LosFormatter . Be careful trying to put a LINQ to SQL entity into the viewstate or the session if you use a session store. Particularly if it is large. See here for more details. For more information on the different types of serialization check out: http://msdn.microsoft.com/en-us/library/cc656732.aspx .

In Windows Workflow (WF) the Persistence Service is responsible for serializing and deserializing objects that are passed to the workflow. Assuming you are using the out of the box, SqlWorkflowPersistenceService it uses the BinarySerialization. The problem is that LINQ to SQL does NOT support BinarySerialization due to the very important EntityRefs that it keeps.

This in my opinion is where the poor design is. Had they taken an approach much more like they did with
ADO.NET Entity Framework , Binary Serialization would work just fine. Why they designed a framework such as LINQ to SQL that is so incompatible with WF, I can't imagine. It seems like the two products were developed in silos. It totally get that the WF workflow needs to have binary serialization to support all types of data, I just don't get why LINQ to SQL was not designed with that in mind. I would love for someone to explain to me the situation.

If you are like me, you REALLY like LINQ. It has so many benefits like working for any type of data source (or at least can be extended to do so). However, I believe the LINQ to SQL version of LINQ has HUGE issues when it comes to Serialization. There are some work arounds thankfully. Unfortunately, none of them are perfect.

This blog discusses one technique, but was not sufficient because it didn't address WF incompatibility.

This blog really summarizes the issues and the work arounds fairly well if you read through all the links in the blog. If you read all the links, you should have a pretty good understanding of the issue. In my opinion, the only one that works is solution usedby the Layer Sample . The problem is that I think for many smaller applications the architecture is overkill. And more importantly, it is difficult to find the exact code to extract from this project since it has so many pieces. Read the last entry on this page by Firedancer on May 29, 2008. This solution is taken from the Layer Sample. All you have to due is clone each object you want to pass to the WF workflow. Unfortunatly, any relationships between the objects will be lost, and you will have to reconstruct it later. This is the problem I have with this solution. I want to be able to pass the object or even objects if I want as a parameter to the WF workflow and have it be the same when I am working in the WF workflow. In particular, I want all the related objects (Entities) to come along also. But, the very part of the workaround that allows it to work, creates this problem. So close, but still not there.

I just could not believe there was no good solution out there and from Microsoft. I don't want to install any third party solution. I figured there must be something I am missing. The answer is ADO.NET Entity Framework that I mentioned earlier. I provides exactly what I want, and adds to it the ability to synchronize the mapping of object model and database, and the option to have a conceptual model instead of having to map directly to the database as my model. The best part is that it is compatible with BinarySerialization and thus WF! Oh, and did I forget you can query the Object Model from ADO.NET Entity Framework using LINQ! What more could I ask for.

The conclusion
My strong recommendation is do NOT use the VS2008 LINQ to SQL file type or LINQ to SQL when using WF. It is just a major headache. Instead use ADO.NET Entity Framework. You can use LINQ on it, so you get the best of both worlds.

To summarize the benefits of using LINQ to Entities and ADO.NET Entity Framework.
Reference
  • You can properly map the database to an object model.
  • You get a conceptual model that allows your objects to be different that your tables.
  • Supports Binary Serialization (including related entities).
  • Compatible with Windows Workflow Foundation (WF)
  • Can query object model using Entity SQL or LINQ to Entities.
  • Relationship navigation between entities that is serialized.
  • Object graph of objects loaded into memory are passed when one of the objects of the graph are passed around. This means no container needed to pass around an object graph like you do with a DataSet.


Additional Resources

Customizing Entities in Linq to SQL

Linq to SQL is very powerful. There is a wonderful designer in Visual Studio 2008. It lets you just drag and drop tables onto the layout and it generates the entities for you. At first I though I would push my luke and see if I could edit the .dbml.designer.cs file. You sort of can, but ultimately, it will get overwritten the next time you open the designer and save the layout. I kept reading on the internet that you could customize these generated entities using a partial class. So, I looked in the .dbml.designer.cs file and wouldn't you know it, the entities are implemented as partial classes. But that still didn't help me at first. Then I saw some sample code that showed separate entities in the project file. These entities where named the same as the entities that were generated in the .dbml.designer.cs file. I didn't see any other connection between the two except that they were in the same namespace and assembly. I tried the same thing on my project. It worked. I did determine that it is important for the namespace to be the same for my classes and the generated classes. I don't know about the assembly have to be the same, but that is not something I am interested in this case. All you have to do is create a class in your own .cs file with the same name and use the key work partial in the class definition. public partial class Person { } Now you can customize (in this examle) Person how you like. This includes validation of properties, cross-property validation, etc. The partial classes defined in the .dbml.designer.cs file get executed first, and then your custom classes.