Monday 25 January 2010

Conditional UIHint for Dynamic Data v2 and .Net 4.0

Still not sure if I should call the .Net 4.0 v2 Big Grin

For the original article see Conditional UIHint from back in October last year, what we want to achieve is change the Field Template in use by page i.e. lets say we have Foreign Key column which would use the default ForeignKey field template but I wanted to use a custom field template during edit currently I would need to create two custom field templates one for edit and one for read only modes. With the ConditionalUIHint I can just apply the attribute like so:

[ConditionalUIHint("MyForeignKey", PageTemplate.Edit)]

And then in Edit mode it would use my custom field template.

As I said in my previous post on this for DDv1 in .Net 4.0 we can create our own custom Meta Classes (MetaModel, MetaTable and MetaColumn etc.); now I said if we could override the UIHint property of the MetaColumn, MetaChildrenColumnand MetaForeignKeyColumn’s then this sample could be made cooler i.e. to get this in DD all we would have to do is change this

private static MetaModel s_defaultModel = new MetaModel();
public static MetaModel DefaultModel
{
    get { return s_defaultModel; }
}

Listing 1 – getting metamodel in Global.asax.cs

to this

// get the custom metamodel
private static MetaModel s_defaultModel = new CustomMetaModel();
public static MetaModel DefaultModel
{
    get { return s_defaultModel; }
}

Listing 2 – getting custom metamodel in Global.asax.cs

so all we have to do is use our own custom metamodel in place of the default Dynamic Data MetaModel cool and if you read A Great Buried Sample in Dynamic Data Preview 4 – Dynamic Data Futures you can use this to control all sorts of things centrally in Dynamic Data without touching each page. Applause

If you look back to the A Great Buried Sample in Dynamic Data Preview 4 – Dynamic Data Futures article you will see that there were five files that made up the Custom Meta classes:

Meta Classes

Image 1 - Meta Classes

Due to low budget on imagination this post they will all be prefixed with CustomBig Grin

We will look at the three extension methods first. the first extension method replaces my old method of finding out which page template we are in:

/// <summary>
/// page extension
/// </summary>
private const String EXTENSION = ".aspx";

/// <summary>
/// Gets the page template from the page.
/// </summary>
/// <param name="page">The page.</param>
/// <returns></returns>
public static PageTemplate GetPageTemplate(this Page page)
{
    // get pages path
    var path = page.AppRelativeVirtualPath;

    // trim it so we just have the page name
    var pageName = path.Substring(path.LastIndexOf("/") + 1, 
        path.Length - path.LastIndexOf("/") - EXTENSION.Length - 1);

    PageTemplate pageTemplate;

    // extract the page template from the page name
    if (Enum.TryParse<PageTemplate>(pageName, out pageTemplate))
        return pageTemplate;
    else
        return PageTemplate.Unknown;
}

Listing 3 – GetPageTemplate extension method

here I’m getting the page template by extracting the page name excluding extension and then matching it to my PageTemplate enum Listing 4 and returning PageTemplate.Unkown if no match is found (after all I may be using some PageTemplates other that the default ones).

[Flags]
public enum PageTemplate
{
    // standard page templates
    Details         = 0x01,
    Edit            = 0x02,
    Insert          = 0x04,
    List            = 0x08,
    ListDetails     = 0x10,
    // default if unknown
    Unknown         = 0xff
}

Listing 4 – PageTemplate enum

And then we have two more extension methods that are used in each Meta Column type to return the ConditionalUIHint:

/// <summary>
/// Gets the Conditional UIHint.
/// </summary>
/// <param name="column">The column.</param>
/// <param name="baseUIHint">The base UIHint.</param>
/// <returns>a UIHint string</returns>
public static String GetUIHintConditionally(this MetaColumn column, String baseUIHint)
{
    // need to get the current page template
    var page = (System.Web.UI.Page)System.Web.HttpContext.Current.CurrentHandler;
    var pageTemplate = page.GetPageTemplate();
    
    var conditionalUIHint = column.GetAttribute<ConditionalUIHintAttribute>();
    if (conditionalUIHint != null && 
        conditionalUIHint.HasConditionalUIHint(pageTemplate))
        return conditionalUIHint.UIHint;
    else
        return baseUIHint;
}

