Using RavenDB in Brown-Field ASP.NET MVC projects

Posted by: Sumit Maitra , on 1/15/2012, in Category ASP.NET MVC
Views: 126768
Abstract: In this article, we will see how RavenDB can be used as Auto-Save cache in an ASP.NET MVC application. We will explore a use case that is typical of brown-field projects (projects involving enhancements to an existing application), where a handy functionality in an ASP.NET MVC application can be achieved by using RavenDB

In this article, we will look at a NoSql database called RavenDB, and explore a use case that is typical of brown-field projects (projects involving enhancements to an existing application), where a handy functionality in an ASP.NET MVC application can be achieved by using RavenDB.

If you are new to NoSql or RavenDB or Document Databases, make sure you read
Hello RavenDB! Introducing RavenDB for .NET Developers. If you are familiar with NoSQL and RavenDB, read on and download our code example.

Let’s take the scenario where you have a nice Purchase Order management system built on ASP.NET MVC. It allows user to create a Purchase Order that has some header data and multiple line items. So the Purchase order with it’s multiple line items is typically saved in RBDMS through a Master-Detail relationship. But from an Object graph point of view, PO is a single document containing a header and multiple line items.

 

Now let’s say users come back saying – “I have to create POs with 100 line items and sometime I forget to hit save before I go for coffee. When I come back I lose all my data because the page timed out when I was on item 51”.

Telling the user to Save often is probably the most common response. But if we think for a moment how Google does it for emails or their documents, Google almost never loses data due to time-outs. You will find most of your data in a ‘draft’ state. Another example is for people who have used WordPress to blog from their site. You will often notice WordPress telling you that you have a ‘more recent’ version of the draft available, do you want to switch?

That little bit extra functionality goes a long way in making our applications that much more usable.
Going back to the PO example, implementing Auto Save in the RDBMS system could be a problem because of multiple reasons: 

Now let’s say users come back saying – “I have to create POs with 100 line items and sometime I forget to hit save before I go for coffee. When I come back I lose all my data because the page timed out when I was on item 51”. Telling the user to Save often is probably the most common response. But if we think for a moment how Google does it for emails or their documents, Google almost never loses data due to time-outs. You will find most of your data in a ‘draft’ state. Another example is for people who have used WordPress to blog from their site. You will often notice WordPress telling you that you have a ‘more recent’ version of the draft available, do you want to switch? That little bit extra functionality goes a long way in making our applications that much more usable. Going back to the PO example, implementing Auto Save in the RDBMS system could be a problem because of multiple reasons: 

 

  • The schema and overall logic changes to save versioned data in the RDBMS system will be non-trivial
  • There might be validation checks that fail because users kept didn’t fill out some fields at that point.
  • Making periodic (30 second) transactional updates to any live system is not good for overall performance.

A work around would be saving your Object Model to RavenDB directly and if user visits the document after a time out, load both Transactional Data and Object data, compare the timestamp and use the freshest set of data.

In this article, we will use a simpler example for brevity. We will use the Blog sample similar to the one we saw in our SignalR app (see
ASP.NET MVC 3 Real Time Collaborative Apps with SignalR) and implement an Auto-Save mechanism that will save the draft versions of the blog in RavenDB.

Note: A Blog is a good example of what could potentially be handled by RavenDB end-to-end in a green-field project. We use it in our sample to keep the size of the app and article manageable. Think of Blog, in the sample, to be a more complex document like a Purchase Order.

Extending a Blog to use RavenDB as an auto save cache

Since this is a brown-field project to which we are adding a feature, we will not start off with a new solution. Instead we start with this code. This code uses Entity Framework CF as its O-RM layer and SQL Express as it RDBMS. It is implemented using ‘Poor Man’s DI’ pattern (I have not used a DI container, that’s for another day).

The Class Diagram of the Domain Objects is as follows
business-model
As we can see, it basically consists of just one class and an Enum at the Domain level. However if you look at the Database diagram, it is a little more involved.

data-diagram

The requirement is that we should be able to group articles by Tag and one article can have many tags. A classic many-to-many relationship saved through a join table as required. For now we just keep PostStatus out of any ‘master’ data.

Also if you look at the Object Relational Map of the table structure, it looks as follows:

object-model

Current Layering of Code

The existing project code is layered as follows:

RavenDbHelloWorld.Data – The data layer responsible for persistence and fetching of data
RavenDbHelloWorld.Domain – The domain layer responsible for managing business logic. Currently this is mostly a pass-through layer with respect to business logic. However it is the container for the Domain entities.
RavenDbHelloWorld.View – The view layer responsible for rendering the web views. It interacts with the Domain layer and the domain layer ONLY. It is completely data storage technology agnostic.

All code is based on abstract base classes and concrete implementations are configured through web.config. It could potentially call the Domain repository directly, but we have wrapped it through a service layer. The service layer has a little bit of business logic that says show only blogs in Published state to users who have not logged in.

With the current status of the application out of the way, let’s look at extending it with the auto-save functionality. From this point onwards, we’ll follow along the code changes step by step.

