Hugoware

The product of a web developer with a little too much caffeine

Posts Tagged ‘.NET

Anonymous Types As Object Templates

with 6 comments

I’ve got a bit of an addiction to intellisense. Dynamic types are interesting but I still prefer being able to see a nice neat list of properties on each of my objects.

Since anonymous types are ‘real’ classes then you can actually create them at runtime. This means you can use them as templates for other objects if you can provide all of the required parameters.

Below is a simple class that allows you to perform queries against a database and use an anonymous type to provide a template for the records to return.

/// <summary>
/// Helps working with database connections
/// </summary>
public class DataConnection {

    #region Constructors

    /// <summary>
    /// Creates a new DataConnection for the connection string
    /// </summary>
    public DataConnection(string connection) {
        this.ConnectionString = connection;
    }

    #endregion

    #region Properties

    /// <summary>
    /// The connection string to use with this DataConnection
    /// </summary>
    public string ConnectionString { get; private set; }

    #endregion

    #region Performing Queries

    /// <summary>
    /// Performs a query for the connection without any parameters
    /// </summary>
    public IEnumerable<T> Query<T>(string query, T template)
        where T : class {
        return this.Query(query, (object)null, template);
    }

    /// <summary>
    /// Performs a query for the connection with the provided paramters
    /// </summary>
    public IEnumerable<T> Query<U, T>(string query, U parameters, T template)
        where T : class 
        where U : class {

        //prepare the connection and command
        Type type = template.GetType();

        //create the connection - (thanks @johnsheehan about the 'using' tip)
        using (SqlConnection connection = new SqlConnection(this.ConnectionString)) {
            using (SqlCommand command = new SqlCommand(query, connection)) {

                //assign each of the properties
                if (parameters != null) {
                    this._UsingProperties(
                        parameters,
                        (name, value) => command.Parameters.AddWithValue(name, value));
                }

                //execute the query
                connection.Open();
                SqlDataReader reader = command.ExecuteReader();

                //start reading each of the records
                List<T> results = new List<T>();
                Dictionary<string, int> fields = null;
                while (reader.Read()) {

                    //prepare the fields for the first time if needed
                    fields = fields == null ? this._ExtractFields(reader) : fields;

                    //generate the record
                    T record = this._CreateAnonymousResult(reader, fields, type, template);
                    results.Add(record);
                }

                //return the results for the query
                return results.AsEnumerable();
            }

        }

    }

    #endregion

    #region Helpers

    //creates a new anonymous type from 
    private T _CreateAnonymousResult<T>(SqlDataReader reader, Dictionary<string, int> fields, Type templateType, T template)
        where T : class {

        //create a container to queue up each record
        List<object> values = new List<object>();

        //use the template to find values
        this._UsingProperties(template, (name, @default) => {

            //try and find the field name
            if (fields.ContainsKey(name)) {

                //check if this is a value
                int index = fields[name];
                object value = reader[index];
                Type convert = @default.GetType();

                //try and copy the va;ue
                if (value != null) {
                    values.Add(Convert.ChangeType(value, convert));
                }
                //not the same type, use the default
                else {
                    value.Equals(@default);
                }

            }
            //no field was found, just use the default
            else {
                values.Add(@default);
            }

        });

        //with each of the values, invoke the anonymous constructor
        //which accepts each of the values on the template
        try {
            return Activator.CreateInstance(templateType, values.ToArray()) as T;
        }
        catch (Exception e) {
            Console.WriteLine(e.Message);
            return template;
        }

    }

    //reads a set of records to determine the field names and positions
    private Dictionary<string, int> _ExtractFields(SqlDataReader reader) {
        Dictionary<string, int> fields = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        for (int i = 0; i < reader.FieldCount; i++) {
            fields.Add(reader.GetName(i), i);
        }
        return fields;
    }

    //performs an action with each of the properties on an object
    private void _UsingProperties(object obj, Action<string, object> with) {
        
        //if there isn't anything to work with, just cancel
        if (obj == null) { return; }

        //get the type and peform an action for each property
        Type type = obj.GetType();
        foreach (PropertyInfo prop in type.GetProperties()) {
            with(prop.Name, prop.GetValue(obj, null));
        }

    }

    #endregion

}

This class allows us to perform basic queries against a database and then provide a template of the records to return. This means that if the information is found then the database value is used but if it is missing then the template value is used instead.

In order to create an anonymous type we need to know the type and each of the properties in the order they appear. After collecting this information we simply need to use the Activator.CreateInstance method to instantiate the class and we’re set! (method _CreateAnonymousResult)

This means we can write a ‘dynamic’ query with results that have intellisense!

//set up the connection
DataConnection data = new DataConnection(CONNECTION_DATA);

//perform the query
var results = data.Query(
    "select * from orders where orderId > @orderId",
    new { orderId = 11000 },
    new {
        orderId = -1,
        shipName = "missing",
        shipRegion = "none"
    });

//access properties
var record = results.First();
int id = record.orderId; //intellisense!

I’ve actually used this same approach before in CSMongo as a way to provide intellisense without knowing anything about the database in advance.

Of course, this is just some code I hacked out this evening. You aren’t going to be able to do a lot with it but it is a good example of how you can reuse anonymous types in your code.

Written by hugoware

August 30, 2010 at 12:19 am

Null Instance Extension Methods

with 4 comments

Checking for null can be time consuming and add a lot of extra noise to the code you write. In some ways it can reduce the readability of your code and obscure the purpose of your functions.

For example, check out all of the null checks required for this bit of code.

public static int GetVersion() {
	
	//check if this exists
	if (!File.Exists(PATH_APP_DETAILS)) { return DEFAULT_VERSION; }

	//try and load the document
	XDocument document = null;
	try {
		document = XDocument.Load(PATH_APP_DETAILS);
	}
	catch {
		return DEFAULT_VERSION;
	}
	if (document == null) { return DEFAULT_VERSION; }
	
	//find the element
	XElement release = document.XPathSelectElement("app/release");
	if (release == null) { return DEFAULT_VERSION; }
	
	//get the value
	XAttribute version = release.Attribute("version");
	if (version == null) { return DEFAULT_VERSION; }
	
	//convert the version
	int current = int.TryParse(version.Value, out current) ? current : DEFAULT_VERSION;
	return current;
	
}

Yikes… that is a lot of code for such a small task but what else can you do? You can’t just call methods on something that is null or your app is going to blow up… or can you?

Null Instance Extension Methods

Extension methods feel magical the first time you see them in action but ultimately they are just static methods that can be invoked from anywhere in your project.

But here is the neat thing — Since an extension method isn’t tied to the instance of a class then they can be invoked without an instance of the class.

Sound confusing? Here is an example of what I mean…

//create an extension method
public static class StringExtensions {
	public static void Test(this string value) {
		Console.WriteLine("Hello from a null test!");
	}
}

//and then call it
string nothing = null;
nothing.Test(); //ok!
nothing.ToUpper(); //crash

You’ll notice that the call to Test worked fine – If you put a break point in the method and check the value argument then you’ll notice it is actually null! We were able to call the method Test even though there wasn’t an instance of a string.

So how can this help us? Well let’s take the same XML problem from earlier in the post and create some extension methods that don’t care if the instance being invoked is null or not.

//helper extension methods for XML documents
public static class XmlExtensions {
	
	//loads an xml document to use
	public static XDocument LoadDocument(this XDocument document, string path) {
		if (!File.Exists(path)) { return null; }
		try { return XDocument.Load(path); }
		catch { return null; }
	}
	
	//uses an XPath to find an element
	public static XElement GetElement(this XDocument document, string path) {
		return document is XDocument ? document.XPathSelectElement(path) : null;
	}
	
	//returns the value of an attribute
	public static string GetAttribute(this XElement element, string name) {
		XAttribute attribute = element is XElement ? element.Attribute(name) : null;
		return attribute is XAttribute ? attribute.Value : string.Empty;
	}
}

Now let’s take a look at how we can solve the same problem as before but with these new extension methods…

public static int GetVersion() {
	
	//open the document (Mono)
	XDocument document = document.LoadDocument(PATH_APP_DETAILS);

	//It appears you have to do it like this in Visual Studio
	//XDocument document = ((XDocument)null).LoadDocument(PATH_APP_DETAILS);
	
	//find the element
	XElement release = document.GetElement("app/release");
	
	//get the version
	string version = release.GetAttribute("version");
	
	//convert and return the value
	int current = int.TryParse(version, out current) ? current : DEFAULT_VERSION;
	return current;
}