/// <summary>
/// Determines whether [has conditional UI hint] 
/// [the specified conditional UI hint].
/// </summary>
/// <param name="conditionalUIHint">The conditional UI hint.</param>
/// <param name="currentPage">The current page.</param>
/// <returns>
/// <c>true</c> if [has conditional UI hint] 
/// [the specified conditional UI hint]; otherwise, <c>false</c>.
/// </returns>
private static Boolean HasConditionalUIHint(
    this ConditionalUIHintAttribute conditionalUIHint, 
    PageTemplate currentPage)
{
    return (conditionalUIHint.PageTemplates & currentPage) == currentPage;
}

Listing 5 – GetUIHintConditionally and HasConditionalUIHint

In Listing 5 we have GetUIHintConditionally and HasConditionalUIHint the first get the UIHint and if the current page template matches one in the ConditionalUIHint the ConditionalUIHint is returned. The HasConditionalUIHint is used to determine if there is a page template match.

public class CustomMetaColumn : MetaColumn
{
    public CustomMetaColumn(
        MetaTable table, 
        ColumnProvider 
        columnProvider) : 
        base(table, columnProvider) { }

    protected override void Initialize() { base.Initialize(); }

    /// <summary>
    /// Gets the name of the field template 
    /// specified for the data field.
    /// </summary>
    /// <value></value>
    /// <returns>
    /// The name of the field template 
    /// specified for the data field.
    /// </returns>
    public override string UIHint
    {
        get
        {
            return this.GetUIHintConditionally(base.UIHint);
        }
    }
}

Listing 6 – CustomMetaColumn (Children and ForeignKey are basically the same)

Now all we need in each Meta Column type is to call the GetUIHintConditionally with the current UIHint and if the conditions are met then the ConditionalUIHint will replace the default one.

Each of the other  Meta classes is there just to facilitate this and are very basic overrides of the base classes.

Also my standard Attribute extension methods are in there.

Downloads

Happy coding

Tuesday 19 January 2010

Page Action Mapping in Dynamic Data

Well the idea here save me creating a new route each time I decide that a certain table should use a different PageTemplate than normal.

Lets say I have these tables (Northwind well when I used AdventureWorks people said I don’t have that can you do a sample with Northwind)

  • Categories
  • CustomerCustomerDemo
  • CustomerDemographics
  • Customers
  • Employees
  • EmployeeTerritories
  • Order Details
  • Orders
  • Products
  • Region
  • Shippers
  • Suppliers
  • Territories

Normally all of these would have a standard list page:

ListPage

Figure 1 – Default List page.

but lets say I want these tables

  • Categories
  • Order Details
  • Region
  • Shippers
  • Suppliers

to use the ListDetails page then I’ve go to enter a custom route for each table; what I propose to do is add an attribute to each table in the metadata and have it done automatically for me and without those extra routes.

The Attribute

What I want is to be able to say is, if this Action or this or this the match it with this e.g. if List or Details then substitute ListDetails i.e. just for the tables I want use the other default routing. Hope that makes sense Happy

[Flags]
public enum PageTemplate
{
    // standard page templates
    Details         = 0x01,
    Edit            = 0x02,
    Insert          = 0x04,
    List            = 0x08,
    ListDetails     = 0x10,
    // custom page templates
    Unknown         = 0xff,
}

Listing 1 – PageTemplate enum

I’m setting up my own enum mainly because the PageAction is not an enum and can’t be used like PageAction.List | PageAction.Details ordered together using the [Flags] attribute and also because I want to be able to add other page templates such as my UpdateableList which has inline editing an insert or one of my other custom PageTemplates.

[MetadataType(typeof(CategoryMetadata))]
[PageActionMapping(PageTemplate.List | PageTemplate.Details, "ListDetails")]
partial class Category
{
    partial class CategoryMetadata
    {
        public Object CategoryID { get; set; }
        public Object CategoryName { get; set; }
        public Object Description { get; set; }
        public Object Picture { get; set; }
        // Entity Set 
        public Object Products { get; set; }
    }
}

Listing 2 – example metadata using attribute

But I also want the Attribute to support more complex mappings like:

[PageActionMapping(
    PageTemplate.List | PageTemplate.Details, "ListDetails",
    PageTemplate.Insert | PageTemplate.Edit, "SpecialUpdatePage")]

Here I have two different mapping for the same table.

