Jul 15 10

Start a web-server for unit testing in C#

by mandel

I’m currently heavily working on Windows (oh irony) and I was confronted with a situation in which I wanted to be able to write a unit test that will access a web-page perform certain operations and check that everything went ok. This is not the first time I have been confronted with this situation (yes, really) and last time I resorted to use the great trick from Phil Haack in which the Microsoft.VisualStudio.WebHost namespace (yes, cassini) is used. I’ve copy paste his code here so that you can easily find it (you cannot longer access it through his page).

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
using Microsoft.VisualStudio.WebHost;
 
namespace Gazintas.Test.Common
{
        /// <summary>
        /// <para>A Web Server useful for unit tests.  Uses the same code used by the
        /// built in WebServer (formerly known as Cassini) in VisualStudio.NET 2005.
        /// Specifically, this needs a reference to WebServer.WebHost.dll located in
        /// the GAC.
        /// </para>
        /// </summary>
        /// <remarks>
        /// <para>If you unseal this class, make sure to make Dispose(bool disposing) a protected
        /// virtual method instead of private.
        /// </para>
        /// <para>
        /// For more information, check out: http://haacked.com/archive/2006/12/12/Using_WebServer.WebDev_For_Unit_Tests.aspx
        /// </para>
        /// </remarks>
        public sealed class TestWebServer : IDisposable
        {
                private bool started = false;
                private Server webServer;
                private int webServerPort;
                private string webServerVDir;
                private string sourceBinDir = AppDomain.CurrentDomain.BaseDirectory;
                private string webRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebRoot");
                private string webBinDir = Path.Combine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WebRoot"), "bin");
                private string webServerUrl; //built in Start
 
                /// <summary>
                /// Initializes a new instance of the <see cref="TestWebServer"/> class on port 8085.
                /// </summary>
                public TestWebServer() : this(8085, "/")
                {
                }
 
                /// <summary>
                /// Initializes a new instance of the <see cref="TestWebServer"/> class
                /// using the specified port and virtual dir.
                /// </summary>
                /// <param name="port">The port.</param>
                /// <param name="virtualDir">The virtual dir.</param>
                public TestWebServer(int port, string virtualDir)
                {
                        this.webServerPort = port;
                        this.webServerVDir = virtualDir;
                }
 
                /// <summary>
                /// Starts the webserver and returns the URL.
                /// </summary>
                public Uri Start()
                {
                        //NOTE: WebServer.WebHost is going to load itself AGAIN into another AppDomain,
                        // and will be getting it's Assemblies from the BIN, including another copy of itself!
                        // Therefore we need to do this step FIRST because I've removed WebServer.WebHost from the GAC
                        if (!Directory.Exists(webRoot))
                                Directory.CreateDirectory(webRoot);
 
                        if (!Directory.Exists(webBinDir))
                                Directory.CreateDirectory(webBinDir);
 
                        CopyAssembliesToWebServerBinDirectory();
 
                        //Start the internal Web Server pointing to our test webroot
                        webServer = new Server(webServerPort, webServerVDir, this.webRoot);
                        webServerUrl = String.Format("http://localhost:{0}{1}", webServerPort, webServerVDir);
 
                        webServer.Start();
                        started = true;
                        Debug.WriteLine(String.Format("Web Server started on port {0} with VDir {1} in physical directory {2}", webServerPort, webServerVDir, this.webRoot));
                        return new Uri(webServerUrl);
                }
 
                private void CopyAssembliesToWebServerBinDirectory()
                {
                        foreach (string file in Directory.GetFiles(this.sourceBinDir, "*.dll"))
                        {
                                string newFile = Path.Combine(this.webBinDir, Path.GetFileName(file));
                                if (File.Exists(newFile))
                                {
                                        File.Delete(newFile);
                                }
                                File.Copy(file, newFile);
                        }
                }
 
                /// <summary>
                /// Makes a  simple GET request to the web server and returns
                /// the result as a string.
                /// </summary>
                /// <param name="page">The page.</param>
                /// <returns></returns>
                public string RequestPage(string page)
                {
                        WebClient client = new WebClient();
                        string url = new Uri(new Uri(this.webServerUrl), page).ToString();
                        using (StreamReader reader = new StreamReader(client.OpenRead(url)))
                        {
                                string result = reader.ReadToEnd();
                                return result;
                        }
                }
 
