Click here to Skip to main content
15,881,803 members
Articles / Web Development / IIS

Introducing the Rabbit Framework and its Dynamic Idea

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
23 Mar 2011CPOL9 min read 24.3K   163   14  
Rabbit Framework is a new lightweight framework for building web sites using ASP.NET Web Pages / WebMatrix. This article describes one interesting idea out of many found in the framework.

Introduction

Rabbit Framework is a lightweight framework for building dynamic web sites using ASP.NET Web Pages. I created it to test out a few interesting ideas, such as Event Driven, the Sinatra Style MVC, Monadic Model, Use Dynamic Everywhere, and Unit Test with Mocking. In fact, it is so interesting that I cannot help hiding the ideas. I published the project on CodePlex and the source code on github.

This article describes the idea of using dynamic everywhere, which includes using dynamic as class references, using ExpandoObject as DTO and using DynamicObject for mocking.

Background

Traditionally, ASP.NET web development is only done through ASP.NET Web Forms technology. Recently, Microsoft introduced two new stacks for web development. They ASP.NET MVC and ASP.NET Web Pages framework. The ASP.NET Web Pages framework is the most interesting in my view.

The ASP.NET Web Pages framework was introduced along with the WebMatrix, a tool clearly targeted more at beginners as opposed to doing hardcore development. Scott Gu warns the readers in his blog post when he was introducing WebMatrix.

If you are a professional developer who has spent years with .NET, you will likely look at the below steps and think – this scenario is so basic - you need to understand so much more than just this to build a “real” application. What about encapsulated business logic, data access layers, ORMs, etc? Well, if you are building a critical business application that you want to be maintainable for years, then you do need to understand and think about these scenarios. (1)

This made me ignore the WebMatrix and the ASP.NET Web Pages framework for a few months until I started to play the Razor pages using Visual Studio. At that moment, I found out that the ASP.NET Web Pages framework is a simple, lightweight yet powerful ASP.NET technology, not just a part of WebMatrix.

ASP.NET Web Pages framework gives users a simple and powerful new way of writing ASP.NET apps. It is different from WebForms as it doesn’t use server controls. It is also different from MVC as it doesn’t follow the MVC pattern. Instead, it follows a much simpler ‘inline page’ model, where a page is basically an HTML page with some code added where needed. In that sense, it is reminiscent of Classic ASP, but it is also very different in the sense that it has the full power of the .NET framework available behind it. It also supports concepts like layout pages which make it much more flexible than the Classic ASP.(2)

Back to Scott Gu's blog post, in fact in the comments, he made it clear later on:

From an application performance perspective, it is the same ASP.NET and so performance is very good. The main thing around scale of projects will be around architecture and code organization. It is easier to get into trouble in a world without clean separation of concerns (which both MVC and WebForms can help with). That isn't to say you can't build good very, very large applications with a single-file approach, nor that you can't build bad apps with a framework that pushes a more separated architecture. (1)

It means if we can have a good architecture and code organization, the applications built on the ASP.NET Web Pages framework will perform as good as Web Forms and MVC.

This encouraged me to explorer more and created a set of tools to form a framework for building web sites using ASP.NET Web Pages. I called it Rabbit framework, because rabbits are light, fast and delicate. And this year is the year of rabbit.

Using the Code

You can open the sample web site in WebMatrix or any version of Visual Studio and run it as a demo. You can also download it as a NuGet package into your existing web site. You are welcome to send feedback on CodePlex and fork the source code on github.

The Rabbit Framework is an experiment at this stage. It is not production ready yet. Use it at your own risk.

Points of Interest

The ASP.NET Web Pages is a web technology stack built on top of the .NET Framework. In .NET Framework 4, the most impressive new feature is dynamic. Rabbit Framework uses dynamic everywhere, which drastically simplifies the code and testing.

Based on the separation of concerns principle, applications usually follow layered design. There are presentation layer, service layer and data access layer. ASP.NET MVC suggests to put the validation in a service layer and to implement repository in a data access layer. It seems that ASP.NET MVC calls the data that are pushed into the view as Model. To me, the Model is the combination of the service layer and data access layer behind it. The data passed from controller to view are so called Data transfer objects (DTO) as per domain driven design (DDD).

In Rabbit Framework,

  • The new dynamic keyword of C#/.NET 4 is used when referencing classes in other layers.
  • The ExpandoObject class is the type of DTO.
  • The DynamicObject class is used to build mock objects.

Using Dynamic Keyword

In a typical layered architecture, classes in presentation layer use classes in service layer. Classes in service layer use classes in data access layer. Whenever a class A uses a class B, A depends on B. A and B are coupled. A won't work without B.