/// <summary>
/// Allows mapping of page action to other 
/// actions on a table by table basis
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PageActionMappingAttribute : Attribute
{
    /// <summary>
    /// Gets or sets the action mapping parameters.
    /// </summary>
    /// <value>The mappings.</value>
    public Dictionary<String, String> MappingParameters { get; private set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="PageActionMappingAttribute"/> class.
    /// </summary>
    /// <param name="mappings">The mappings.</param>
    public PageActionMappingAttribute(params Object[] mappingParameters)
    {
        SetMappings(mappingParameters);
    }

    /// <summary>
    /// Converts the mapping parameters from an object array to a dictionary.
    /// </summary>
    /// <param name="mappingParameters">The mapping parameters.</param>
    private void SetMappings(Object[] mappingParameters)
    {
        // initialise dictionary
        MappingParameters = new Dictionary<String, String>();

        // make sure we some parameters
        if ((mappingParameters != null) && (mappingParameters.Length != 0))
        {
            // check that they are in pairs
            if ((mappingParameters.Length % 2) != 0)
                throw new ArgumentException(
                    "PageActionMappingAttribute mapping parameters needs an even number of prameters");

            // loop through each pair
            for (int i = 0; i < mappingParameters.Length; i += 2)
            {
                var keyObject = mappingParameters[i];
                var value = mappingParameters[i + 1];

                // check there is a valid key
                if (keyObject == null)
                    throw new ArgumentException("PageActionMappingAttribute a key is null");

                // check it is a PageTemplate
                if (!(keyObject is PageTemplate))
                    throw new ArgumentException("PageActionMappingAttribute keys must be a PageTemplate");

                // check value is of type string
                if (!(value is String))
                    throw new ArgumentException("PageActionMappingAttribute value must be a string");

                // get the key(s)
                var keys = ((PageTemplate)keyObject).ToString().Split(new String[] { ", " }, 
                    StringSplitOptions.RemoveEmptyEntries);

                // check to see if we have OR'ed enum values
                if (keys.Length > 1)
                {
                    // loop through each enum value
                    foreach (var key in keys)
                    {
                        // make sure we don't have any duplicates.
                        if (this.MappingParameters.ContainsKey(key))
                            throw new ArgumentException(String.Format(
                               "PageActionMappingAttribute has duplicate key:{0}", key));

                        // add entries to dictionary
                        this.MappingParameters.Add(key, (String)value);
                    }
                }
                else
                    // add entries to dictionary
                    this.MappingParameters.Add(keys[0], (String)value);
            }
        }
    }
}

Listing 3 – PageActionMappingAttribute

The only complicated part of the attribute is the SetMappings methods all this private method does is convert the Object array into a Dictionary<String, String>() and do a bit of validation of the parameters.

The Route Handler

The route handler inherits from the DynamicDataRouteHandler and then get the PageActionMappingAttribute and check for a mapping match by checking if the dictionary contains a key that matches the action and if so substitutes the current action for the mapped value.

/// <summary>
/// Does page action mapping on routes saving 
/// manually adding routes for custom page template.
/// </summary>
public class PageActionMappingRouteHandler : DynamicDataRouteHandler
{
    public PageActionMappingRouteHandler() { }

    /// <summary>
    /// Creates the handler.
    /// </summary>
    /// <param name="route">The route.</param>
    /// <param name="table">The table.</param>
    /// <param name="action">The action.</param>
    /// <returns>An IHttpHandler</returns>
    public override IHttpHandler CreateHandler(
        DynamicDataRoute route,
        MetaTable table,
        string action)
    {
        var httpContext = HttpContext.Current;
        if (httpContext != null && httpContext.User != null)
        {
            // get mappings
            var actionMappings = table.GetAttribute<PageActionMappingAttribute>();
            // apply substitute mapping
            if (actionMappings != null && 
                actionMappings.MappingParameters.ContainsKey(action))
                action = actionMappings.MappingParameters[action];

            // return route from base
            return base.CreateHandler(route, table, action);
        }
        // return null in no context
        return null;
    }
}

Listing 4 – PageActionMappingAttribute

Putting it Together

Now all we need to do is add the route handler to the Routes in the Global.asax file.

routes.Add(new DynamicDataRoute("{table}/{action}.aspx")
{
    Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
    RouteHandler = new PageActionMappingRouteHandler(),
    Model = model
});

Listing 5 – Default route with route handler

all we need to do is assign our route handler in each route in the Global.asax file see Listing 5 and add our metadata see Listing 2.

Downloads

Monday 11 January 2010

Required Field Highlighting in Dynamic Data Field Templates

I’ve been using this for some time and thought I should post a small article on it.

RequiredField RequiredFKField

 Figure 1 – Highlighted fields

The idea is to give the user some prior knowledge the some field are required and these are have their backgrounds tinted yellow, so lets look at the code for this:

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

    SetUpValidator(RequiredFieldValidator1);
    SetUpValidator(RegularExpressionValidator1);
    SetUpValidator(DynamicValidator1);

    // add 'validationRequired' class to the TextBox
    if (Column.IsRequired)
        TextBox1.CssClass += " validationRequired";
}

