Showing posts with label MetaColumn. Show all posts
Showing posts with label MetaColumn. Show all posts

Monday, 26 October 2009

Conditional UIHint

Long time no posts Open-mouthed but I’m posting again should have some DDv2 and ASP.Net 4.0 samples soon Open-mouthed

I just had need of a conditional UIHint for a project I was working and so here it is.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ConditionalUIHintAttribute : Attribute
{
    public String UIHint { get; set; }
    public PageTemplate PageTemplates { get; private set; }

    public ConditionalUIHintAttribute() { }

    public ConditionalUIHintAttribute(String uiHint, PageTemplate pageTemplates)
    {
        UIHint = uiHint;
        PageTemplates = pageTemplates;
    }
}

Listing 1 – the  conditional UIHint attribute

We will need and IAutoColumnGenerator to generate the columns for us and here that is in Listing 2 I’ve simplified it and all the extra code extension methods are in the sample at the end of the article.

/// <summary>
/// Implements the IAutoFieldGenerator interface and 
/// supports advanced scenarios such as declarative 
/// column ordering, workaround for attribute 
/// localization issues.
/// </summary>
public class AdvancedFieldGenerator : IAutoFieldGenerator
{

    private MetaTable _table;
    private bool _multiItemMode;
    protected Page _page;

    /// <summary>
    /// Creates a new AdvancedFieldGenerator.
    /// </summary>
    /// <param name="table">The table this class generates fields for.</param>
    /// <param name="multiItemMode"><value>true</value> to indicate a 
    /// multi-item control such as GridView, <value>false</value> for 
    /// a single-item control such as DetailsView.
    /// </param>
    public AdvancedFieldGenerator(MetaTable table, bool multiItemMode, Page page)
    {
        if (table == null)
            throw new ArgumentNullException("table");

        _table = table;
        _multiItemMode = multiItemMode;
        _page = page;

    }

    public ICollection GenerateFields(Control control)
    {
        var template = _page.GetPageTemplate();
        var fields = from c in _table.GetVisibleColumns(_multiItemMode, template)
                     select new DynamicField()
                                  {
                                      DataField = c.Name,
                                      HeaderText = c.DisplayName,
                                      UIHint = c.ConditionalUIHint(template)
                                  };

        return fields.ToList();
    }
}

Listing 2 – IAutoColumnGenerator

The neat thing here is the ConditionalUIHint extension method that returns the ConditionalUIHint (see it in Listing 5) if the PageTemplate matches otherwise the standard UIHint is returned, here I’ve created a different Boolean field template called AltBoolean that is used only in Edit pages; you could also specify:

[ConditionalUIHint("AltBoolean", PageTemplate.Edit | PageTemplate.Insert)]

which would use the condition field template in both Edit and Insert mode, but in the sample metadata you will get a different field template for Edit and Insert pages to show off the feature.

[MetadataType(typeof(ProductMetadata))]
partial class Product
{
    partial class ProductMetadata
    {
        public Object ProductID { get; set; } 
        public Object ProductName { get; set; } 
        public Object SupplierID { get; set; } 
        public Object CategoryID { get; set; } 
        public Object QuantityPerUnit { get; set; } 
        public Object UnitPrice { get; set; } 
        public Object UnitsInStock { get; set; } 
        public Object UnitsOnOrder { get; set; } 
        public Object ReorderLevel { get; set; } 

        [ConditionalUIHint("AltBoolean", PageTemplate.Edit)]
        public Object Discontinued { get; set; } 
        // Entity Set 
        public Object Order_Details { get; set; } 
        // Entity Ref 
        public Object Category { get; set; } 
        // Entity Ref 
        public Object Supplier { get; set; } 
    }
}

Listing 4 – sample metadata

To use this in a page you would need to:

public partial class Details : System.Web.UI.Page
{
    protected MetaTable table;

    protected void Page_Init(object sender, EventArgs e)
    {
        DynamicDataManager1.RegisterControl(DetailsView1);
        table = DetailsDataSource.GetTable();
        DetailsView1.RowsGenerator = new AdvancedFieldGenerator(table, true, this);
    }

Listing 3 – Page_Init Details page

move the table assignment to the Page_Init and add the

DetailsView1.RowGenerator = new AdvancedFieldGenerator(table, true, Page);

to the Page_Init of each page you want to use it with. I’ve added this to every page in the sample.

Below are the extension methods attributes required to use the above code

/// <summary>
/// Conditionals the UI hint.
/// </summary>
/// <param name="column">The column.</param>
/// <param name="currentPage">The current page.</param>
/// <returns></returns>
public static String ConditionalUIHint(this MetaColumn column, PageTemplate currentPage)
{
    var conditionalUIHint = column.GetAttribute<ConditionalUIHintAttribute>();
    if (conditionalUIHint != null && conditionalUIHint.AlternateUIHint(currentPage))
        return conditionalUIHint.UIHint;
    else
        return column.UIHint;
}

/// <summary>
/// Alternates the UI hint.
/// </summary>
/// <param name="cUIHint">The c UI hint.</param>
/// <param name="currentPage">The current page.</param>
/// <returns></returns>
public static Boolean AlternateUIHint(this ConditionalUIHintAttribute cUIHint, PageTemplate currentPage)
{
    return (cUIHint.PageTemplates & currentPage) == currentPage;
}

Listing 5 – Extension methods

Updated: For ASP.Net 4.0 if MetaColumn.UIHint (see A Great Buried Sample in Dynamic Data Preview 4 and now ASP.Net 4.0 Beta 2) was virtual we could have overridden it to achieve the same thing without  having to add the AdvancedFieldGenerator to each page.

Download

All other extension methods are provided in the sample.

Have fun Open-mouthed

For the next post I think we may be able to use a FieldTemplateFatory like the EnumFieldTemplateFactory from the old Futures sample. And maybe something extra for ASP.Net 4.0 using the MetaModel classes Dont tell anyone

Tuesday, 14 July 2009

Securing Dynamic Data Preview 4 Refresh – Part 2

Continuing from the previous article Securing Dynamic Data Preview 4 Refresh – Part 1 we will proceed to complete the second two items in the to do list below:

Things we will need to Do

  • Dynamic Data Route Handler
  • Remove Delete Link from List and Details pages
  • Secure Meta model classes
  • Make columns read only using Entity Templates.

Secure Meta model classes

It would have been nice if I could have overridden the Scaffold and IsReadOnly methods of the MetaColumn class for this there would have been less code peppered around Dynamic Data but we can hope for the future.

public class SecureMetaModel : MetaModel
{
    /// <summary>
    /// Creates the metatable.
    /// </summary>
    /// <param name="provider">The metatable provider.</param>
    /// <returns></returns>
    protected override MetaTable CreateTable(TableProvider provider)
    {
        return new SecureMetaTable(this, provider);
    }
}

Listing 1 – SecureMetaModel class

public class SecureMetaTable : MetaTable
{
    /// <summary>
    /// Initializes a new instance of the <see cref="SecureMetaTable"/> class.
    /// </summary>
    /// <param name="metaModel">The meta model.</param>
    /// <param name="tableProvider">The table provider.</param>
    public SecureMetaTable(
        MetaModel metaModel, 
        TableProvider tableProvider) :
        base(metaModel, tableProvider) { }

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

