Using RavenDB in Brown-Field ASP.NET MVC projects
Posted by: Sumit Maitra
in Category ASP.NET MVC
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
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.
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:
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
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.
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
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
The Update method has the same code too because RavenDB internally determines if you are creating a new Document of updating an existing one.
“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
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
The relevant Web.config entries in the <appSettings> sections looks as follows
The Updated BlogControllerFactory constructor with two Repository instances looks like this
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.
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.
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.
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
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.
If data in the View is cached, show user the option to reload the saved version.
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
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.
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.
You can click on the little ‘pencil’ to see the following details
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.
Add the following CSS class to Style.css
It gives the async ‘Saving’ message a yellow background.
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.
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