Showing posts with label CustomValidator. Show all posts
Showing posts with label CustomValidator. Show all posts

Friday, 11 July 2008

Part 3 - Standard ASP.Net Page with Dynamic Data features added to take advantage of the FieldTemplates.

As far as I can see there are three (oops! four then) types of Custom Page:

  1. Custom Pages Part 1 - Standard Custom Page based on an existing PageTemplate and customised in the DynamicData\CustomPages folder.
  2. Custom Pages Part 2 - A completely Custom Page again in the DynamicData\CustomPages folder.
  3. Custom Pages Part 3 - Standard ASP.Net Page with Dynamic Data features added to take advantage of the FieldTemplates.
  4. Custom Pages Part 4 - A DetailsView and a GridView using Validation Groups
  5. Custom Pages Part 5 - I18N? Internationalisation Custom Page

What we are going to do first is just to copy the Edit.aspx page we created in the Part 2, from the ~/DynamicData/CustomPages/Orders to the root of the site and rename it to OrderTable.aspx. (This is just to save going through everything from the previous example)

Copy and rename to OrdersTable.aspx

Figure 1 – copy and rename it to OrdersTable.aspx

To complete the rename we will need to rename the class:

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="OrdersTable.aspx.cs" 
    Inherits="OrdersTable" %>
Listing 1 – OrdersTable.aspx

public partial class OrdersTable : System.Web.UI.Page
{

Listing 2 – OrdersTable.aspx.cs

As you can see the Inherits and class name have been changed to OrdersTable this is just to make the class unique in the website.

Next I think more useful is a GridView rather than a DetailsView so replace the it using the instructions in the previous post (make sure you change all the dvOrders entries to gvOrders, I’m assuming you are using this naming convention for the sake of the tutorial). Set the LinqDataSource options to

Enable: Delete, Insert & Update

Figure 2 - Enable: Delete, Insert & Update

Enable: Paging, Sorting, Editing & Deleting

figure 3 - Enable: Paging, Sorting, Editing & Deleting

Now this is what we have:

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

<!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 runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>Edit Order</h1>
        <asp:ValidationSummary ID="ValidationSummary1" runat="server" EnableClientScript="true" 
HeaderText="List of validation errors" />
<asp:DynamicDataManager ID="ddmOrders" runat="server" /> <asp:DynamicValidator runat="server" ID="ddvOrders" ControlToValidate="gvOrders" Display="None"> </asp:DynamicValidator> <asp:GridView runat="server" ID="gvOrders" DataSourceID="ldsOrders" AllowPaging="True" AllowSorting="True" AutoGenerateColumns="False" DataKeyNames="OrderID"> <PagerSettings Mode="NextPreviousFirstLast" /> <Columns> <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" /> <asp:DynamicField DataField="OrderID" /> <asp:DynamicField DataField="Customer" /> <asp:DynamicField DataField="Employee" /> <asp:DynamicField DataField="OrderDate" /> <asp:DynamicField DataField="RequiredDate" /> <asp:DynamicField DataField="ShippedDate" /> <asp:DynamicField DataField="Shipper" /> <asp:DynamicField DataField="Freight" /> <asp:DynamicField DataField="ShipName" /> <asp:DynamicField DataField="ShipAddress" /> <asp:DynamicField DataField="ShipCity" /> <asp:DynamicField DataField="ShipRegion" /> <asp:DynamicField DataField="ShipPostalCode" /> <asp:DynamicField DataField="ShipCountry" /> </Columns> </asp:GridView> <asp:LinqDataSource runat="server" ID="ldsOrders" ContextTypeName="NorthwindDataContext" EnableUpdate="True" TableName="Orders" EnableDelete="True" EnableInsert="True"> </asp:LinqDataSource> </div> </form> </body> </html>

Listing 3 – OrdersTable.aspx

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.DynamicData;

public partial class OrdersTable : System.Web.UI.Page
{
    // create a class level variable to hold the metatable
    protected MetaTable mtOrders;