    /// <summary>
    /// Gets the scaffold columns.
    /// </summary>
    /// <param name="mode">The mode.</param>
    /// <param name="containerType">Type of the container.</param>
    /// <returns></returns>
    public override IEnumerable<MetaColumn> GetScaffoldColumns(
        DataBoundControlMode mode, 
        ContainerType containerType)
    {
        return from column in base.GetScaffoldColumns(mode, containerType)
               where column.SecureColumnVisible()
               select column;
    }
}

Listing 2 – SecureMetaTable class

/// <summary>
/// Secures the column visible.
/// </summary>
/// <param name="column">The column.</param>
/// <returns></returns>
public static Boolean SecureColumnVisible(this MetaColumn column)
{
    var userRoles = Roles.GetRolesForUser();
    var activeDenys = column.GetColumnPermissions(userRoles);
    if (activeDenys.Contains(ColumnDeny.Read))
        return false;
    else
        return true;
}

Listing 3 – SecureColumnVisible extension method

[Flags]
public enum ColumnDeny
{
    Read    = 1,
    Write   = 2,
}

Listing 4 – ColumnDeny enum

So in the above four listings we have the simplified solution to hide columns based on user roles.

How it works

Listing 1 is required to let us include the SecureMetaTable in the default model Listing 2 is the SecureMetaTable all we do here is filter the columns based on user roles see Listing 3 SecureColumnVisible which hides columns based on ColumnDeny.Read this very easily and cleanly done thanks to the ASP.Net team and letting us derive from the meta classes.

Make columns read only using Entity Templates

Now to make columns read only in Edit or Insert modes based on use roles, for this we will modify tow of the default EntityTemplates Default_Edit.ascx.cs and Default_Insert.ascx.cs in these template we add the code in listing 5 to the DynamicControl_Init event handler.

if (currentColumn.SecureColumnReadOnly())
    dynamicControl.Mode = DataBoundControlMode.ReadOnly;
else
    dynamicControl.Mode = DataBoundControlMode.Edit; //Insert for the insert template
Listing 5 – Code to make a field read only in Edit or Insert modes
protected void DynamicControl_Init(object sender, EventArgs e)
{
    DynamicControl dynamicControl = (DynamicControl)sender;
    dynamicControl.DataField = currentColumn.Name;

    if (currentColumn.SecureColumnReadOnly())
        dynamicControl.Mode = DataBoundControlMode.ReadOnly;
    else
        dynamicControl.Mode = DataBoundControlMode.Edit; //Insert for the insert template
}

Listing 6 – finished DynamicControl_Init for the Edit Entity Template

/// <summary>
/// Secures the column read only.
/// </summary>
/// <param name="column">The column.</param>
/// <returns></returns>
public static Boolean SecureColumnReadOnly(this MetaColumn column)
{
    var userRoles = Roles.GetRolesForUser();
    var activeDenys = column.GetColumnPermissions(userRoles);
    if (activeDenys.Contains(ColumnDeny.Write))
        return true;
    else
        return false;
}

Listing 7 – SecureColumnReadOnly extension method

/// <summary>
/// Get a list of permissions for the specified role
/// </summary>
/// <param name="attributes">
/// Is a AttributeCollection taken 
/// from the column of a MetaTable
/// </param>
/// <param name="role">
/// name of the role to be matched with
/// </param>
/// <returns>A List of permissions</returns>
public static List<ColumnDeny> GetColumnPermissions(this MetaColumn column, String[] roles)
{
    var permissions = new List<ColumnDeny>();

    // you could put: 
    // var attributes = column.Attributes;
    // but to make it clear what type we are using:
    System.ComponentModel.AttributeCollection attributes = column.Attributes;

    // check to see if any roles passed
    if (roles.Count() > 0)
    {
        // using Linq to Object to get 
        // the permissions foreach role
        permissions = (from a in attributes.OfType<SecureColumnAttribute>()
                       where a.HasAnyRole(roles)
                       select a.Permission).ToList();
    }
    return permissions;
}

Listing 8 – GetColumnPermissions extension method

The above code Listings 5 & 6 simply test to see if the column is restricted to read only based on users roles and uses Listing 7 & 8 extension methods to achieve this.

And finally to make this work with Dynamic Data we need to modify the Global.asax

public class Global : System.Web.HttpApplication
{
    private static MetaModel s_defaultModel = new SecureMetaModel();
    public static MetaModel DefaultModel
    {
        get
        {
            return s_defaultModel;
        }
    }

    public static void RegisterRoutes(RouteCollection routes)
    {
        DefaultModel.RegisterContext(
            typeof(Models.NorthwindEntities), 
            new ContextConfiguration() { ScaffoldAllTables = true });

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

    void Application_Start(object sender, EventArgs e)
    {
        RegisterRoutes(RouteTable.Routes);
    }
}

Listing 9 – adding the SecureMetaModel to the Globalasax

Once we have a declared  our metamodel property in the Global.asax we can just reference it for the app by using it to register the context.

Download

Note: This has an ASPNETDB database which requires SQL Express 2008 and a connection to Northwind you will need to edit the connection string for Northwind.

I’m working on better hyperlinks for another post to follow shortly, these hyperlinks will offer the option of:

  • Hiding the link if it is disabled
  • Showing plain text if disabled

via an property at design time.