Installing Raven DB using NuGet

Bring up the Package Manager Console and select the Default Project: as RavenDbHelloWorld.Data” and type the following to install RavenDB

PS > install-package RavenDB

RavenDB package is about 20MB so it may take a while to install.

Starting the RavenDB Server

Once installation is complete, open command prompt and navigate to the packages directory of your project. Now change directory into the RavenDB.x.x.xxx\server folder. Replace the x with actual version number.

There is just one exe in the directory, Raven.Server.exe. Type the following to start the server

C:\...\server\Raven.Server.exe

You will get a bunch of console messages indicating RavenDB server has started.

Start IE and navigate to
http://localhost:8080/. You will be greeted with RavenDB’s administration page. It’s a Silverlight application so if you don’t have Silverlight plugin, you need to install it first.

new-raven-db-admin-home
 

Adding RavenDBRepository to the Data Layer

Add a new class RavenBlogPostRepository.cs in RavenDbHelloWorld.Data project. Inherit it from BlogPostRepository (declared in the RavenDbHelloWorld.Domain project). Add the following class level field

DocumentStore _store = null;
(DocumentStore is in Raven.Client.Document namespace)

Add the following constructor

Constructor

DocumentStore is defined in the Raven.Client.Document namespace. As the name implies it represents the logical document store layer for RavenDB.

Implement the Create method as follows

RavenDB Create


The Update method has the same code too because RavenDB internally determines if you are creating a new Document of updating an existing one.

RavenDB Update

“Look Ma No Mapping”


If you look at the above two methods you will see these are different from the SqlBlogPostRepository versions in the sense there is a lot of work done to map the Domain object into RDBMS friendly entities. In RavenDB you are directly taking the domain object and saving it.

So here RavenDB delivers on promise of NoSql (or no O-R mapping).

To read from RavenDB implement the read method as follows

RavenDB Read

As you will see we are using LINQ directly to query into RavenDB and RavenDB is able to return us the exact object without need for additional mapping. This is about all the code we need to persist our domain objects into RavenDB.

Since the functionality doesn’t really affect business logic (everyone who edits should have the auto save functionality) there are no changes in the Domain layer.

Understanding the layering


A part of good coding practices mandates that we should not be programming against concrete classes. As a result the only business/domain class that can be new-ed from the Controller layer is the ProductService. However product service takes a parameter of type BlogPostRepository.BlogPostRepository is an abstract class and it’s concrete classes exists in the Data layer. The concrete instances are hence created via reflection and ONLY in the CompositionRoot class.

The CreateControllerFactory(…) method is responsible for creating the repository instances and wiring up the controller factory. To move further with our implementation we need to update the CompositionRoot class to generate two repositories and pass them to the BlogControllerFactory. The modified CreateControllerFactory() method looks as follows

CreateControllerFactory

The relevant Web.config entries in the <appSettings> sections looks as follows
 
image

The Updated BlogControllerFactory constructor with two Repository instances looks like this

BlogControllerFactor

Changes to enable Asynchronous and Automatic save to RavenDB

Updates to BlogController.cs

With the factories and composition root updated, we are now ready to update the View and the Controller. Update the BlogController constructor to receive two repository instances and save them.

image

Update the Get Edit action method to get both the RDBMS instance and the Cached instance from RavenDB. If the instance from RavenDB exists and has a newer timestamp send it back to the View with the ‘IsCached’ flag set to true indicating the view is showing a cached version. Else pass the instance loaded from SQL Server.

Get Edit action method

Add an action method that doesn’t return a cached object. We will use this to retrieve the SQL server version if the user desires so.

image

Finally add a HTTP Post Method to save to RavenDB. This one returns a JsonResult because if will be an Async Postback and we don’t want the page to refresh for every AutoSave

HTTP Post Method

Changes to Edit.cshtml


With the Controller all setup all we need to do is finish off the client side. Add a floating element that will show up when AutoSave is in progress and also show the AutoSave status message.

AutoSave css

If data in the View is cached, show user the option to reload the saved version.

image

Finally the Javascript to periodically trigger AutoSave

ravendb-autosave

Let’s go through it line by line

- On load of the page

  • Hide the AutoSave message container
  • Declare a variable lastTimeOutId = -1. This will be used later to prevent multiple timers being set
  • Wireup the keyup event of the BlogPost text area such that 30 seconds after user starts typing set the timeout. When the timeout happens, the saveToCache method is called.

- The saveToCache function does the actual post back by calling the SaveCache Action method.

  • Show the autoSaveContainer with the ‘Saving…’ message.
  • We use the autoSaveContainer to load the Json result of the postback.
  • refreshForm is our callback method that will be called once the ActionMethod returns.

- refreshForm – Callback does the following

  • It resets the lastTimeOutId so that if user continues to type the timeOut is set to fire 30 seconds later again. This way the minimum interval between two auto saves is 30 seconds.
  • Sets another timeout that hides the Auto Save Status message after two seconds.