    protected void Page_Init(object sender, EventArgs e)
    {
        // register the DetailsView with the DynamicDataManager
        ddmOrders.RegisterControl(gvOrders, true);
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        // get the metatable
        mtOrders = ldsOrders.GetTable();
        Title = mtOrders.DisplayName;
    }
    protected void gvOrders_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)
    {
        // make sure the Update worked before redirecting back to Order List
        if (e.Exception == null || e.ExceptionHandled)
        {
            Response.Redirect(mtOrders.ListActionPath);
        }
    }
    protected void gvOrders_ItemCommand(object sender, DetailsViewCommandEventArgs e)
    {
        //make sure that it's the Cancel command
        if (e.CommandName == DataControlCommands.CancelCommandName)
        {
            Response.Redirect(mtOrders.ListActionPath);
        }
    }
}

Listing 4 – OrdersTable.aspx.cs (the bits with strike through no longer required, just delete them)

As you can see very little is different form the previous example in the previous post.

OrdersTable.aspc in action

Figure 4 – OrdersTable.aspc in action

The difference come when you click Edit you get inline editing (like you get in the Listdetails.aspx page) but with all the FieldTemplates were you want them and validation also works:

Edit and validation in action

Figure 4 – Edit and validation in action

Note: It’s worth noting that at this point the page will only be accessible if you link to it via say the Default.aspx or run it directly. Because by default the RouteExistingFiles property of RouteCollection is set to false any file that exist already will not be affected by Routing. But using this kind of page will probably be used more in standard ASP.Net applications taking advantage of DynamicData.

Thursday, 10 July 2008

Part 2 - A completely Custom Page again in the DynamicData\CustomPages folder

As far as I can see there are three (oops! four then) types of Custom Page:

  1. Custom Pages Part 1 - Standard Custom Page based on an existing PageTemplate and customised in the DynamicData\CustomPages folder.
  2. Custom Pages Part 2 - A completely Custom Page again in the DynamicData\CustomPages folder.
  3. Custom Pages Part 3 - Standard ASP.Net Page with Dynamic Data features added to take advantage of the FieldTemplates.
  4. Custom Pages Part 4 - A DetailsView and a GridView using Validation Groups
  5. Custom Pages Part 5 - I18N? Internationalisation Custom Page 

Creating a Completely Custom Page