Listing 1 – the highlighter code.

It’s the last if statement that does the work if the the column is required then the “validationRequired” class is added to the fields class.

Note: All you need to do is add this code to each Edit field template that has a required field validator.
/* required field class */
.validationRequired
{
    background-color: #FFFEBD;
}

Listing 2 – required field css class

Making ForeignKey Field Template have Required Validation

The next thing I like is to add validation the FK field templates so there is a select [type] as the first entry in the FK field if the field is required and validation is enabled.

<asp:DropDownList 
    ID="DropDownList1" 
    runat="server" 
    CssClass="droplist">
</asp:DropDownList>
<asp:RequiredFieldValidator 
    runat="server" 
    ID="RequiredFieldValidator1" 
    CssClass="droplist" 
    ControlToValidate="DropDownList1" 
    Display="Dynamic" 
    Enabled="false" />
<asp:DynamicValidator 
    runat="server" 
    ID="DynamicValidator1" 
    CssClass="droplist" 
    ControlToValidate="DropDownList1" 
    Display="Dynamic" />

Listing 3 – ForeignKey_Edit.aspx

protected void Page_Load(object sender, EventArgs e)
{
    if (DropDownList1.Items.Count == 0)
    {
        if (!Column.IsRequired || 
            (Column.IsRequired && Mode == DataBoundControlMode.Insert))
        {
            DropDownList1.Items.Add(new ListItem("[Not Set]", ""));
        }

        PopulateListControl(DropDownList1);
    }
    DropDownList1.ToolTip = Column.Description;

    SetUpValidator(RequiredFieldValidator1);
    SetUpValidator(DynamicValidator1);

    // add 'validationRequired' class to the TextBox
    if (Column.IsRequired)
        DropDownList1.CssClass += " validationRequired";
}

Listing 4 – ForeignKey_Edit.aspx.cs

In Listing 3 I have added the standard RequiredFieldValidator and DynamicValidator to the ForeignKet_Edit field template and in Listing 4 I have added the setup for both validators and also changed the if statement that adds the “[Not Set]” entry to the dropdown list; this is make sure the user picks a value when the column is required and it is in insert mode as opposed to just accepting the default value.

Like I said only a short article but neatOpen-mouthed

Friday 8 January 2010

Added Alex Gorbatchev’s Syntax Highlighter to my blog

Just added new syntax highlighting that uses Alex Gorbatchev SyntaxHighlighter similar to the one on MSDN blogs and ASP.Net Dynamic Data forum also I’m using Prabir Shrestha's Windows Live Writer Syntax Highlighter also here is a blog post about adding the hosted JavaScript to Blogger, I must mention Kyle Savant who got it working with blogger first and sent me the how to Open-mouthed

protected override void OnInit(EventArgs e)
{
    linkButtonId = this.UniqueID + "_Button";

    // Define the name and type of the client scripts on the page.
    string scriptName = "NotAClue.Web.UI.WebControls.ResourceFiles.NAC_OpenPopup.js";
    Type scriptType = this.GetType();

    // Get a ClientScriptManager reference from the Page class.
    ClientScriptManager csm = Page.ClientScript;

    // Check to see if the Client Script Include is already registered.
    if (!csm.IsClientScriptIncludeRegistered(scriptType, scriptName))
    {
        // include main Flash Content script
        string urlJS = csm.GetWebResourceUrl(scriptType, scriptName);
        csm.RegisterClientScriptInclude(scriptType, scriptName, urlJS);
    }

    base.OnInit(e);
}

Listing 1 – Sample listing.