The dependency between a class A and a class B is strong, when A only works with B. The dependency is weak, if A works with an interface, which is implemented by B. The weaker dependency makes A and decoupled.

E.g. This is coupled and strong dependency.

C#
public class Model
{
    public Repository Repository {get; set;}
}

This is decoupled and weak dependency.

C#
public class Model
{
    public IRepository Repository {get; set;}
}

The benefits of decoupling the dependency is that it provides code reusability. We can have different implementation of the interface.

C#
public class SqlRepository : IRepository
{
}

public class JsonRepository : IRepository
{
}

The Service class become reusable to either SQL repository or JSON repository without change. Where in coupled scenario, it is impossible.

The technique to decouple classes is to use interfaces. This is well known and widely used in statically typed programming language, such as C++, Java and C# (prior to 4.0). While it weakened the dependency between classes, it also introduced more artifacts to the source code system. There could be a number of interfaces required and need management.

Back to the age of C/C++, you write the functions and classes in .cpp files. You also have to declare the functions and classes in separated .h files. With Java/C#, this is not required. Therefore things get simplified. We can save our effects to write code other than to manage the code artifacts .

When C# 4.0 introduced dynamic, the game to fight coupling could change further more. We can get rid of the interface by using dynamic.

E.g. Repository is referenced by dynamic.

C#
public class Model
{
    public dynamic Repository {get; set;}
}

The dynamic keyword essentially disables the C# compiler's compile time type checking to make it so called dynamically typed. This does not change the strongly typed nature of C#/.NET. It means if you did not assign the correct object, the compiler won't complain. But it will cause error at run time.

Without interfaces, we saved more effects coding and have less things to manage.

The compile time type checking is an advantage of statically typed language. It prevents invalid code. This is a tradeoff. We trade the compile time type check and refactoring with simplicity and extensibility.

To ensure the objects are correctly referenced, unit testing are important. Thankfully, unit testing based on dynamic is much simplified.

C#
[TestMethod]
public void SaveAs_Should_Validate_New_Id_Exists()
{
dynamic data = new ExpandoObject(); //create a DTO
data.Id = "old-id";

var repository = new Mock();
repository.Setup("Exists", new object[] { "new-id" }, true); //tell model new id exists

var model = new PageModel();
model.Repository = repository; //push mock repository to model 

model.SaveAs(data, "new-id"); //trigger the model 

Assert.IsTrue(model.HasError); //make sure model report error
repository.Verify(); //make sure reporsitory.Exists is called
}

The example code above shows that it is easy to push a mock repository to the model.

Using ExpandoObject as DTO

Ruby on Rails uses ActiveRecord in Model. ASP.NET MVC uses Entity Framework as repository and the Entity as Model, the M of MVC. Rabbit Framework uses ExpandoObject as DTO.

In Rabbit Framework, controller calls model. Model calls repository. Model is a service façade and unit of work container. Repository provides identity map and in memory cache. The references between each other are dynamic. The DTOs are .NET dynamic objects, the ExpandoObject. This design makes it very flexible, extensible and testable.

Here is an example of how controller passes DTO to view by using ExpandoObject.

In controller,

C#
dynamic model = new ExpandoObject();
model.Message = "hi";
@RenderPage("View", model)    

In view,

C#
@Page.Message    

This is the same idea as someone probably has tried with ASP.NET MVC.

ASP.NET
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>

ASP.NET MVC has strongly typed view. It also has a ViewData dictionary to pass extra and dynamic data. In MVC 3, the ViewBag property exposes a dynamic object to pass late-bound data from a controller to a view. If the model is dynamic, there is no need to have an extra ViewBag.

In the data access layer, the Massive project is a good example of using dynamic for persisting data.

Mock, Stub and Fake, All in One

Since Rabbit Framework embraces dynamic everywhere as possible. Mocking became very easy. The example below shows a test that does a few things:

  • Push mock repository to Model
  • Setup expectations
  • Trigger model execution
  • Verify to ensure expectation is met
C#
[TestMethod]
public void SaveAs_Should_Validate_New_Id_Exists()
{
    dynamic data = new ExpandoObject(); //create a DTO
    data.Id = "old-id";
    var repository = new Mock();
    repository.Setup("Exists", new object[] 
		{ "new-id" }, true); ////tell the model new id exists

    var model = new PageModel();
    model.Repository = repository; //push the mock to model 

    model.SaveAs(data, "new-id"); //trigger the model 
  
    Assert.IsTrue(model.HasError); //make sure model report error
    repository.Verify(); //make sure reporsitory.Exists is called
}

This is the SaveAs function to be tested.