This is relatively straight forward, this page will be a no frills page just the controls no formatting etc.

  1. Create a new web forms page in the ~/DynamicData/CustomPages/Orders folder with the name Edit.aspx you don’t need to apply a master page, this page is going to be simple.
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
        
        </div>
        </form>
    </body>
    </html>
  2. Now you have the new Edit.aspx page in the Orders folder. Add a DetailsView give it an ID of dvOrders, now add a new LinqDataSource underneath the DetailsView give it an ID of ldsOrders.
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:DetailsView ID="dvOrders" runat="server" Height="50px" Width="125px">
            </asp:DetailsView>
            <asp:LinqDataSource ID="ldsOrders" runat="server">
            </asp:LinqDataSource>
        </div>
        </form>
    </body>
    </html>
  3. Now Configure the LinqDataSource to look at the Orders table and enable Updating.
    <asp:LinqDataSource ID="ldsOrders" runat="server" 
        ContextTypeName="NorthwindDataContext" 
        EnableUpdate="True" 
        TableName="Orders">
    </asp:LinqDataSource>
  4. Enable updating on the DetailsView and also set the DefaultMode to Edit, see below image.
    Setting DetailsView options 
    <asp:DetailsView ID="dvOrders" runat="server" 
        Height="50px" 
        Width="125px" 
        AutoGenerateEditButton="True" 
        DefaultMode="Edit">
    </asp:DetailsView>
  5. Now point the DetailsView DataSource at the LinqDataSource ldsOrders and enable Editing
    Select Data Source and Enable Editing 
    You will notice that the DetailsView has now had its fields populated:
    <Fields>
        <asp:BoundField DataField="OrderID" HeaderText="OrderID" InsertVisible="False" ReadOnly="True" SortExpression="OrderID" />
        <asp:BoundField DataField="CustomerID" HeaderText="CustomerID" SortExpression="CustomerID" />
        <asp:BoundField DataField="EmployeeID" HeaderText="EmployeeID" SortExpression="EmployeeID" />
        <asp:BoundField DataField="OrderDate" HeaderText="OrderDate" SortExpression="OrderDate" />
        <asp:BoundField DataField="RequiredDate" HeaderText="RequiredDate" SortExpression="RequiredDate" />
        <asp:BoundField DataField="ShippedDate" HeaderText="ShippedDate" SortExpression="ShippedDate" />
        <asp:BoundField DataField="ShipVia" HeaderText="ShipVia" SortExpression="ShipVia" />
        <asp:BoundField DataField="Freight" HeaderText="Freight" SortExpression="Freight" />
        <asp:BoundField DataField="ShipName" HeaderText="ShipName" SortExpression="ShipName" />
        <asp:BoundField DataField="ShipAddress" HeaderText="ShipAddress" SortExpression="ShipAddress" />
        <asp:BoundField DataField="ShipCity" HeaderText="ShipCity" SortExpression="ShipCity" />
        <asp:BoundField DataField="ShipRegion" HeaderText="ShipRegion" SortExpression="ShipRegion" />
        <asp:BoundField DataField="ShipPostalCode" HeaderText="ShipPostalCode" SortExpression="ShipPostalCode" />
        <asp:BoundField DataField="ShipCountry" HeaderText="ShipCountry" SortExpression="ShipCountry" />
    </Fields>
    
  6. We need to do a search and replace on the BoundField and replace it with DynamicField and also replace all the extra properties that are not used by DynamicField. For these fields use a Regular Expression search and replace with this string as the source:
    HeaderText=\".*\" SortExpression=\".*\" 
    Search and replace using regular expressions
  7. Now All we have to do is change the foreign key fields to EntitySet/EntityRefs, i.e. CustomerID to Customers and ShipVia to Shipper. As in the last article you can find this information in the Linq to SQL classes designer.cs file.
    <Fields>
        <asp:DynamicField DataField="OrderID" />
        <asp:DynamicField DataField="Customers" />
        <asp:DynamicField DataField="Employees" />
        <asp:DynamicField DataField="OrderDate" />
        <asp:DynamicField DataField="RequiredDate" />
        <asp:DynamicField DataField="ShippedDate" />
        <asp:DynamicField DataField="Shipper" />
        <asp:DynamicField DataField="Freight" />
        <asp:DynamicField DataField="ShipName" />
        <asp:DynamicField DataField="ShipAddress" />
        <asp:DynamicField DataField="ShipCity" />
        <asp:DynamicField DataField="ShipRegion" />
        <asp:DynamicField DataField="ShipPostalCode" />
        <asp:DynamicField DataField="ShipCountry" />
    </Fields>
  8. Now at the beginning of the page add a ValidationSummary, DynamicDataManager and DynamicValidator see below:
    <asp:ValidationSummary ID="ValidationSummary1" runat="server" EnableClientScript="true"
        HeaderText="List of validation errors" />
    <asp:DynamicDataManager ID="ddmOrders" runat="server" />
    <asp:DynamicValidator runat="server" 
        ID="ddvOrders" 
        ControlToValidate="gvOrders"
        Display="None">
    </asp:DynamicValidator>
  9. In the code behind add the following:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    // remember to add this using
    using System.Web.DynamicData;
    public partial class DynamicData_CustomPages_Orders_Edit : System.Web.UI.Page { // create a class level variable to hold the metatable protected MetaTable mtOrders; protected void Page_Init(object sender, EventArgs e) { // register the DetailsView with the DynamicDataManager ddmOrders.RegisterControl(dvOrders, true); } protected void Page_Load(object sender, EventArgs e) { // get the metatable mtOrders = ldsOrders.GetTable();
    // add title to page


    Title = “Orders”; } protected void dvOrders_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e) { // make sure the Update worked before redirecting back to Order List if (e.Exception == null || e.ExceptionHandled) { Response.Redirect(mtOrders.ListActionPath); } } protected void dvOrders_ItemCommand(object sender, DetailsViewCommandEventArgs e) { //make sure that it's the Cancel command if (e.CommandName == DataControlCommands.CancelCommandName) { Response.Redirect(mtOrders.ListActionPath); } } }
  10. Now select the event handlers for the DetailsView from it’s properties.
    Selecting the event handlers 
  11. Oh and pop a title in at the top of the page
    <h1>Edit Order</h1>

And that's the bare minimum custom page, next the standard ASP.Net page using DynmaicData.

Thursday, 3 July 2008

Dynamic Data: Part 1 - FileImage_Edit FieldTemplate

  1. Part 1 - FileImage_Edit FieldTemplate
  2. Part 2 - FileImage_Edit FieldTemplate

This post come from this thread Anyone know how to use the FileImageAPI Edit Funtionality? on ASP.Net Dynamic Data forum in which proposed two solutions to the issue the in DynamicDataFutures there was no FileImage_Edit FieldTemplate.

Here are my proposed solutions:

  1. Get a list of images from the specified folder [ImageUrl("~/images/{0}.png")] and let the user choose
  2. Let the user upload the file to the [ImageUrl("~/images/{0}.png")] folder and the pass the filename to the DB field.

Solution 1

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="FileImage1_Edit.ascx.cs"
    Inherits="FileImage1_Edit" %>
<asp:RadioButtonList ID="RadioButtonList1" runat="server" 
    RepeatDirection="Horizontal"
    RepeatLayout="Flow">
</asp:RadioButtonList>

Listing 1 - FileImage.ascx

using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.Web.DynamicData;

public partial class FileImage1_Edit : FieldTemplateUserControl
{
    public override Control DataControl
    {
        get
        {
            return RadioButtonList1;
        }
    }

    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);

        //check if image exists
        if (FieldValue == null)
            return;

        //format image url
        string url;
        var imageUrlAttribute = MetadataAttributes.OfType<ImageUrlAttribute>().FirstOrDefault();
        if (imageUrlAttribute == null || String.IsNullOrEmpty(imageUrlAttribute.UrlFormat))
            return;

        // get images folder
        String imagesDir = imageUrlAttribute.UrlFormat.Substring(0, imageUrlAttribute.UrlFormat.LastIndexOf("/") + 1);

        // get a list of images in the ImageUrlAttribute folder
        var dirInfo = new DirectoryInfo(Server.MapPath(imagesDir));
        var imagesFolder = ResolveUrl(imagesDir);
        var files = dirInfo.GetFiles();

        var imageFormat = MetadataAttributes.OfType <ImageFormatAttribute>().FirstOrDefault();

        if (imageFormat != null)
        {// use attributed DisplayWidth and DisplayHeight to size the images
            foreach (FileInfo file in files)
            {
                String img = String.Format
                    (
                        "<img src='{0}' alt='{1}' width='{2}' height='{3}' />",
                        imagesFolder + file.Name,
                        file.Name.Substring(0, file.Name.LastIndexOf(".")),
                        imageFormat.DisplayWidth,
                        imageFormat.DisplayHeight
                    );
                // embed image in the radio button
                var li = new ListItem(img, file.Name);
                this.RadioButtonList1.Items.Add(li);
            }
        }
        else
        { // if no ImageFormatAttribute don't resize images
            foreach (FileInfo file in files)
            {
                String img = String.Format
                    (
                        "<img src='{0}' alt='{1}' />",
                        imagesFolder + file.Name,
                        file.Name.Substring(0, file.Name.LastIndexOf("."))
                    );
                // embed image in the radio button
                var li = new ListItem(img, file.Name);
                this.RadioButtonList1.Items.Add(li);
            }
        }
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        dictionary[Column.Name] = RadioButtonList1.SelectedValue;
    }

    protected void RadioButtonList1_DataBound(object sender, EventArgs e)
    {
        if (FieldValue != null)
        {// if FieldValue has a value then set currently selected image
            var selectedImage = ((String)FieldValue).Substring(0, ((String)FieldValue).LastIndexOf("."));
            for (int i = 0; i < RadioButtonList1.Items.Count; i++)
            {
                // set select image 
                if (selectedImage == RadioButtonList1.Items[i].Value)
                {
                    RadioButtonList1.Items[i].Selected = true;
                    break; // break out of the loop only one can be selected.
                }
            }
        }
    }
}

Listing 2 - CodeBehind for FileImage_Edit.ascx

In the OnDataBinding event handler we setup RadioButtonList1 with embedded images form the folder specified in ImageUrlAttribute. When the ListItem is created the value string is set to the filename including extension, so only the filename and not the path is stored. Then in the RadioButtonList1_DataBound event handler the currently selected image is set if FieldValue is not null. In the ExtractValues method of the FieldTemplate it is a simple matter of getting the RadioButtonList1.SelectedValue to return the user selected image.

Select Image Method 

Figure 1 – Select Image Method

Solution 2

In this solution I decided that FileImage_Edit FieldTemplate needed to handle both scenarios, so I have created some more Attributes.

using System;
using System.Text;

// attribute to determin what mode the FileImage_Edit is in
// the default is Select.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FileImageAttribute : Attribute
{
    public FileImageAttribute(EditorType edit)
    {
        Edit = edit;
    }

    public EditorType Edit { get; set; }

    // Editor Type
    public enum EditorType
    {
        Select,
        Upload
    }
}

// a way of specifying which extension type to accept
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FileImageTypesAttribute : Attribute
{
    public FileImageTypesAttribute()
    {
        ImageTypes = DefaultExtensions;
    }
    
    public FileImageTypesAttribute(params String[] imageTypes)
    
        if (imageTypes.Length > 0)
        {
            ImageTypes = imageTypes;
        }
        else
        {
            ImageTypes = DefaultExtensions;
        }
    }

    public String[] ImageTypes { get; set; }

    // default extensions
    public static String[] DefaultExtensions
    {
        get { return new String[] { "gif", "png", "jpeg", "jpg" }; }
    }

    public String ToString()
    {
        StringBuilder extensions = new StringBuilder();

        foreach (var ext in ImageTypes)
        {
            extensions.Append(ext + ", ");
        }
        return extensions.ToString().Substring(0, extensions.Length - 2);
    }
}