                /// <summary>
                /// Makes a  simple POST request to the web server and returns
                /// the result as a string.
                /// </summary>
                /// <param name="page">The page.</param>
                /// <param name="formParameters">
                /// The form paramater to post. Should be in the format "Name=Value&Name2=Value2&...&NameN=ValueN"
                /// For extra credit, build a version of this method that uses a NameValue collection. I use a
                /// string because it's possible you may want to post flat text.
                /// </param>
                /// <returns></returns>
                public string RequestPage(string page, string formParameters)
                {
                        HttpWebRequest request = WebRequest.Create(new Uri(new Uri(this.webServerUrl), page)) as HttpWebRequest;
 
                        if (request == null)
                                return null; //can't imagine this happening.
 
                        request.UserAgent = "Sutext UnitTest Webserver";
                        request.Timeout = 10000; //10 secs is reasonable, no?
                        request.Method = "POST";
                        request.ContentLength = formParameters.Length;
                        request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
                        request.KeepAlive = true;
 
                        using (StreamWriter myWriter = new StreamWriter(request.GetRequestStream()))
                        {
                                myWriter.Write(formParameters);
                        }
 
                        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                        if (response.StatusCode < HttpStatusCode.OK && response.StatusCode >= HttpStatusCode.Ambiguous)
                                return "Http Status" + response.StatusCode;
 
                        string responseText;
                        using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
                        {
                                responseText = reader.ReadToEnd();
                        }
 
                        return responseText;
                }
 
                /// <summary>
                /// Extracts a resources such as an html file or aspx page to the webroot directory
                /// and returns the filepath.
                /// </summary>
                /// <param name="resourceName">Name of the resource.</param>
                /// <param name="destinationFileName">Name of the destination file.</param>
                /// <returns></returns>
                public string ExtractResource(string resourceName, string destinationFileName)
                {
                        if (!started)
                                throw new InvalidOperationException("Please start the webserver before extracting resources.");
 
                        //NOTE: if you decide to drop this class into a separate assembly (for example,
                        //into a unit test helper assembly to make it more reusable),
                        //call Assembly.GetCallingAssembly() instead.
                        Assembly a = Assembly.GetExecutingAssembly();
                        string filePath;
                        using (Stream stream = a.GetManifestResourceStream(resourceName))
                        {
                                filePath = Path.Combine(this.webRoot, destinationFileName);
                                using (StreamWriter outfile = File.CreateText(filePath))
                                {
                                        using (StreamReader infile = new StreamReader(stream))
                                        {
                                                outfile.Write(infile.ReadToEnd());
                                        }
                                }
                        }
                        return filePath;
                }
 
                /// <summary>
                /// Stops this instance.
                /// </summary>
                public void Stop()
                {
                        Dispose();
                }
 
                ~TestWebServer()
                {
                        Dispose(false);
                }
 
                public void Dispose()
                {
                        Dispose(true);
                        GC.SuppressFinalize(this);
                }
 
                ///<summary>
                ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
                ///</summary>
                /// <remarks>
                /// If we unseal this class, make sure this is protected virtual.
                /// </remarks>
                ///<filterpriority>2</filterpriority>
                private void Dispose(bool disposing)
                {
                        if (disposing)
                        {
                                ReleaseManagedResources();
                        }
                }
 
                // Cleans up the directories we created.
                private void ReleaseManagedResources()
                {
                        if(this.webServer != null)
                        {
                                this.webServer.Stop();
                                this.webServer = null;
                                this.started = false;
                        }
 
                        if (Directory.Exists(this.webBinDir))
                                Directory.Delete(this.webBinDir, true);
 
                        if (Directory.Exists(this.webRoot))
                                Directory.Delete(this.webRoot, true);
                }
        }
}

This is a great solution if either you expect all your developers to have visual studio since I’m not sure that you are allows to distribute WebDev.WebHost.dll, in order cases, this solution is no good. This is where Mono becomes extremely useful:

