If you've worked with ASP.NET Dynamic Data before, you know that you can customize the scaffoling by creating a partical class for the generated Entity Framework/Linq-to-SQL classes. In this partial class, you can specify another class as the metadata class, and in the metadata class, you can create public fields with the same name as the public properties in your data-access classes and use the set of attributes in System.ComponentModel.DataAnnotations and System.ComponentModel to customize the scaffolding.
There's, of course, no intellisense ... you manually have to get all the field names right. Given that there are no "real" dependencies, there won't be any Exceptions or compile-time errors, either.
For me, this gets pretty old pretty quickly. I've written a small code generator which helps you get the initial scaffolding classes without writing them manually. It does this by loading an assembly, looking for all Entity Framework and Linq-to-SQL classes and generating the partial classes and metadata classes as a starting point for your customization.
The code is so little that I hardly dare to post it --- but I figured that it might help one or another developer. It should work for Entity Framework and Linq-to-SQL. (Note: you can also download the complete project and a ready-to-run binary at the end of this post).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data.Objects.DataClasses;
using System.Data.Linq.Mapping;
namespace DynamicDataCodeGenerator
{
public class MetadataGenerator
{
public static string GenerateMetadata(string assemblyName)
{
StringBuilder bld = new StringBuilder();
Assembly asm = Assembly.LoadFrom(assemblyName);
foreach (Type type in asm.GetTypes())
{
EdmEntityTypeAttribute[] atts =
(EdmEntityTypeAttribute[])type.GetCustomAttributes(
typeof(EdmEntityTypeAttribute), true);
if (atts.Length== 1)
{
AppendCodeForType(bld, type);
}
TableAttribute[] tableAtts =
(TableAttribute[])type.GetCustomAttributes(
typeof(TableAttribute), true);
if (tableAtts.Length == 1)
{
AppendCodeForType(bld, type);
}
}
return bld.ToString();
}
private static void AppendCodeForType(StringBuilder bld, Type type)
{
bld.Append("[MetadataType(typeof(Metadata))]").AppendLine();
bld.Append("public partial class ").Append(type.Name).AppendLine();
bld.Append("{").AppendLine();
bld.Append(" [ScaffoldTable(true)]").AppendLine();
bld.Append(" public class Metadata").AppendLine();
bld.Append(" {").AppendLine();
foreach (PropertyInfo prop in type.GetProperties())
{
bld.Append(" [ScaffoldColumn(true)]").AppendLine();
bld.Append(" public object ").Append(prop.Name).AppendLine(";");
bld.AppendLine();
}
bld.Append(" }").AppendLine();
bld.Append("}").AppendLine();
bld.AppendLine();
bld.AppendLine();
}
}
}
Download: DynamicDataCodeGenerator.zip (51KB)
Isn't this the kind of thing that T4 is for?
Posted by: Andy Pook | 02/03/2010 at 11:45 PM
Andy: Yes. In practice however there would be a few issues. First, the TT template would quite likely be longer (and substantially more complex) than the version above as you'd have to parse the EDMX instead of the DLL. (Yes, you *could* parse the DLL, too ... it just wouldn't feel too natural as the TT's output would always be delayed by one rebuild cycle ... ). But the main issue is that the TT file would re-generate the generated code on every save unless the TT is removed from the project after the initial compilation. (Asp.net MVC for example supports a special "run once"-kind of mode for TT ... ).
All in all, I think the approach above is more readable, shorter and better gets the point across, that this is a run-once code gen.
Posted by: Ingo Rammer | 02/03/2010 at 11:52 PM
I cant get this to work with LinqtoSQL... ideas?
Posted by: Scott | 05/05/2010 at 11:11 PM