Listing 3 - FileImageAttributes.cs

Here we have two new attribute form the FileImage_Edit FieldTemplate.

  • FileImageAttribute - this allows the selecting of one of two edit modes Select and Upload
    e.g. [FileImage(FileImageAttribute.EditorType.Upload)] which set the control to use the FileUpload to get new files and save them in to the folder specified by ImageUrlAttribute which is now REQUIRED otherwise an error is generated.
  • FileImageTypesAttribute - Allows the specifying of a list of file extensions to match the uploaded file against.

Next in the FileImage_Edit.ascx we add a PlaceHolder with an Image, a FileUpload control and a CustomValidator to handle any errors generated by the FieldTemplate when uploading files.

The kinds of error we want to trap are:

  • Any upload error such as file not found etc.
  • File types that don't match any of the file extensions supplied.
  • If a file is not an image file.

These error type need to be displayed in the same way other errors are shown in Dynamic Data.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="FileImage_Edit.ascx.cs"
    Inherits="FileImage_Edit" %>
<asp:RadioButtonList ID="RadioButtonList1" runat="server" 
    RepeatDirection="Horizontal"
    RepeatLayout="Flow">
</asp:RadioButtonList>
<asp:PlaceHolder ID="PlaceHolderImage" runat="server" Visible="false">
    <asp:Image ID="ImageEdit" runat="server" /><br />
</asp:PlaceHolder>
<asp:FileUpload ID="FileUploadEdit" runat="server" Visible="false" />
<asp:CustomValidator ID="CustomValidator1" runat="server" 
    ErrorMessage="">
</asp:CustomValidator>

