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>
11 comments
[…] Using AppHarbor, Bitbucket and Mercurial with ASP.NET and Silverlight – Part 2 CouchDB, Cloudant and… – Erno de Weerd continues this series of posts looking at building an application to host in the cloud using AppHarbor, ASP,NET, Silverlight, etc. This part looks at storing data in the cloud using hosted CouchDB […]
The Morning Brew - Chris Alcock » The Morning Brew #977
Step 13. Your html editor eat your code 🙂 I am trying to deploy solution with wcf service and silverlight app and not sure what I need put in endpoint address. How I can access to svc file?
Yaroslav
Yaroslav, I made the html editor spit it out again 🙂 Let me know if you need help.
Erno de Weerd
yeah, can you help me. Actually I have solution with 3 parts: webapplication (with silverlight app), silverlight project, wcf service. I trying access to wcf service using in endpoint myappadres/service.svc but can’t access…
Yaroslav
Are you trying to connect locally or when the app is deployed? Locally should be as simple as adding a service reference and clicking Discover because it is in the same solution. To connect when deployed on appharbor you need two things: 1. the address of the service and 2. you’ll need to switch between debug (local) and release (deployed) It’s all in step 13, 14 and 15.
Erno de Weerd
I have this issue on appharbor, locally all fine (with endpoint for localhost:1509). For appharbor I am using endpoint http://myhost.apphb.com/service.svc and I can’t access this service . I think the problem is that http://myhost.apphb.com is url to my web app not wvf service…
Yaroslav
Did you try to access it by simply trying to browse to the svc file?
Erno de Weerd
Yeah I tried browse locally (it works), and to apphabor links (doesn’t work, 404)
Yaroslav
My webservice returns a 404 too but that is because I secured it (using forms authentication) and ASP.NET tries to forward me to login.aspx (which it can’t find). You might have the wrong path. You could just add an html file to the solution to be deployed in the same folder as the .svc and try to browse to it. If you want to, feel free to share your bitbucket account (if you are using that) with me (reader is enough, my user name is ernow) and I’ll have a look.
Erno de Weerd
Thank you. I Figured out an issue. I have in 1 solution webforms app and service, but appharbor allow access only one app. So, can I move webservice to webform app?
Yaroslav
Yes, certainly. Make sure you copy over any other classes and configuration as well. It might be easier to simply add a Silverlight enabled WCF Service to the WebApp and copy the code.
Erno de Weerd