Using AppHarbor, Bitbucket and Mercurial with ASP.NET and Silverlight – Part 2 CouchDB, Cloudant and Hammock
After having created a hosted ASP.NET and Silverlight application on AppHarbor we’ll take it a bit further by adding cloud storage.
There are many ways to add storage but in this tutorial the application will be provisioned with a CouchDB database hosted by Cloudant.
- First go to AppHarbor and provision your application by clicking “View Available add-ons” on the application’s page.
- As you can see there are a lot of services that can be added to your application. Select “Cloudant”.
- The Oxygen version is free and will give you 250MB of online storage. There are paid plans as well but 250 MB will do nicely for now so click the Add button. If everything went right the browser will navigate to the application’s page and at the bottom you can see that your application has been configured to use Cloudant.
- Before we add database logic to our application we’ll set up a web service first. This web service will called from within the Silverlight application. Right-click the DemoApp.Web project and select Add | New Item
- I named my Silverlight-enabled WCF Service DemoService:
- Here is the code I put in the service:
using System.ServiceModel; using System.ServiceModel.Activation; namespace SLDemo.Web { [ServiceContract(Namespace = "demoservice")] [SilverlightFaultBehavior] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class DemoService { [OperationContract] public void AddToDatabase(string key, string value) { } [OperationContract] public string GetFromDatabase(string key) { return ""; } } }
- Compile the application. Right-click on to the Silverlight project and select Add Service Reference… and click Discover.
- I named the Namespace DemoServiceReference:
- Click OK. Then add an extra button to MainPage.xaml:
<button name="setTimeButton"></button> <button name="getTimeButton"></button>
- I wrote the code for the click handlers like this:
using System; using System.Windows; using System.Windows.Controls; namespace SLDemo { public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } private void setTimeButton_Click(object sender, RoutedEventArgs e) { var proxy = new DemoServiceReference.DemoServiceClient(); proxy.AddToDatabaseCompleted += proxy_AddToDatabaseCompleted; proxy.AddToDatabaseAsync("time", DateTime.Now.ToLongTimeString()); } void proxy_AddToDatabaseCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) { Dispatcher.BeginInvoke(() => { if (e.Error != null) { timeTextBlock.Text = e.Error.Message; } else { timeTextBlock.Text = "Time sent to service."; } }); } private void getTimeButton_Click(object sender, RoutedEventArgs e) { var proxy = new DemoServiceReference.DemoServiceClient(); proxy.GetFromDatabaseCompleted += proxy_GetFromDatabaseCompleted; proxy.GetFromDatabaseAsync("time"); } void proxy_GetFromDatabaseCompleted(object sender, DemoServiceReference.GetFromDatabaseCompletedEventArgs e) { Dispatcher.BeginInvoke(() => { if (e.Error != null) { timeTextBlock.Text = e.Error.Message; } else { timeTextBlock.Text = e.Result; } }); } } }
- You can now run the application and click the buttons to test the web service.
- If you deploy the application to AppHarbor as it is right now you will be using an invalid endpoint address for the server and the deployed Silverlight application will not be able to connect to the web service. Let’s fix that.
- Open the ServiceReferences.ClientConfig file and add another endpoint like this:
<endpoint address="http://demoservice.apphb.com/DemoService.svc" binding="customBinding" bindingConfiguration="CustomBinding_DemoService" contract="DemoServiceReference.DemoService" name="Deployed_DemoService" />
- Note that I renamed the original endpoint and that my address will be different from yours. You can check the correct address by looking at the application’s page in AppHarbor.
- Next change the code that creates the proxy like this:
private void setTimeButton_Click(object sender, RoutedEventArgs e) { #if DEBUG var proxy = new DemoServiceReference.DemoServiceClient("Local_DemoService"); #else var proxy = new DemoServiceReference.DemoServiceClient("Deployed_DemoService"); #endif proxy.AddToDatabaseCompleted += proxy_AddToDatabaseCompleted; proxy.AddToDatabaseAsync("time", DateTime.Now.ToLongTimeString()); }
- Do this for the other proxy creation code too or, even better, refactor the proxy creation into a function.
- Check whether the application is still working locally (it should)
- Commit this new version in the ToroiseHG Workbench and push it to BitBucket (see the first post in this series to see how to do that)
- If all is well the application should build in AppHarbor and be deployed so you can test it in the cloud. Because AppHarbor compiles the Release version of the application, the correct web service endpoint is selected.
- To be able to test the CouchDB code locally we’ll need a CouchDB on our development machine. I tried to install the CouchDB Windows versions but I ran into some blocking issues probably due to the fact that the installers are still very young. Fortunately I found CouchBase. CouchBase provides an implementation of CouchDB. Just download a version fitting for your PC and install it. I picked the “Couch Single Server”
- A CouchDB is not a relational database. It is more like Azure’s table storage but even simpler. You add objects to the storage instead of rows. Objects need to have an Id and must be serializable. You can read more about the document database features of CloudDB here
- To access the database in .NET we have several options. The one I picked was relax-net also known as: RedBranch.Hammock Because CouchDB is normally accessed using a REST API most .NET middleware are simple wrappers. Hammock is no different. At first I downloaded the Hammock binaries but when I ran into a bug in Hammock I had to download the sources and make a custom build of Hammock.
- Download the Hammock sources and build I made here Note that this is just a build that is fixed for Cloudant. This issue has been submitted to the RedBranch.Hammock team and hopefully they will pick it up.
- In Visual Studio, create a Folder in the DemoApp.Web project called Lib. This folder will contain all assemblies that the web application is dependent on and are not part of the .NET Framework. By putting them into this folder and marking them with “Copy to output” the assemblies will be deployed and available on AppHarbor. If you do not add the assemblies to this folder they will not be a part of the files in version control and not be uploaded to BitBucket and AppHarbor.
- Copy the DLLs from the zip with Hammock to the Lib folder.
- Open a command prompt in the folder that contains the solution. Execute the following command: subst S: DemoApp.WebLib
- This maps the Lib folder to a virtual S: drive. The reason to do this is that the Visual Studio projects will contain an absolute path to the dll’s and when the sources are downloaded to multiple computers these paths will be different. The subst will fix that. I even created a little batch file that does this for me and put it in the solution folder.
- In Visual Studio right-click the Lib folder and select Add | Existing items and add the Hammock and JSON dll’s to the project. Make sure you mark them both as “Copy to output”.
- Right-click the DemoApp.Web project and select “Add Reference”. Click Browse and navigate to the S: drive and add both dll’s.
- Change the code of the web service to make it store the string in the database like this:
using System.Linq; using System.ServiceModel; using System.ServiceModel.Activation; using System.Security.Cryptography.X509Certificates; using System.Net.Security; using RedBranch.Hammock; using System.Configuration; using System.Net; using System; namespace SLDemo.Web { [ServiceContract(Namespace = "demoservice")] [SilverlightFaultBehavior] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class DemoService { [OperationContract] public void AddToDatabase(string key, string value) { var session = CreateSession(); SomeEntity entity; try { entity = session.Load(key); } catch (CouchException exception) { if (exception.Status == 404) { entity = new SomeEntity { Id = key }; } else { throw; } } entity.SomeProperty = value; session.Save(entity); } [OperationContract] public string GetFromDatabase(string key) { var session = CreateSession(); var doc = session.Load(key); return doc == null ? "" : doc.SomeProperty; } private string DatabaseName { get { return ConfigurationManager.AppSettings["DATABASE_NAME"]; } } private Session CreateSession() { ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate; var connection = new Connection(new Uri(ConfigurationManager.AppSettings["CLOUDANT_URL"])); if (!connection.ListDatabases().Contains(DatabaseName)) { connection.CreateDatabase(DatabaseName); } return connection.CreateSession(DatabaseName); } bool ValidateServerCertificate( object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { //if (sslPolicyErrors == SslPolicyErrors.None) return true; //return false; } class SomeEntity : Document { public string SomeProperty { get; set; } } } }
- This code defines a class SomeEntity that is a Document that will be stored in the database. The CreateSession method connects to the database ignoring missing trusted certificates. The URL to the Cloudant database and the name of the database are stored in web.config file
- Add to the web.config the settings for the database connection. Note that these settings that will be used while running the application locally:
<?xml version="1.0"?> <configuration> <appSettings> <add key="CLOUDANT_URL" value="http://127.0.0.1:5984"/> <add key="DATABASE_NAME" value="testdb"/> </appSettings>
In the next part of this series we’ll add SQL Server to store authenticated users and we will secure the web service and allow users to login.