Listing 4 - new FileImage_Edit.ascx

using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.Web.DynamicData;

public partial class FileImage_Edit : FieldTemplateUserControl
{
    public override Control DataControl
    {
        get
        {
            return RadioButtonList1;
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        // add CustomValidator1's event handler
        CustomValidator1.ServerValidate += new ServerValidateEventHandler(CustomValidator1_ServerValidate);
        RadioButtonList1.DataBound += new EventHandler(RadioButtonList1_DataBound);
    }

    void CustomValidator1_ServerValidate(object source, ServerValidateEventArgs args)
    {
        var fileImage = MetadataAttributes.OfType<FileImageAttribute>().FirstOrDefault();

        if (fileImage.Edit == FileImageAttribute.EditorType.Select)
            return; // don't bother with the validator if in Select mode

        var imageUrlMetadata = MetadataAttributes.OfType<ImageUrlAttribute>().FirstOrDefault();
        if (FileUploadEdit.HasFile && imageUrlMetadata != null && !String.IsNullOrEmpty(imageUrlMetadata.UrlFormat))
        {
            String imagesDir = imageUrlMetadata.UrlFormat.Substring(0, imageUrlMetadata.UrlFormat.LastIndexOf("/") + 1);
            if (String.IsNullOrEmpty(imagesDir)) imagesDir = "~/images";

            String path = Server.MapPath(imagesDir);
            String fileExtension = FileUploadEdit.FileName.Substring(FileUploadEdit.FileName.LastIndexOf(".") + 1).ToLower();
            var extensions = MetadataAttributes.OfType<FileImageTypesAttribute>().FirstOrDefault();

            String[] allowedExtensions = extensions.ImageTypes.Length > 0
                ? extensions.ImageTypes : FileImageTypesAttribute.DefaultExtensions;

            if (allowedExtensions.Contains(fileExtension))
            {
                var bytes = FileUploadEdit.FileBytes;
                try
                {
                    var a = System.Drawing.Image.FromStream(new MemoryStream(bytes));
                }
                catch
                {
                    // catch any error when user tries to load a file that 
                    // is not an image recognised by System.Drawing
                    args.IsValid = false;
                    CustomValidator1.ErrorMessage = "Not an Image, must be one of the following types: " + extensions.ToString();
                }
            }
            else
            {
                // Raise an error if the files extensionf does not match the allowed extensions
                args.IsValid = false;
                CustomValidator1.ErrorMessage = "Not correct Image type, must be one of the following types: " + extensions.ToString();
            }
        }
        else
        {
            // no file to download you decide wether this is a valid error
            // to throw comment out
            if (!FileUploadEdit.HasFile)
            {
                args.IsValid = false;
                CustomValidator1.ErrorMessage = "No file to download";
            }

            // the ImageUrlAttribute is required to work in either select or upload edit mode
            if (imageUrlMetadata == null || String.IsNullOrEmpty(imageUrlMetadata.UrlFormat))
            {
                args.IsValid = false;
                CustomValidator1.ErrorMessage = "ImageUrl attribute missing.";
            }
        }
    }

    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);

        var fileImage = MetadataAttributes.OfType<FileImageAttribute>().FirstOrDefault();
        var imageUrlMetadata = MetadataAttributes.OfType<ImageUrlAttribute>().FirstOrDefault();

        if (imageUrlMetadata == null || String.IsNullOrEmpty(imageUrlMetadata.UrlFormat))
        {
            CustomValidator1.IsValid = false;
            CustomValidator1.ErrorMessage = String.Format("An ImageUrlAttribute is required on the column '{0}' for the FileImage_Edit FieldTemplate to work.", Column.Name);
            return; // the ImageUrlAttribute is a required attribute need to throw an error
        }

        // get images folder
        String imagesDir = imageUrlMetadata.UrlFormat.Substring(0, imageUrlMetadata.UrlFormat.LastIndexOf("/") + 1);
        if (String.IsNullOrEmpty(imagesDir))
            imagesDir = "~/images";