/**
 * Copyright 2010 Canonical Ltd.
 * 
 * This file is part of UbuntuOne on Windows.
 * 
 * UbuntuOne on Windows is free software: you can redistribute it and/or modify		
 * it under the terms of the GNU Lesser General Public License version 		
 * as published by the Free Software Foundation.		
 *		
 * Ubuntu One on Windows is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	
 * GNU Lesser General Public License for more details.	
 *
 * You should have received a copy of the GNU Lesser General Public License	
 * along with UbuntuOne for Windows.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Authors: Manuel de la Peña <manuel.delapena@canonical.com>
 */
using System;
using System.IO;
using System.Net;
using System.Threading;
using Mono.WebServer;
 
namespace Canonical.UbuntuOne.Common.Tests
{
    /// <summary>
    /// This class allows to start a XSP server for testing purposes. Before the server is ran
    /// all the different properties must be set up so that the server knows where to run from an
    /// what to run.
    /// </summary>
    [Serializable]
    public class TestWebserver
    {
 
        private ApplicationServer _server;
 
        #region Property settings
 
        /// <summary>
        /// a comma separated list of virtual directory and
	/// real directory for all the applications we want to manage
	/// with this server. The virtual and real dirs. are separated
	/// by a colon. Optionally you may specify virtual host name
	/// and a port.
        /// </summary>
        public string Apps { get; set; }
 
        /// <summary>
        /// adds application definitions from all XML files
        /// found in the specified directory DIR. Files must have
	/// .webapp' extension
        /// </summary>
        public string AppConfigDir { get; set; }
 
        /// <summary>
        /// adds application definitions from the XML
	/// configuration file. See sample configuration file that");
        /// comes with the server.
        /// </summary>
        public string AppConfigFile { get; set; }
 
        /// <summary>
        /// the server changes to this directory before anything else.
        /// </summary>
        public string RootDir { get; set; }
 
        /// <summary>
        /// Gets and sets the port number in which the server will be ran.
        /// </summary>
        public int Port { get; set; }
 
        /// <summary>
        /// Gets and sets the ip of the server.
        /// </summary>
        public string Ip { get; set; }
 
        /// <summary>
        /// Gets and sets if the server should be stop when there is an exception.
        /// </summary>
        public bool NonStop { get; set; }
 
        /// <summary>
        /// Gets and sets if we want the server to be verbose.
        /// </summary>
        public bool Verbose { get; set; }
 
        /// <summary>
        /// The exception which stop the server.
        /// </summary>
        public Exception Exception { get; set; }
 
        #endregion
 
        /// <summary>
        /// Creates a new webserver that can be used for testing purposes.
        /// </summary>
        public TestWebserver()
        {
            // set the basic settings
            Port = 8080;
            Ip = "0.0.0.0";
        }
 
        public void Start()
        {
            var ipaddr = IPAddress.Parse(Ip);
 
            if (!String.IsNullOrEmpty(RootDir))
            {
                Environment.CurrentDirectory = RootDir;
            }
 
            RootDir = Directory.GetCurrentDirectory();
 
            WebSource webSource = new XSPWebSource(ipaddr, Port, false);
 
            _server = new ApplicationServer(webSource)
            {
                Verbose = Verbose,
                SingleApplication = false
            };
 
            if (Apps != null)
                _server.AddApplicationsFromCommandLine(Apps);
 
            if (AppConfigFile != null)
                // allows to load the config from a file
                _server.AddApplicationsFromConfigFile(AppConfigFile);
 
            if (AppConfigDir != null)
                // add config from a dir
                _server.AddApplicationsFromConfigDirectory(AppConfigDir);
 
            if (Apps == null && AppConfigDir == null && AppConfigFile == null)
                _server.AddApplicationsFromCommandLine("/:.");
 
            var vh = _server.GetSingleApp();
            if (vh != null)
            {
                // Redo in new domain
                vh.CreateHost(_server, webSource);
                var svr = (TestWebserver)vh.AppHost.Domain.CreateInstanceAndUnwrap(
                    GetType().Assembly.GetName().ToString(), GetType().FullName);
                webSource.Dispose();
                svr.Start();
            }
 
            if (_server.Start(!NonStop, Exception) == false)
                return;
        }
 
        /// <summary>
        /// Allows to stop the server if it is currently running.
        /// </summary>
        public void Stop()
        {
            _server.Stop();
        }
    }
}

The above class allows to start an XSP server that will host a webpage that you can use for your unit tests. This code was inspired by the XSP implementation from mono and adapted so that is will be a class that could be easily instantiated for unit testing and that can be distributes since it just depends on Mono.WebServer2.dll which has the MIT license, I hope it helps you :D

Jun 21 10

WCF inactivity timeout=infinite

by mandel

One of my last projects in Electrabel has involved the creation of a 3-tier application using the .Net framework. During the development of the application we decided to use a number of libs that we though would ease the development of the application, this frameworks were:

  • Spring.Net
  • NHibernate
  • NVelocity
  • WCF

As many of you may know already, adding high level frameworks to a project is a double-edge sword. While adding this kind of frameworks does indeed accelerate the initial development of the application, they do complicate the process of hunting down bugs since you really need to know how the frameworks have been designed.

During our development and due to a small mistake we found or selfs in one of those situations in which we really had to think about the interaction between WCF and Spring.Net.

Spring.Net and WCF integration documentation

Before the project I did not have much experience with Spring.Net and therefore trusted the designer other developer that was working with me. As any developer would do, he read the documentation from Spring.Net in order to integrate WCF and Spring.Net through Springs IoC container. If you read Springs documentation you will find the following:

28.4. Creating client side proxies declaratively

To create a client side proxy based on the use of ChannelFactory, you can use this rather ugly ‘boiler plate’ XML snippit that takes uses Spring’s support for calling factory methods on object instances.

  <!-- returns ChannelFactory<ICalculator>("calculatorEndpoint").CreateChannel() -->
 
  <object id="serverAppCalculator" type="Spring.WcfQuickStart.ICalculator, Spring.WcfQuickStart.ClientApp"
          factory-object="serverAppCalculatorChannelFactory"
          factory-method="CreateChannel" />
 
  <object id="serverAppCalculatorChannelFactory"
          type="System.ServiceModel.ChannelFactory&lt;Spring.WcfQuickStart.ICalculator>, System.ServiceModel">
 
    <constructor-arg name="endpointConfigurationName" value="serverAppCalculatorEndpoint" />
 
  </object>

Well, that seems easy! After a number of evenings wasted on this, I can tell you it is not!

Clean up correctly when exceptions occur with a WCF client

Before I start complaining about the Spring.Net documentation it is interesting to read a very nice post at MSDN that addresses side effects of the using block when working with WCF. Take some time and read it.

From that article the part I am the most interested is the one related of how to clean up correctly when exceptions occur, for example:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

Spring.Net and WCF problems

I am sure that you are smarter than me and you can easily spot the error… well I could not, and for those of you who want to know here it is:

Spring.Net will create the WCF clients as Singletons and will inject them in your objects. Of course this does not mean that Spring.Net will be the nice guy that will ensure that your clients are cleaned correctly when there is an error! Guess what, we use to have errors, to be precise, we use to have issues related with the inactivityTimeout used in our clients.

We (I, since at that point I was the only developer in the project) did not know that the issue was related with the integration of Spring.Net and WCF and therefore googled for the inativityTimeout exception I was getting in my logs.

InactivityTimeout == infinite

If you google how to get the inactivity timeout to be infinite you will find the following solution:

The default receive timeout is 10 minutes, so WCF client will be disconnected after the idle time exceeded that limitation. What can I do if the connection needs to be kept alive?

Solution #1 – Server provides a dummy operation for client calls it regularly to let it not idle.

Solution #2 – Enable reliableSession and set receiveTimeout and inactivityTimeout to “infinite” in both the client and server.

The configuration snippet may like the following:

 
<system.serviceModel>
  <bindings>
    <wsHttpBinding>
      <binding name="WSHttpBinding" receiveTimeout="infinite">
        <reliableSession inactivityTimeout="infinite" enabled="true" />
      </binding>
    </wsHttpBinding>
  </bindings>
  <services>
  ...
  </services>
  ...
</system.serviceModel>

You can get more detail explanation in the following reference link.

Reference in MSDN:
Binding.ReceiveTimeout Property

Guess what, I did that, and after implementing the solution, my clients will crash every 20 mins, WTF!!!

Hosting WCF services in IIS with reliable sessions

At this point I’m going to let you the last piece of the puzzle, the services were hosted on IIS and I was the only person testing the application… Right now, the smartest of you probably know the issue, the other like me will say, and what??.

Well, the issue is the following, IIS does also have a timeout that will recycle that process in IIS when it is not used. Since we are using sessions, when the process is recycled the sessions is lost and the clients crash, again….. This made us realize that we should correctly implement the clients by hand and just inject the ChannelFactories through Spring.Net and completely ignore the Spring.Net documentation.

Actual implementation

As you might have noticed already cleaning correctly the client is quite a lot of typing for what you have to do, to simplify things I implemented to methods that will take care of it:

public void CleanClientCall(Action action, ICommunicationObject client)
{
    try{
        action();
        client.Close();
    }
    catch (CommunicationException e){
        ...
        client.Abort();
    }
    catch (TimeoutException e){
       ...
       client.Abort();
    }
    catch (Exception e){
        ...
        client.Abort();
        throw;
    }
}
 
public TResult CleanClientCall<TResult>(Func<TResult> action, ICommunicationObject client){
    try{
        var result = action();
        client.Close();
        return result;
    }
    catch (CommunicationException e){
        ...
        client.Abort();
    }
    catch (TimeoutException e){
       ...
       client.Abort();
    }
    catch (Exception e){
        ...
        client.Abort();
        throw;
    }
}

Well, in summary:

  1. Do not follow the documentation of Spring.Net and inject the factories, not the clients.
  2. Clean the clients correctly.
  3. Do not set timeouts to infinite, do things correctly, otherwise you will have problems later.
  4. Be lazy and use helpers methods to wrap the clean up if you can :P
  5. I know this took me some time to fix and I hope it helps the next pour soul that has this problem.

    References

Jun 17 10

Where the hell am I?

by mandel

I have been away from the blog and from python and macaco-contacts for a while and some people migh have started wondering if I have given up on Ubuntu and desktopcouch, but let me tell you it is completely the opposite! My personal life has been crazy latelly after landing my dreamed job and sorting out moving back to the U.K. Spain from Belgium.

My new job should allow me to give far more to the Ubuntu community and will give me some extra time to allow me do some work on my new android phone (I´m in love with my HTC Magic). Right now just my close friends know about my new position and I will only make it available on the web as soon as it is official enough that is, I have started working already, but let me tell you, I cannot wait ´til I start. I am so excited about what I will be working on that I have already started investigating different ways to approach my goal and to get things nice and ready so that I can start coding as soon as I arrive to the “office” (the quotes are intentional :D )

May 1 10

Developers do not understand big Oh

by mandel

Computer scientist should really start paying more attention to their algorithm professors at university. After reading this post in the gnome rss I found it ridiculous/amazing that a developer had to write code to actually prove a well know proven behavior in searching algorithms. That post first proves that a lot of developers always to try solve their problems with the same hammer (binary search) and that really do not understand Big Oh. Here is the explanation I gave in my comment in the blog:

Comment

Did you really have to write the code?!? Common that is what big Oh notation is for. There main problem people are having is to truly understand what big Oh means. What I mean is the following, we know as a fact that a linear search is O(n) while a binary search is O(log N), of course if we do not understand our algorithm (in this case binary search) we will think that O(log N) will always outperform O(N) but that is not true.

If you carefully think about the definition of big Oh notation you will find that in most cases we are worried about N begin close to a large number. This is a very important fact, because we are ignoring the exact and precise performance of our implementation. If we where to talk about the performance of the binary search we will have to say that it is O(log N) + K where K represents the constant overhead that our algorithm has. Getting back to big numbers, if we say that K is constant, it means that when the limit of N is infinite K becomes 0 since a small number divided by a large number is nearly 0. At this point we can agree that if our problem is facing large numbers, we ought to ignore K since it has no added value. Nevertheless, there are cases where O(N) does outperform O(log N) because we are working with a small number of items. In the linear search algorithm you have not overhead (nearly) meaning that K is 0 while in the binary search we do have K, because the number of items is small, we cannot ignore K! That is, O(N) is better than O(log N) + K because K is large enough when dealing with a small list!!

As you can see, if the algorithm is correctly understood as well as the environment in which we use it, there is actually no need to write tests cases etc… you could have reached the conclusion using pen and paper ;) .

Nevertheless, I think this is a goo post for other people but please explain somewhere that the theory s right! people should stop using big Oh until the really understand it meaning and the algorithm implementation…

Apr 30 10

Using C# with Desktopcouch == desktopcouchsharp

by mandel

As I have mentioned several times, during my everyday job I deal with C# and although I every now and then I complain about it, I consider the language to be quite good overall. One of the things that has been bothering me is the lack of a lib to used Desktopcouch from C#! This is a pity since there are some great application on Gnome written on the Mono stack that will really could take advantage of Desktopcouch. I have noticed that most of the time people in the blog-sphere complaining about things, but really do not do anything to solve them. Trying to avoid to be a one of “those whiners” I give you desktopcouchsharp.

Basic API

Writing a nice API is nothing but easy and I’ve got to recognized that I’m not such a great hacker (and I wrote the lib in 2 days and spent 2/3 trying to compile the lib in Ubuntu ’til I got desperate and I used openSUSE on VirtualBox… sorry Ubuntu but it is for the greater good :( ). I used Divan as an initial starting point for my code. Although Divan is a great library, I have decided to use its code as the base/starting point for desktopcouchsharp

To be able to use desktopcouchsharp you have first to define your document classes, for example:

Address

using System;
using Newton.Json;
 
namespace Desktopcouch
{
    // define an enum for the address type.
    public enum AddressType{
        HOME,
        WORK,
        OTHER
    }
 
    // and address is not a document but we need to state how
    // to serialize it we use the attribute to let the serializer
    // know we want to add attributes
    // with info of how to serialize the object
    [JsonObject(MemberSerialization.OptIn)]
    public class Address
    {
         #region Serialized Properties
 
         [JsonProperty("country")]
         public string Country { get; set; }
 
         [JsonProperty("state")]
         public string State { get; set; }
 
         [JsonProperty("city")]
         public string City { get; set; }
 
         [JsonProperty("postalcode")]
         public string Postalcode { get; set; }
 
         [JsonProperty("street")]
         public string Street { get; set; }
 
         // because the type is an enum, we will let the
         // serializer know that we will provide him with
         //  the logic to convert it to the correct values
         // rather than ints
         [JsonProperty("type")]
         [JsonConverter(typeof(AddressTypeConverter))]
         public AddressType AddressType { get; set; }
 
         #endregion
 
         #region Constructors
 
         Address ()
         {
              // required for deserialize
         }
 
         #endregion
    }
}

AddressTypeConverter

The AddressTypeConverte lets the Serializer know how to serialize, deserialize our AddressType enum.

Contact

using System;
using Desktopcouch;
 
namespace MacacoExample
{
 
    public class Contact : Record{
 
        #region Properties
 
        [JsonProperty("first_name")]
        public string FirstName { get; set; }
 
        [JsonProperty("middle_name")]
        public string MiddleName { get; set; }
 
        [JsonProperty("last_name")]
        public string LastName { get; set; }
 
        [JsonProperty("addresses")]
        public MergeableList<Address> Addresses{ get; set; }
 
        #endregion
 
        #region Constructors
 
        Contact ()
            : base ("record_type_url"){
            // desrialization reasons
        }
 
        public Contact (string name, 
                            string middleName, 
                            string lastName)
            : base ("record_type_url") {
             Name = name;
             MiddleName= middleName;
             LastName = lastName; 
             Addresses = new MergeableList<Address>();
        }
        #endregion
    }// class
}// namesapce

DB usage

(I always use the contact example because is the most recognized record in desktopcouch for know… calendar talk in UDS? ;) )

There is a very important thing to mention, desktopcouchsharp it is not a ORM/ODM library, it is a library to be able to use Desktopcouch from C#. While using dictionary is a natural way to interact with Desktopcouch from python, the same cannot be said for C#. In the .Net realm the Object is the king (Clearly visible in the implementation of IronPython and its “late method binding”. Desktopcouchsharp will not create a proxy for you to use, it will not deduce how to serialize you objects etc… The idea of the lib is very simple, you create a document and you mark/decorate the properties to be serialize with the appropriate Json.Net attributes.

using System;
using Desktopcouch;
 
namespace Desktopcouch.Example
{
	class MainClass
	{
		public static void Main (string[] args)
		{
			Console.WriteLine ("Create db with name sharp_test");
			var db = new DesktopcouchDatabase ("sharp_test", true);
			// create some contacts to add
			var first = new Contact ("Manuel", "", "de la Pena");
			var firstId = db.PutRecord (first);
			Console.WriteLine ("Added contact with id {0}", firstId);
			var second = new Contact ("Jaime", "", "de la Pena");
			var secondId = db.PutRecord (second);
			Console.WriteLine ("Added contact with id {0}", secondId);
			// Delete the db
			db.DeleteRecord (firstId);
			Console.ReadKey ();
		}
	}
}

Vie operations are possible through the use of the CouchDesignDocument object that allows to add and remove view.

var design = db.NewDesignDocument("contacts");
			design.AddView("by_letter", @"function(doc) {
        if (doc.record_type == 'http://www.freedesktop.org/wiki/Specifications/desktopcouch/contact') {
        function first_letter(name) {
            var first_letter = name.substring(0, 1);
            return first_letter.match(/[A-Za-z]/) ? first_letter.toLowerCase() : '0';
        }
 
        try {
            deleted = doc['application_annotations']['Ubuntu One']
                ['private_application_annotations']['deleted']}
        catch (e) {
            deleted = false;}
        if (! deleted) {
            var raw_last = doc.last_name || '';
            var raw_first = doc.first_name || '';
            var raw_company = doc.company || '';
 
            var name_to_test = raw_last || raw_first || raw_company;
 
            var emit_letter = first_letter(name_to_test);
            var emit_last = raw_last.toLowerCase();
            var emit_first = raw_first.toLowerCase();
            var emit_company = raw_company.toLowerCase();
 
            emit([emit_letter, emit_last, emit_first, emit_company], 1);}}}");
			// very important, otherwhise it will not be in the db
			design.Synch();
			var result = db.Query("contacts");
			Console.WriteLine("Count the result", result.Result.Count);

Record class

The record class is just a small abstract class that implements a number of basic properties to be implemented by records. There is no need to extend that class but you do have to implement the IRecord interface.

Constructors

I know that this is not the “most” POCO implementation but you DO have to implement an empty constructor for the classes that you have to serialize. The constructor does NOT have to be public, but it has to be present.

Collections

I believe that the serialization of collections deserves a special mention. As you might now the python implementation (that is, desktopcouch.records) will make sure that all the collections you use in your records are MergeableLists. This is a “trivial” thing to do in a dynamic language but not so easy in a dynamically typed language such as C#. Ideally I could have written the library in such a way that dictionaries that used collection will not be able to be serialized, or just create a runtime proxy that will use a MergeableList instead but that is not trivial and adds a big amount of overhead. Instead i have taken the decision to allow anyone to serialize their object in anyway and explicitly use a MergeableList when required. The serialization of any collection class in C# will be a json array (besides dictionaries of course!).

I nice way to make a more C# API for your objects will be to provide a non-public property used for serialization that has in its signature the MergeableList type and use a public one that uses a generic type such as IList:

Attachments

I have added the ability to add attachments to record just as the python lib does unfortunalty this has not yet been added to the lib (did I mention this is a version 0.1?)

Code

All code is available in launchpad (lp:desktopcouchsharp) under BSD license. I will be adding documentation in this page to ensure that people can easily use the code.

Rant

The compilation of the code has been a pain in the ass due to a bug in the Mono compiler version 2.4, if you want to compile the code you DO NEED 2.6 to do it, otherwise you will waste the time as I did.