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

6 comments:

Ben said...

I am unable to build this in Visual Studio 2010 Beta 2, The error is that UIHint is not virtual, override or abstract. I there something that I am missing?

Stephen J. Naughton said...

You may have to wait for the RC in Feb to get it working sorry :(

Steve

John said...

pure gold!

now have you on my book mark toolbar - let me say a big thanks for the time and effort you put in to this DD community.

Stephen J. Naughton said...

Your welcome John and good comment are always welcome :)

Steve

Unknown said...

Hello,

I am using some extensions from NuGet the is developed by you. And so my project is using AdvancedMetaModel... In this case, how can I change my project to add CustomUiHint? In other words: in this post you suggest to use CustomMetaModel. So, could I have the code of AdvancedMetaModel to change add some custom code accordingly? Or what do you suggest?

Thanks Steve for your great work on DD

Stephen J. Naughton said...

I have a OSS project that will be on GitHub soon that has ALL my bits integrated into a Project Template that is Bootstrap Friendly. That should do what you want. for Now though just Add these changes to the Advanced Meta Model and you should be good.

Steve