        var imageFormat = MetadataAttributes.OfType<ImageFormatAttribute>().FirstOrDefault();

        switch (fileImage.Edit)
        {
            case FileImageAttribute.EditorType.Upload:
                FileUploadEdit.Visible = true;
                if (FieldValue == null)
                    return;
                String fileName = (String)FieldValue;
                ImageEdit.ImageUrl = imagesDir + "/" + fileName;


                if (imageFormat != null)
                {
                    ImageEdit.Width = imageFormat.DisplayWidth;
                    ImageEdit.Height = imageFormat.DisplayHeight;
                }
                PlaceHolderImage.Visible = true;
                break;
            case FileImageAttribute.EditorType.Select:
            default:
                FileUploadEdit.Visible = false;

                var dirInfo = new DirectoryInfo(Server.MapPath(imagesDir));
                if (!dirInfo.Exists)
                    dirInfo.Create(); // if directory does not exist then create it

                // get a list of images in the ImageUrlAttribute folder
                var imagesFolder = ResolveUrl(imagesDir);
                var files = dirInfo.GetFiles();

                if (imageFormat != null)
                {// size image to ImageFormatAttribute
                    foreach (FileInfo file in files)
                    {
                        String img = String.Format
                            (
                                "<img src='{0}' alt='{1}' width='{2}' height='{3}' />",
                                imagesFolder + file.Name,
                                file.Name.Substring(0, file.Name.LastIndexOf(".")),
                                imageFormat.DisplayWidth,
                                imageFormat.DisplayHeight
                            );
                        // embed image in the radio button
                        var li = new ListItem(img, file.Name);
                        this.RadioButtonList1.Items.Add(li);
                    }
                }
                else
                {// if no ImageFormatAttribute supplied the do not resize image
                    foreach (FileInfo file in files)
                    {
                        String img = String.Format
                            (
                                "<img src='{0}' alt='{1}' />",
                                imagesFolder + file.Name,
                                file.Name.Substring(0, file.Name.LastIndexOf("."))
                            );
                        // embed image in the radio button
                        var li = new ListItem(img, file.Name);
                        this.RadioButtonList1.Items.Add(li);
                    }
                }
                break;
        }
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        // get Edit type Select or Upload
        var fileImage = MetadataAttributes.OfType<FileImageAttribute>().FirstOrDefault();
        switch (fileImage.Edit)
        {
            case FileImageAttribute.EditorType.Upload:
                var imageUrlMetadata = MetadataAttributes.OfType<ImageUrlAttribute>().FirstOrDefault();

                if (FileUploadEdit.HasFile && imageUrlMetadata != null && !String.IsNullOrEmpty(imageUrlMetadata.UrlFormat))
                {
                    // get the 
                    String imagesDir = imageUrlMetadata.UrlFormat.Substring(0, imageUrlMetadata.UrlFormat.LastIndexOf("/") + 1);
                    if (String.IsNullOrEmpty(imagesDir))
                        imagesDir = "~/images";

                    // resolve full path c:\... etc
                    String path = Server.MapPath(imagesDir);

                    // get files extension without the dot
                    String fileExtension = FileUploadEdit.FileName.Substring(FileUploadEdit.FileName.LastIndexOf(".") + 1).ToLower();

                    // get allowed extensions
                    var extensions = MetadataAttributes.OfType<FileImageTypesAttribute>().FirstOrDefault();
                    String[] allowedExtensions = extensions.ImageTypes.Length > 0
                        ? extensions.ImageTypes : FileImageTypesAttribute.DefaultExtensions;

                    if (allowedExtensions.Contains(fileExtension))
                    {
                        // try to upload the file showing error if it fails
                        try
                        {
                            FileUploadEdit.PostedFile.SaveAs(path + "\\" + FileUploadEdit.FileName);
                            ImageEdit.ImageUrl = imagesDir + "/" + FileUploadEdit.FileName;
                            ImageEdit.AlternateText = FileUploadEdit.FileName;
                            dictionary[Column.Name] = FileUploadEdit.FileName;
                        }
                        catch (Exception ex)
                        {
                            // display error
                            CustomValidator1.IsValid = false;
                            CustomValidator1.ErrorMessage = ex.Message;
                            dictionary[Column.Name] = null;
                        }
                    }
                }
                else
                {
                    // retrun null if no file or ImageUrlAttribute is null or empty
                    dictionary[Column.Name] = null;
                }
                break;
            case FileImageAttribute.EditorType.Select:
            default:
                // return currently selected item
                dictionary[Column.Name] = RadioButtonList1.SelectedValue;
                break;
        }
    }

    protected void RadioButtonList1_DataBound(object sender, EventArgs e)
    {
        var fileImage = MetadataAttributes.OfType<FileImageAttribute>().FirstOrDefault();
        if (FieldValue != null && fileImage.Edit == FileImageAttribute.EditorType.Select)
        {
            //var selectedImage = (String)FieldValue;
            RadioButtonList1.Enabled = true;

            for (int i = 0; i < RadioButtonList1.Items.Count; i++)
            {
                // set select image 
                if ((String)FieldValue == RadioButtonList1.Items[i].Value)
                {
                    RadioButtonList1.Items[i].Selected = true;
                    break;
                }
            }
        }
    }
}

Listing 5 - the code behind for FileImage_Edit.aspx

The code from Listing 5 has been split into two sections by the switch statement as you can see it is switching on fileImage.Edit which is of type FileImageAttribute.EditorType and select drops through to default so if no attribute is set then select will be used.

switch (fileImage.Edit)
{
    case FileImageAttribute.EditorType.Upload:
        // do upload stuff here 
        break;
    case FileImageAttribute.EditorType.Select:
    default:
        // do select stuff here
        break;
}
Listing 6 - switch code snippet
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.Web.DynamicData;

[MetadataType(typeof(FileImageTestMD))]
public partial class FileImageTest : INotifyPropertyChanging, INotifyPropertyChanged
{
    public class FileImageTestMD
    {
        public object Id { get; set; }
        public object Description { get; set; }
[UIHint("FileImage")] [ImageUrl("~/images/{0}")] // now REQUIRED [ImageFormat(80, 80)] // now display in edit is not used but could be [FileImage(FileImageAttribute.EditorType.Upload)] [FileImageTypes("gif", "png", "jpeg", "jpg")] public object filePath { get; set; } } }

Listing 7 - Metadata classes

You can see form the Metadata classes how the new attributes are used.

Note: This uses the latest Dynamic Data Runtime and Futures, you can get them from here
Oh yes and thanks to Marcin Dobosz's for his help on the CustomValidator.

Upload File Method

Figure 2 – Upload File Method

Sunday, 1 June 2008

DateTime Validation in DynamicData

Step 1 add a CustomValidator to the CustomUserControl a copy of the DateTime_Edit.ascx (or just modify the existing DateTime_Edit.ascx user control)

<asp:CustomValidator ID="CustomValidator1" runat="server" 
    ErrorMessage="Invalid DateTime"
    ControlToValidate="TextBox1" 
    OnServerValidate="CustomValidator1_ServerValidate">
</asp:CustomValidator>

Step 2 Add a server side event OnServerValidate="CustomValidator1_ServerValidate" and an error message.

protected void Page_Load(object sender, EventArgs e)
{
    TextBox1.ToolTip = Column.Description;

    //SetUpValidator(RequiredFieldValidator1);
    //SetUpValidator(RegularExpressionValidator1);
    //SetUpValidator(DynamicValidator1);
}

Step 3 In the Page_Load event comment out the three SetUpValidator calls.

protected void CustomValidator1_ServerValidate(object source, ServerValidateEventArgs args)
{
    DateTime tempDateTime;
    String textDateTime = TextBox1.Text;
    if (DateTime.TryParse(textDateTime, out tempDateTime))
    {
        args.IsValid = true;
    }
    else
    {
        args.IsValid = false;
    }
}

Step 4 add you server side event handler for the OnServerValidate event and you've done it.