As far as I can tell, no matter what you throw at this code, you will either get the default version or the value in the XML document. This definitely reduces the lines of code count… but the clarity gains might be debatable.

  1. A developer modifying this code might not realize the value could be null or could be a real object. If they call an instance method and the value ends up being null then the application is going to crash.
  2. Some developers are going to scratch their heads when they read the line XDocument document = document.LoadDocument(...). Invoking a method on a value you’re declaring and should be null and returning the result it to itself? Huh?
  3. The more forgiving your code is then the more prone to external errors it will be, which in turn will introduce subtle runtime errors in your applications. Sometimes it’s a better idea just to throw an exception and keep the program from running.

So, while this approach has benefits it also has pitfalls. Make sure to evaluate the risks before using something like this in your code. If implemented carefully this could reduce complexity in your application…

…or at least make for a neat parlor trick the next time you’re with your dev team.

Written by hugoware

August 22, 2010 at 9:14 pm

Quirks Of Using For Each

leave a comment »

Collections can be tricky sometimes when you’re writing code. This is especially true when you need to modify them while you’re looping through them. Let’s look at a couple examples that can get you into trouble.

//create a typical list of items
List<string> items = new List<string> {
    "first", "second", "third", "fourth"
};

//now clear the list
foreach (string item in items) {
    items.Remove(item);
}

Of course, this won’t work. Why?

InvalidOperationException: Collection was modified; enumeration operation may not execute

Once our loop has started the boundaries of the collection have been defined. Adding or removing items will change those boundaries and create errors. Naturally, most languages aren’t going to like this and will crash.

Fortunately, we do get an Exception message (which is the best kind of error message). However, we should be more concerned about subtle problems that could arise if we change a collection while looping through it. For example, consider the ForEach method attached to Generic Lists…

//get a list of the employees that 
//should be deleted from the system
List<string> firedEmployees = new List<string> {
    "jeff", "blake", "steve"
};

//and delete them
firedEmployees.ForEach(employee => {
    EmployeeSystem.Delete(employee);
    firedEmployees.Remove(employee);
});

Everyone deleted? Nope…

I don’t know about you, but I was surprised to find that one employee remained on this list after the code was run (the second employee at that). ForEach made sure to keep track of our collection as it changed but unfortunately it created an undesirable result.

To take it a step further, if you were to add a new item each time then you’d end up with an infinite loop, an error that would be caught sooner but a problem all the same.

This straight forward code has potential for some serious problems.

So, abandon the framework! Move to Java! .NET is doomed!

…Or we can just write an extension method to take care of both problems at the same time.

public static class EnumerableExtensions {

    /// <summary>
    /// Safely enumerates through a collection
    /// </summary>
    public static IEnumerable<T> Each<T>(this IEnumerable<T> collection, Action<T> action) {

        //move the collection into its own list and
        //perform the work on each item
        collection.ToList().ForEach(action);
        return collection;

    }

}

The fix itself isn’t very impressive. One additional call to ToList() and we’re pretty much okay. But this does keep the original collection safe while we enumerate through the protected one.

Written by hugoware

July 22, 2010 at 11:03 pm

Almost Sorta Real Dynamic in .NET

with 8 comments

This post discusses the basics of writing your own dynamic type – If you’re interested in the finished code make sure to download the source.

The new dynamic keyword is a great way to create and modify a dynamic object in your .NET code – For example, check out this bit of code.

dynamic counter = new {
    sent = 0,
    received = 0
};
counter.sent++;

… Oh… wait… That doesn’t actually work…

That’s because even though you have an object declared as dynamic, it is still just the same old object it always was under the hood. Maybe not the dynamic utopia we were expecting. Seems like plain ol’ Reflection with a slightly cleaner syntax.

Bummer…

However, Microsoft was awesome enough to actually allow us to create our own dynamic types by simply inheriting from the DynamicObject class. This post looks at how we can create a semi-dynamic object by handling the binding methods ourselves.

[Source Code: Dynamic.cs]

Our goal is to create an object that we can add, remove and change values on the fly. It ends up being a little easier than we expect. Let’s look at the minimum code we are going to need to get started.

public class SimpleDynamic : DynamicObject {

    //contains the values to use
    private Dictionary<string, object> _Values = new Dictionary<string, object>();

    //handles getting values from our object
    public override void TryGetMember(GetMemberBinder binder, out object result) {
        //...
    }

    //handles assigning values to our object
    public override bool TrySetMember(SetMemberBinder binder, object value) {
        //...
    }

}