All set. Build and Run the app.

  • Navigate to /Blog url
  • Create a New Blog, set the status to Published and Save.
  • Now Click on Edit.
  • Change some text and wait 30 seconds. Notice the “Saving” and “Saved successfully” messages on top right corner.
  • Now without hitting the save button go back to index page or let the page timeout.
  • You will see the old content on the Index page
  • Click Edit. You will a message like the following. Notice you will see the last modified text in the Blog Content instead of the saved version

reload-auto-saved-version


  • Click on the link to load the Saved Version
  • Voila! You now have Auto Save functionality in your own application.
  • You can be creative and use the auto save version for accidental deletes as well because the entire object is still in RavenDB.
Notes
Post Build Events

If you go to the build events of the View web poject (Right click on solution explorer and select Properties), you will notice the following three lines.

These lines are required because we were so clean with our references, that they don’t get copied over automatically and hence are not available at debug time. So to make the dependent dlls available at runtime, the following scripts are necessary to copy them over.

copy "$(SolutionDir)RavenDbHelloWorld.Data\bin\$(ConfigurationName)\RavenDbHelloWorld.Data.dll" "$(TargetDir)
copy "$(SolutionDir)RavenDbHelloWorld.Data\bin\$(ConfigurationName)\EntityFramework.dll" "$(TargetDir)
copy "$(SolutionDir)RavenDbHelloWorld.Data\bin\$(ConfigurationName)\Raven.*.dll" "$(TargetDir)

RavenDB Admin Page

After an AutoSave has executed you can go back to the RavenDB dashboard and see the object for yourself.

ravendb-admin-document

You can click on the little ‘pencil’ to see the following details

ravendb-document-home

If you click on the Metadata tag you will see the meta information that RavenDB stores to be able to re-create the object while reading from the database.

ravendb-document-meta
 

CSS Changes
Add the following CSS class to Style.css

image

It gives the async ‘Saving’ message a yellow background.
Using Aspnetdb

To show the business logic of only registered users being able to see draft versions, this application uses standard asp.net forms authentication. It expects aspnetdb to be available in your SQL Store. If you don’t have it, you can either install it by running <Windows
Drive>:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regsql.exe
or just make sure each time you publish you set the status to Published, else all your new blogs will keep vanishing from the Index page
.

Conclusion

In conclusion, we saw how RavenDB can be used as Auto-Save cache in an ASP.NET MVC application. Other similar technologies include MongoDB, CouchDB, Redis and Memcache. Though Redis and Memcache are more of in-memory caches with serialization abilities. If you are looking at speeding your page reloads and intend to use caches, Redis and Memcache are arguably faster options.

In the end, RavenDB is true blue .NET built Document Database. I kind of like the sound of that :).

How about you? Do let us know what you think you can use RavenDB in ASP.NET for.

The entire source code of this article can be downloaded
over here

Give me a +1 if you think it was a good article. Thanks!
Recommended Articles
Sumit is a .NET consultant and has been working on Microsoft Technologies since his college days. He edits, he codes and he manages content when at work. C# is his first love, but he is often seen flirting with Java and Objective C. You can follow him on twitter at @sumitkm or email him at sumitkm [at] gmail


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by yeurch on Tuesday, January 17, 2012 8:40 AM
Hi Sumit,

While I think that this article provides a great introduction to using RavenDB (not to mention a great brown-field use case for the technology), I found that there were several problems with the initial downloaded solution.

Firstly, the BlogPostService class does not have a Read method (which you use in your Edit method above).  To make it work, I've just called Read on the repository directly.
Secondly, and perhaps most importantly, the final line of your post-build steps was not present in the downloaded version, resulting in an exception occurring when trying to instantiate the cache blog repository.  I think you should have included editing the post-build step as a task in your tutorial, before the step of running / testing the results.

Now, a question for you: would you say your approach beats using a cache solution like memcached, and if so, why?

As I say though, it's a very good introduction to using RavenDB, so thanks very much for it!
Comment posted by Sumit on Wednesday, January 18, 2012 1:56 AM
@Yuerch - Apologies for the gaffes and thanks for pointing them out. Your workaround were perfect. A new code version is being uploaded that will take care of the missing method and the build event change.

Next, I am not in a position to answer 'better-than-memcached-or-not' question rightaway because I have not done enough due-diligence with memcached. But I will definitely take up the challenge and maybe in a followup article do a memcached+ASP.NET scenario and then look at the differences. Will quote you for the inspiration :-). Till then folks at ServiceStack have a good comparison http://www.servicestack.net/mythz_blog/?p=474

Thanks again, for following through with the article and your comments.
Comment posted by Sumit on Wednesday, January 18, 2012 2:00 AM
@Yeurch The service stack article is between Redis and RavenDB ONLY. Looks like I gotta get to a comparison with Memcache soon enough ;-)
Comment posted by charlie on Friday, July 27, 2012 11:42 AM
not sure if you are aware but the first 3 parargraphs starting with 'Now let’s say users come back saying' repeat themselves
Comment posted by Admin on Monday, July 30, 2012 5:15 AM
Fixed the issue charlie. Thanks!

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel