Ever since Ruby On Rails came on the market with their RAD application development tools I was jealous. Why couldn’t we do what they do with ruby in ASP.NET and better? Appearantly Microsoft wasn’t all that happy either with the developments, because they have come up with a framework that in my opinion is going to rival ruby on rails with features that are a little more robust.
This article is not going to be an extensive tutorial on dynamic data in ASP.NET, Scott Hanselman has some pretty good videos online and I think that is enough to get you started. Instead I want to show something that is coming in .NET 4.0 and that is available today as the Dynamic Data futures release.
What is ASP.NET dynamic data
ASP.NET dynamic data is a framework that can help you set up a website with CRUD screens in no time at all. I think you don’t need more than 20 minutes to get the basics working. That includes a basic database scheme for lets say a blog.
What it offers is the possibility to create CRUD functionality without having to write a single line of code (Ok, I admit, you do need to write a line of code to configure it). It also offers the possibility to extend the application with custom pages, layout, etc. And because it is ASP.NET you get all the other cool stuff for free like ASP.NET Ajax, Web.sitemap navigation, membership, profiles, etc.
What it doesn’t offer is support for creating dynamic screens for your WCF services or custom domain models. And that’s the point where I started thinking.
What is wrong with dynamic data
I miss the possibility to use my WCF services enormously; I can’t stand the fact that I have to build my web application the RoR way. Ruby on Rails is cool, but it has a fundamental problem. Scalability is a bit tricky as the backend is merged with frontend code. It’s one big messy pile of code, despite the well executed design patterns.
What I really want is to build business services, process services and integration services and build a frontend on top of that. And of course this should not take a year to build 😉
I refuse to think that this is too much to ask for dynamic data. One could argue that dynamic data was build for developers who like ruby on rails and build small applications. I simply disagree after having researched the new .NET 4.0 version of dynamic data.
My idea for dynamic data
The idea I have is to create a dynamic data website with a backend system in the form of a WCF service. This is not supported by dynamic data, but what the heck I can sure try to make it compatible.
For dynamic data to work you need two things: A DataContext and a ModelProvider for that DataContext. Register the DataContext in the Metamodel of ASP.NET dynamic data and you’re done.
What I basically need to do is replace the ModelProvider with my own version and implement a custom DataContext class for my webservice. The ModelProvider is quite easy to make and I had that part up and running in less than 30 minutes. It was the DataContext that was going to take me ages to complete. The reason why this takes ages, is because ASP.NET dynamic data works with LINQ queries to retrieve and store objects in the DataContext instance. ADO.NET entity framework and LINQ to SQL support this out of the box. My webservice doesn’t.
The end result of this is that I don’t want to spend my time on writing LINQ support for my webservice. That would make ASP.NET dynamic data not really more efficient than using the normal ASP.NET techniques of dragging and dropping pages together with ObjectDataSource instances on them.
Dynamic data futures
Although my first attempt to create a dynamic data site with a WCF backend failed, I didn’t stop there.
Microsoft has a new habit of releasing futures releases of ASP.NET, which contain new features that are planned for new versions of ASP.NET, but are build in such a way that they work on the current code base. It’s not all that stable, but it offers a great opportunity to test new stuff out.
One of the things it contains is support for custom domain models in ASP.NET dynamic data. It does that by offering the DynamicObjectDataSource and a custom model provider. This makes it possible to do dynamic data with custom objects instead of having to use LINQ to SQL or ADO.NET entity framework.
This is all in the new .NET framework 4.0, so if you’re patient enough you can wait for it to be released and you don’t have to cope with the problems that you may get by using the futures components.
Using the dynamic data futures now
There are basically three options to get support for custom objects in combination with dynamic data today.
- You can write a custom datacontext with LINQ support
- Download the futures release, compile it and use it as is (With the added difficulty of mixups between .NET 3.5 assemblies and futures assemblies, which is a pain I can tell you that!)
- Use the method I’m about to write down here.
Setting up your website for using DynamicObjectDataSource
To get the new ObjectDataSource working I used two websites. One website based on the RTM release of dynamic data. This is going to be the donor for my field templates and one website that is going to be the host of the new dynamic data implementation. This site needs to be normal ASP.NET web application to minimize the amount of trash that you get.
The first step I performed after creating the websites was to copy the DynamicDataFolderFieldTemplates from the donor into the host website.
After that I added two references to my project:
- System.Web.DynamicData
- System.Web.Routing
The last reference is only required when you want to generate dynamic pages too. I didn’t use the routing mechanism that comes with ASP.NET MVC and ASP.NET dynamic data, but you can do that too if you want.
This gives you the possibility to build dynamic data pages with LINQ to SQL or ADO.NET entity framework. To get support for custom objects I needed one more thing and that was some code files from the Dynamic Data Futures release:
- All files from the MetadataProvider folder
- SimpleModelProvider,SimpleTableProvider,SimpleColumnProvider from the DataProviders folder
- DynamicObjectDataSource from the DataSources folder
I copied all these files to a folder named MicrosoftInternals in my web application and pressed Ctrl + Shift + B to build the whole thing. What you end up with is a new item in the toolbox named DynamicObjectDataSource that you can use on your website.
The next step is to prepare the webservice proxy for use with the new DynamicObjectDataSource. For this I created a new class called AccountDataManager, which is a class marked with [DataObject(true)]. This class contains various methods to perform Select, Insert, Update and Delete operations all marked with the [DataObjectMethod(…)] attribute to make them visible to ASP.NET. My data object class looks like this:
1: [DataObject(true)]
2: public class AccountDataManager
3: {
4: [DataObjectMethod(DataObjectMethodType.Select)]
5: public ICollection<AccountInfo> FindAllAccounts()
6: {
7: var client = new MaintainAccountsClient();
8: return new Collection<AccountInfo>( client.FindAllAccounts());
9: }
10:
11: [DataObjectMethod(DataObjectMethodType.Insert)]
12: public void CreateAccount(AccountInfo newAccount)
13: {
14: var client = new MaintainAccountsClient();
15: client.CreateAccount(newAccount.AccountNumber, newAccount.Description);
16: }
17:
18: [DataObjectMethod(DataObjectMethodType.Select)]
19: public IEnumerable<AccountInfo> FindAccountById(int AccountId)
20: {
21: var client = new MaintainAccountsClient();
22: return new Collection<AccountInfo>() { client.FindAccount(AccountId) };
23: }
24:
25: [DataObjectMethod(DataObjectMethodType.Update)]
26: public void UpdateAccountDetails(AccountInfo account)
27: {
28: //TODO: Implement this method
29: }
30: }
After doing that I created a new page with the following content:
1: <asp:Content ID="PageContent" ContentPlaceHolderID="SiteContent" runat="server">
2: <asp:GridView ID="AccountsGridView" runat="server" DataSourceID="AccountDataSource"
3: DataKeyNames="AccountId" AutoGenerateColumns="True" OnRowCommand="AccountsGridView_RowCommand">
4: <Columns>
5: <asp:ButtonField ButtonType="Link" CommandName="Select" Text="Details" />
6: </Columns>
7: </asp:GridView>
8: <cc1:DynamicObjectDataSource ID="AccountDataSource" runat="server" TypeName="ExpensesWeb.DataSources.AccountDataManager"
9: DataObjectTypeName="ExpensesWeb.AccountMaintenanceService.AccountInfo" OldValuesParameterFormatString="original_{0}"
10: SelectMethod="FindAllAccounts">
11: </cc1:DynamicObjectDataSource>
12: <asp:DynamicDataManager ID="PageDynamicDataManager" runat="server" />
13: </asp:Content>
This is all that is required to get the page working. Mind you, there is no code. All I did was create a dataobject and the rest was done using markup. Pretty neat if you ask me, however it doesn't work entirely to plan.
ASP.NET dynamic data has support for metadata through the use of attributes on your class. I will talk about this in the next section. To get this working you need to add one line of code to the OnInit method override in the code-behind of the page:
1: protected override void OnInit(EventArgs e)
2: {
3: base.OnInit(e);
4:
5: PageDynamicDataManager.RegisterControl(AccountsGridView);
6: }
Now you have a page that is working 100% with ASP.NET dynamic data. It doesn't require anymore code than this.
Expanding the sample website
Once I had a page up and running I could start expanding the sample with Metadata to rename fields, hide fields, etc. To modify the way fields are rendered you need to create a partial class for the class from the contract of the service you are using. On this class you put two attributes as the sample shows.
1: [ScaffoldTable(true)]
2: [MetadataType(typeof(AccountInfoMetadata))]
3: public partial class AccountInfo
4: {
5:
6: }
What this does is mark the class for scaffolding, so that ASP.NET can work with it and also specify a metatype for the class. This is done so you can keep certain metadata information even if the contract class is regenerated. You can of course specifiy custom field data on the real class, but this illadviced because these attributes are lost when you update the service reference.
1: public class AccountInfoMetadata
2: {
3: [DisplayName("Account number")]
4: [Required(ErrorMessage = "Account number is required")]
5: public object AccountNumber { get; set; }
6:
7: [Required(ErrorMessage = "Description is required")]
8: public object Description { get; set; }
9:
10: [ScaffoldColumn(false)]
11: public object AccountId { get; set; }
12: }
The code displayed in the above sample hides the AccountId field from the user interface and makes the account number and description required. Once you specify the metadata for a field that field behave the same on every kind of screen you render it on.
Check out the videos on ASP.NET for more ideas on how to expand the posibilities of ASP.NET dynamic data pages.
Conclusion
ASP.NET dynamic data is not only for people who want simple websites like you can build with ruby on rails. It’s also meant for those who do enterprise development and want to save time building complex pages by using type metadata already recorded.
While you can’t use the methods explained here with the .NET 3.5 SP1 version of dynamic data. It will be possible to do the stuff explained in this article with the version that is coming with the .NET framework 4.0 or by downloading the dynamic data futures package and modify the code.
One last word of warning: This is not for the faint hearted, as these components are build around a complex blackbox. Also the components used here are test versions and should be used with caution (I made my sample with the juli release of dynamic data futures. There is a newer release available).
Resources
- ASP.NET Dynamic data site – http://www.asp.net/dynamicdata
- ASP.NET Dynamic data 4.0 preview 2 – http://www.codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=20628
- ASP.NET Dynamic data futures juli release – http://www.codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=14475
One comment
Hi All!
Has someone tried to modify the mappingSource of the DataContext at run-time before anything is excecuted? I want to able to change the datasource at run-time to point at different databases depending on who the User is.
Right now it is using this code:
base(global::System.Configuration.ConfigurationManager.ConnectionStrings[“FAMP_ConnectionString”].ConnectionString, mappingSource)
Thanks!
Gerardo Gala