Note: If you downloaded the source code above you might be wondering why these examples don’t look the same. The source code is the complete Dynamic class while this post only goes over the basics of using the DynamicObject class.

In order to create our dynamic object we must handle getting and setting values. We can use a simple Dictionary<string,object> to hold our values until we need them.

//handles getting values from our object
public override void TryGetMember(GetMemberBinder binder, out object result) {
    return this._Values.TryGetValue(binder.name, out result);
}

//handles assigning values to our object
public override bool TrySetMember(SetMemberBinder binder, object value) {
    this._Values.Remove(binder.name);
    if (value is object) { this._Values.Add(binder.name, value); }
    return true;
}

Not much code, but now our dynamic object is a little more dynamic than it was before. We can now work with properties a lot easier.

//create the new object
dynamic simple = new SimpleDynamic();

//add and update some values
simple.red = "#f00";
simple.green = "#0f0";
simple.count = 10;
simple.add = new Action<int>(value => simple.count += value);

//and check the values
Console.WriteLine(simple.red); // #f00
Console.WriteLine(simple.green); // #0f0
Console.WriteLine(simple.count); // 10

//invoke methods
simple.add(5);
Console.WriteLine(simple.count); // 15

Of course this class isn’t that helpful since it’s only regular Dictionary without string indexes but it is interesting to see how you can make a ‘dynamic’ object with only a few lines of code.

As for the included source code, it has a few extra features that this class doesn’t have.

  • Use Anonymous Types directly to build out entire objects.
  • Support for the += operator.
  • Enumeration through member names.
  • String indexes (for both single and nested properties).

Here are a few examples the Dynamic.cs class can do.

//create using anonymous types (and nested anonymous types)
dynamic info = new Dynamic(new {
    name = "Hugo",
    age = 30,
    settings = new {
        color = "orange",
        homepage = "website.com"
    }});

//add values using +=
info.create = new { value = false; }
info.nested.value.allowed = 55;

//enumeration of properties
foreach(string prop in info) {
    Console.WriteLine("{0}: {1}", prop, info[prop]);
}

I’d caution against using this a lot in your projects, but it is really interesting to see the kind of control Microsoft has built into the new DynamicObject class.

[Source Code: Dynamic.cs]

Written by hugoware

July 15, 2010 at 12:32 am

Cobalt MVC Preview!

with 3 comments

Lately, I’ve been working a lot on my project Cobalt which is, more or less, jQuery for server side ASP.NET MVC views. I’ve shown some code examples before but today I have a screen cast of some of the code in action.

Watch the video now!

Hopefully, I’ll have some code released in the next week or two, but for now feel free to give thoughts and feedback!

Written by hugoware

May 27, 2010 at 9:09 am

MVC Templating – jQuery Style!

with 3 comments

I’ve messed around with a variety of ways to make MVC templating better but I’ve never really been satisfied with any of my solutions until now.

One of the things that I really like about jQuery is that it allows for better separation of markup from code since you can use CSS selectors to find and change elements on the page. ASP.NET MVC requires server side blocks of code to be mixed into the markup to display content.

The other day I started working on a new project I’ve named Cobalt – A jQuery style, context aware templating framework for ASP.NET MVC. I’m not quite ready to release the code but I wanted to share the idea in advance.

Note: When I say “jQuery Style”, I mean method chaining with css selectors – You can’t use $()… sorry 🙂

Let’s pretend we have the following markup

[Index.aspx (Markup)]

<%@ Page ... %>
<html>
    <head>
        <title>Untitled</title>
    </head>
    <body>
        <h1>Title</h1>
        <div class="content">
            <p></p>
            <ul></ul>
        </div>
        <a id="back" >Previous</a> | <a id="next" >Next</a>
    </body>
</html>

Using Cobalt, we can use jQuery style code to select the correct elements and update content in server side code at the top of the view.

[Index.aspx (Server Code)]


//select tags by their names
this.Find("h1")
    .Text("My Page");