C#
public PageModel SaveAs(dynamic item, string newId)
{
Assert.IsTrue(item != null);
var oldId = item.Id as string;
Value = item;
Value.Id = newId;
Validate();
if (HasError) return this;
 
if (oldId != newId)
{
if (Repository.Exists(newId))
{
Errors.Add("Id", string.Format("{0} exisits already.", Value.Id));
}
else
{
Repository.Delete(oldId);
Value.Id = newId;
}
}
 
if (!HasError) Repository.Save(Value.Id as string, Value);
return this;
}

Mock Implementation

Mocking an interface is difficult. It requires emitting classes dynamically, like what Moq does. Mocking a concrete class is more difficult.

Mocking using dynamic object is very easy. The Mock class in Rabbit Framework probably is the world's simplest mock implementation. 160 Lines in total. No dependency to any 3rd party library.

The Mock class is derived from the DynamicObject. It allows to:

  • Verify the sequence of methods to be called
  • Verify the times of methods to be called
  • Verify the parameter count being passed to methods
  • Verify each parameter's type being passed to methods
  • Verify each parameter's value being passed to methods
C#
public class Mock: DynamicObject
{
    List<Expectation> expectations = new List<Expectation>();
    
    public void SetupGet(string name, object value)
    {
        //setup property getter access expectation   
    }

    public void SetupSet(string name, object value)
    {
        //setup property setter access expectation    
    }

    public void Setup(string name, object[] parameters, object returnValue = null)
    {
        //setup property method access expectation    
    }

    public void Verify()
    {
        //verify records against expectations
    }
    
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        //record property getter access
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        //record property setter access
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, 
	object[] args, out object result)
    {
        //record method
    }
}

Listed below are test cases for the Mock object. All cases have ExpectedException attribute, because the Mock object is expected to catch all the scenarios.

C#
[TestClass]
public class MockTest
{
    /// <summary>
    /// Method1 is not called
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockMethodNotRun()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsAny<int>() }, 10);
        mock.Verify();
    }

    /// <summary>
    /// Method1 has been called already. Add more setup.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockMethodCalled()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsAny<int>() }, 10);
        Assert.AreEqual(10, mock.Method1("a", -2));
        Assert.AreEqual(10, mock.Method1("b", -2m));
        mock.Verify();
    }

    /// <summary>
    /// Method1 is called w/ 0 parameters, expected: 2.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterCount()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", 2.5m }, 10);
        Assert.AreEqual(10, mock.Method1());
        mock.Verify();
    }

    /// <summary>
    /// Method1 parameter #2 type failed, expected: IsNotNull, actual [null].
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterNotNull()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsNotNull() }, 10);
        Assert.AreEqual(10, mock.Method1("a", null));
        mock.Verify();
    }

    /// <summary>
    /// Method1 parameter #2 type failed, expected: IsNull, actual 2.
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterNull()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsNull() }, 10);
        Assert.AreEqual(10, mock.Method1("a", 2));
        mock.Verify();
    }

    /// <summary>
    /// Method1 parameter #2 type failed, expected: System.Int32, actual System.Decim
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterType()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", It.IsAny<int>() }, 10);
        Assert.AreEqual(10, mock.Method1("a", -2m));
        mock.Verify();
    }

    /// <summary>
    /// Method1 parameter #1 value failed, a:System.String, actual: b:System.String
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockParameterValue()
    {
        dynamic mock = new Mock();
        mock.Setup("Method1", new object[] { "a", 2.5m }, 10);
        Assert.AreEqual(10, mock.Method1("b", 2.5));
        mock.Verify();
    }

    /// <summary>
    /// set_Prop1 parameter #1 type failed, expected: 
    /// System.String[], actual System.Int32
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockPropertyType()
    {
        dynamic mock = new Mock();
        mock.SetupSet("Prop1", It.IsAny<string[]>());
        mock.Prop1 = 5;
        mock.Verify();
    }

    /// <summary>
    /// set_Prop1 parameter #1 value failed, expected: 
    /// 5:System.String, actual: 5:System.Int32
    /// </summary>
    [TestMethod]
    [ExpectedException(typeof(UnitTestException))]
    public void TestMockPropertyValue()
    {
        dynamic mock = new Mock();
        mock.SetupSet("Prop1", "5");
        mock.Prop1 = 5;
        mock.Verify();
    }
}

Conclusion

Dynamic is a new and interesting feature in C#/.NET 4. Using dynamic to trade the compile time type checking with code simplicity is open for debate. But the idea of getting rid of interfaces (and possible generics) is big. It is a topic worth more research, because dynamic is not just tied to Rabbit Framework. It applies to all other forms of development.

If this article had your attention on using dynamic, you can use Rabbit Framework for your own experiments. You are also welcome to share your thoughts on CodePlex and fork the source code on github.

History

  • First version, March 2011, correspondent Rabbit Framework version V0.3.0.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --