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:
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
[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.
4 comments:
Great !!
Nikunj Dhawan
Thanks Nikunj, your comment is much appreciated :)
Steve
Great Sample! I really appreciate your posts about dynamic data. you I have one question, is it possible to use this approach to add dynamic hyper links to GridView? for example I want to be able to add some links for some of my entities. for example for Contact Entity I like to add order_sevice.aspx?id= link in GridView which is a non-dynamicdata page.
You should be abe to link to any static (none routed page) no problem my sample allows you to change the page action for a particular table.
Steve
Post a Comment