//select tags by name
this.Find("a")
    .Attr("href", this.Url.Action(link.Text())
    .Css(new { color = "#f00" });

//find by ID
this.Find("#back")
    .Remove();

//use other selectors
this.Find(".content > p")
    .Html(this.Model.PostContent);

//select classses and children
this.Find(".content > ul")
    .Remove();

//or perform sub queries
this.Find(".content", content =>
    content.Find("strong")
        .Text(bold.Text().ToUpper())
            .AppendTo(content)
            );

Keep in mind, this code is part of the view and not in a code behind file.

Of course, this view could probably be just as easily updated using a Model and assigning values using server side blocks of code, but consider some of the advantages of keeping our code separated.

  1. Markup stays free of server side code blocks so it is easier to pull the content from external resources (for example a database)
  2. If the markup is constantly being changed by another team member (for example a designer) we can avoid tinkering with the new HTML and instead make changes to our selectors (if even needed).
  3. Many web developers are familiar with CSS selectors – The template could be understood by other team members and developers regardless of programming language choice.

Being Context Aware

The context of a selector is another important part of Cobalt. If you’re working in a UserControl you might changes to stay within the context of the control. At the same time, you might want some of your commands to change other parts of the page.

[SomeControl.ascx]

<@ Control ... %>

<h3>Latest Tweets</h3>
<ul class="tweet-list" ></ul>
<a href="http://www.twitter.com/hugoware" >Follow Me!</a>
<% =this.Html.ActionLink("Rendered Link", "Index") %>

When you use the this.Find(...) command, the actions are queued up and executed only for the calling control. That said, you can use the this.Page or the this.Parent properties to change the context (and scope) of a command.

[SomeControl.ascx (Server Code)]


//context stays inside of the UserControl
this.Find("ul.tweet-list")
    .Html(this.Model.Tweets);

//context stays inside of the UserControl
this.Find("h3")
    .Text("Follow Me!");

//finds both links (static and rendered)
this.Find("a")
    .Attr("href", this.Url.Action("Home"))
    .Text("changed!");

//context changes to the page level
this.Page.Find("head")
    .Append(this.Model.Stylesheet);

//updates the title in the page
this.Page.Find("title")
    .Text("Changed the title!!");

//never finds the title (context is in the control)
this.Find("title")
    .Text("Title not changed!");

Again, this might be a little extra code in some cases, but we are also able to access elements all over the page without needing to apply special WebControls or wire up to the Page life cycle.

It is worth pointing out that both links will be discovered by the a selector since the actions aren’t actually invoked until after the page is rendered. This means you can use MVC like you normally would and still use Cobalt to find and change elements. In fact, as far as I can tell this should work with WebForms as well as MVC, but I’d have to test it before I could say how well it works.

Work In Progress

This project is only a few days old right now but is already coming along very well. I’ll be posting some code soon but till then feel free to share any ideas you have.

Written by hugoware

May 17, 2010 at 11:56 pm

Sienna MVC

leave a comment »

This last week has been a time of mourning for me but I have reached the acceptance phase. Now, that I’m ready to move on I can share some code I’ve been working on.

I’ve blogged a lot before about using WebForms and more recently using WebControls with ControlAdapters in MVC, but for the most part it was just messing around with small sections at a time. Tonight I published my templating framework on GitHub called Sienna.

Before you read further realize that this is not WebForms in MVC. This is WebControls in MVC — or basically the templating and using code behinds for View logic.

I might be alone on this but I am having a hard time of letting go of using code behinds for separating code from the page markup. No matter how simple a view is server side blocks of code are ugly. They are difficult to read and even harder to refactor, which is something that code behind files still excel at.

Sienna makes it so you can create new instances of Pages, assign to properties and then return them as ActionResults. To take it a step further, Sienna uses ControlAdapters to render cleaner markup and avoid the junk IDs that ASP.NET typically spews out… yes, and it drops the ViewState as well 🙂

Let’s look at an example of how Sienna works. Below is a sample page from a blog engine web application.

[/Views/Index/BlogPost.aspx]

<%@ Page Language="C#" 
    CodeBehind="BlogPost.aspx.cs" 
    Inherits="Site.Views.Home.BlogPost" %>
<html>
    <head runat="server">
        <title></title>
    </head>
    <body>
        <h1 id="name" runat="server" />
        <div id="posted" runat="server" class="date" />
        <div id="content" runat="server" />
        
        <form id="commentBox" method="post" action="[controller:Home][action:Comment]" runat="server" >
            <input id="email" runat="server" type="text" _id="email" _name="email" />
            <textarea id="comment" elementname="comment" runat="server" ></textarea>
        </form>
    </body>
</html>

[/Views/Index/BlogPost.aspx.cs]

using System;
namespace Site.Views.Home {

    //displays a blog post
    public partial class BlogPost : System.Web.UI.Page {

        //the post to publish display
        public Post Post { get; set; }

        //display the content
        protected override void OnLoad(EventArgs e) {

            this.Title = this.Post.Title;
            this.name.InnerText = this.Post.Title;
            this.content.InnerHtml = this.Post.Content;
            this.posted.InnerText = this.Post.Created.ToShortDateString();

            //add the classes for the date
            this.posted.AddClass("newest-post");

            //handle showing comments or not
            this.commentBox.Visible = Visitor.IsLoggedIn;

        }

    }

}

This might be a small amount of code but there are a lot of things to explain about it. I’ll focus in some of the more relevant parts.

  • Instead of using the built-in WebControls in ASP.NET we are using regular HTML controls that have been marked to be handled by the server.
  • Because ‘id’ and ‘name’ have special meanings to ASP.NET, we can use _id and _name to define the actual IDs to display, otherwise, no ID is shown. This allows a control to be available to the code behind using one ID and then rendered using another (or none at all). You can also use elementid, displayid or actualid if you do not like using underscores.
  • Attributes like href and src can use a special format that will be parsed by a URL helper in the background at render time. The format is [name:value][otherName:otherValue].
  • Because we use HTML elements for the template, we have access to the InnerHTML and the InnerText properties to assign values. Additionally, Sienna includes additional HTML helper methods for other common functions.
  • This example shows a normal Page as the type but you can use anything that inherits from the Page type — including ViewPages, which will give you access to the Url and Html helpers. If you aren’t using a ViewPage, the UrlHelper and Controller instances are stored in the Page.Items property.

Now, how would you actually use this view from a controller? Well, it is really quite easy…

//shows the latest blog post
public ActionResult Index() {

    //get the content
    BlogPost post = BlogPostRepository.GetLatest();

    //create the view and assign the value
    var view = this.CreatePage<Views.Home.BlogPost>();
    view.Post = post;

    //show the page
    return this.Page(view);

}

And that is it – The PageResult is returned and executed normally and the final result is nice and clean HTML output.

<html>
    <head>
        <title>My Post</title>
    </head>
    <body>
        <h1>My Post</h1>
        <div class="date newest-post" >10/15/2009</div>
        <div>
            <!-- snip... -->
        </div>
    </body>
</html>

You can also use this same approach to assign directly to properties on the page. For a second example, let’s say our code behind actually looked like the following.

using System;

namespace Site.Views.Home {

    //displays a blog post
    public partial class BlogPost : System.Web.UI.Page {

        //the name of the post to show
        public string Name {
            get { return this.name.InnerHtml; }
            set { 
                this.name.InnerHtml = value;
                this.Title = value;
            }
        }

        //the content to display
        public string Content {
            get { return this.content.InnerHtml; }
            set { this.content.InnerHtml = value; }
        }

        //the day the blog post was created
        public DateTime Posted {
            get { return DateTime.Parse(this.posted.InnerHtml); }
            set { this.posted.InnerText = value.ToShortDateString(); }
        }
    }
}

The nice thing about this approach is that the View defines which properties need to be encoded by using the InnerText or InnerHTML properties. It also allows us to assign other types like DateTime but then the View determines how to use it. Depending on how you look at it, this approach replaces the ViewModel by filling both roles at once.

Unfortunately when we create this control the links between the HTML elements and the properties are not immediately available. That doesn’t take place until after the page starts to render. So, to solve this we can assign to the Init event as part of when we create the Page.

//shows the latest blog post
public ActionResult Index() {

    //get the content
    BlogPost post = BlogPostRepository.GetLatest();

    //show the page
    return this.Page<Views.Home.BlogPost>(page => {
        page.Name = post.Title;
        page.Content = post.Content;
        page.Posted = post.Created;
    });

}

And our page is correctly created and rendered into view!

Technically, this whole approach should work with existing WebForms pages by allowing you to use MVC Controllers and Routes and then returning the correct page automatically. Of course, this hasn’t been tested and I really don’t recommend that you use it that way… but still interesting to think about 🙂

Sienna works with MVC projects so you don’t need to start anything over. Simply add the files to your project, generate the Browser definitions file (which is part of the ControlManagement class in Sienna) and off you go!

[Source Code: Sienna]

So go try it out and let me know what you think!

Written by hugoware

May 9, 2010 at 11:08 pm