This user hasn't shared any biographical information

REST API Design Rulebook – a waste of e-ink

REST API Design RulebookREST API Design Rulebook by Mark Masse
My rating: 1 of 5 stars

The book tries to establish a set of rules for REST interfaces – I’m OK with that, it’s what its title says. When you define rules, you take an opinion, that’s fine. And most of what is defined as a rule is something I’d support as a good practice. A possible benefit of this rule book would be to provide some compact, memorizable, and clear guidance on good practices.

But then there’s this obsession about WRML.

WRML is a proposed way of defining the content types and schemas of REST APIs – see http://www.wrml.org/about. And what do we find as the sole literature on WRML: the REST API Design Rulebook. WRML has about zero industry acceptance; I never heard of it, and it does not even have a wikipedia page. And yet the REST API Design Rulebook puts it into the heart and center of every halfway advanced topic. Now a “rule” that is centered around an obscure standard proposal is totally void, and so this book is a simple waste of e-ink (even if that was its only fault, which it isn’t).

1 Comment

JavaScript for Metro-style apps: now I get it.

I’ve spent the last few days at the Microsoft //build/ conference where the first preview version of Windows 8 was shown (and everyone got a tablet PC, which I like, and – and this is going to be important for the rest of the post – has built-in 3G network).

I really quite like the new Metro-style UI for touch applications. I’ve liked the Metro UX framework from the start when it first came into view on Windows Phone 7. I’m not sure if it’s a good idea to go to the full-screen home screen for launching apps when in desktop mode, but that’s a different story, and you can still install Launchy anyway.

What I didn’t really understand was why JavaScript and HTML were integrated into the platform in such a deep way. You have three independent language systems for creating applications that use the new Windows Runtime (“WinRT”): native C++ code with XAML as markup, .NET with XAML as markup, and JavaScript with HTML as markup. Each of them is anchored into the system at the same level. Access to the WinRT is provided to each of these systems in a different way using “projections”: translations of type systems and conventions to make working with the WinRT feel like you did not. Repeat; JavaScript has been anchored AT THE SAME LEVEL AS C++ AND .NET.

Phase 1: Writing it off

Seeing actual code written in HTML/JS however was quite a downer. I had hoped for a more semantic way of expressing layout than XAML provides. XAML is basically a 1-to-1 representation of an object graph, while HTML expresses layout more as an intent. I normally find working with XAML a lot less productive than it should be. However, to make use of the controls that the platform provides, you need to drop concrete class names into your markup, making HTML for Metro-style applications more concrete than it should be.

As for JavaScript, there is an extra JavaScript library called the WinJS to provide access to platform specifics. And there are some: since an HTML application is, well, and application instead of a web site, it needs to care about process lifecycle and state management. It’s really hard to put this in words – do you know the feeling when something looks all reasonable, but just does not feel right? That’s it with me and the WinJS.

But my main problem with the whole HTML/JS stack was that I could not understand WHY. Everyone I talked to stated that the HTML path for building Metro-style apps existed so that web developers could bring their existing skill set and write great apps from the get-go. I don”t quite buy this. Because first of all, an existing skill set might not be so important. The enormous success of the iOS platform proves that if something is attractive enough, you even write Objective-C. I think it’s an underestimation of developers who master modern web site development when you assume they could not learn some C# or VB on a level that makes you perfectly productive at the level you need for UI programming.

And secondly, you actually have to extend your skill set anyway: WinRT controls, WinJS, process lifecycle management…

So what I concluded is that the HTML/JS stack was basically an oversized technological approach to solve the business problem that for the platform to succeed, it’ll have to launch with a fully stacked app store.

Phase 2: and then I got it.

I have two days left in California before I head back to Germany. I’m going to take a shower, pack my things, and drive south to the beach once this blog post is finished. Which will cause me my favorite problem: I tend to get lost. And the car does not have GPS.

But what I do have is a tablet PC with GPS and a 3G connection. So when I woke up at 4AM (the jet lag never left), I sat down and wrote myself a Metro app that displays a map full screen, and puts my current position into the center, and puts a pin at it.

And how do you display a Google Map? Yeah. Exactly. All you need is some HTML and JS. (I ended up using Bing Maps because it works better with the pinch-to-zoom gesture).

Here’s the app.

The thing that sets apart the HTML/JS way of writing Metro-style apps over using XAML markup is not that it’s easier to write, or yields better results. In fact, I believe that for larger apps, the robust type system and patterns like MVVM give .NET a clear edge.

HTML/JS Applications that integrate with web resources intensely have a lot less friction. You don”t need to write an adapting SDK to translate between a service like Maps. HTML and JavsScript are the native language of the web, and when you go there, it’s better to speak that language well.

, ,

3 Comments

Take the Stoic Test

An underestimated developer superpower: Keep calm and focused with a juicy geek discussion exploding in the office/Twitter/Comments/Yammer around you. You get one point for each question that you can claim to keep your head down and your mouth shut:

  • Is this an entity or a value object?
  • Does Apple provide open software system?
  • Does using Open Source Software pose a risk?
  • Is Google evil?
  • Is this interface really RESTful?
  • Is Java dead?

If you score higher than five, you get an official hand-drawn STOIC badge to attach to your CV.

I doubt that I ever get one.

Leave a Comment

Pipes, valves, and social networks

This is a chat room.
The tube is a message bus. When a sender pushes a message into it, it just comes out on the other side, and everyone receives it.
This is e-mail.
We’ve added a valve on the sender’s side. It lets the sender control to whom the message goes out. On the receiving side there’s nothing, you can get messages from everyone – look into your spam folder for proof.
This is Twitter.
Now there’s a valve on the receiver’s side. Everything you publish is public, but you only get messages from senders you follow.
This is Facebook.
The two valves on both sides are interlocked. Opening them to someone enables sending and receiving messages.
This is Google+ Circles.
The valves are independent. The red valve controls the receivers of message; this is the circle a message is published to. The green valve is a circle of of the recipient. The message only gets through if the sender is also in the receiver’s circles.

2 Comments

Beware of the dreaded passive arbiter!

The setup seemed simple enough: Two application servers running ASP.NET MVC, and a MongoDB replica set with two standard nodes and an arbiter.

If you’re familiar with MongoDB replica sets, feel free to skip the following paragraph:
When using MongoDB in a productive environment, two servers and an arbiter is much of the absolute minimum requirement. You cannot seriously run on a single server; MongoDB is not designed to provide good durability on just one machine. A two-node replica set provides replication of changes to a second machine. It also provides a failover server should one server go down. A replica set works much like classic master-slave replication, but the master is elected automatically: if the node that currently holds the master role (the “primary”) does not respond to a heartbeat, another node (a “secondary”) becomes the new primary. Which secondary is promoted is quite an interesting ceremony. An important aspect is that for a primary to be elected, it needs to “see” the majority of nodes in the replica set – otherwise, a broken network link could split the replica set in the middle, and there’d be two primarys (see a popular piece of fantasy literature on what happens in such a situation). A result of these rules is that a replica set with just two nodes cannot work – when one server goes down, the other won’t see a majority; even when the secondary node fails, the primary demotes itself because it finds itself alone.
This is where an arbiter comes in. An arbiter is a MongoDB instance that does not hald any data. It does not take part in replication. Its sole purpose is to provide a majority for the election of a new primary. In a two-node setup with an arbiter, one server can fail and the other still has a majority of two thirds of the replica set.
And then there’s priority. Priority is a MongoDB server setting that determines the order in which nodes are elected primary – a node with a higher priority wins. The most useful setting for priority however is 0. Setting a node’s priority to 0 makes it a “passive” node – it will never become elected the primary. This is useful if a machine is e.g. used just for backups, or if it’s in a remote data center. Otherwise, a passive node acts like a normal member of the replica set – data is replicated to it, and it can be used for running queries against it.

Alright. So we’ve got passive nodes, and arbiters. This is mutual exclusive – an arbiter cannot be a passive node. Passive nodes can be used for queries, arbiters can’t, they don’t hold data.

To query the status of a node in a replica set, you can run the command db.isMaster() against it. It returns a document that describes the shape of the replica set, and the role the queried server plays within it. Two fields that are returned are if the node is a passive, and if it’s an arbiter.

The C# driver for MongoDB (and probably other drivers as well) use the isMaster command to determine which servers are available, which is the primary, and qhich ones can be used for queries. If a query is allowed to be executed against a read-only replica of the database by setting the slaveOk setting, the driver looks for secondary and passive nodes. Which is correct. But this is where things start to go wrong – using this sequence of in itself harmless details.

MongoDB up to and including the current version 1.81 allow setting the priority on an arbiter node.

If you set the priority to 0, a node will report itself to be a passive. Yes, this includes arbiters. Ouch.

The driver looks for secondaries and passives to run queries. It does not exclude arbiters, because these normally aren’t secondaries or passives. Except when the server claims to be one, because someone set the priority to 0. Second ouch.

If you run a query against an arbiter, it will return an error that it’s neither primary nor secondary, and cannot be read from: { "$err" : "not master or secondary, can't read", "code" : 13436 } .Luckily, the driver recognizes the connection as faulty and removes it from the connection pool.

So what kept me busy for about ten working hours was this: an application server was reporting a MongoDB error once every time the IIS application pool had restarted. It turned out it was connecting to an arbiter, and the reason for that was that a DBA had set the priority on the sorry arbiter to 0, which was a leftover of its configuration before it became an arbiter.

First conclusion: don’t set the arbiter’s priority to 0. Just don’t. Arbiters don’t need a priority setting, or in fact, mostly no settings at all.

Second conclusion: I really love MongoDB for its simplicity, the data modelling possibilities, and its performance. But things like the dreaded passive arbiter really raise a question mark on its maturity – the problem would have been easily avoidable on either the server or the driver (or both, for better). What also needs to be admitted that 10gen is extremely responsive and helpful. They promised to fix the C# driver within a week(!) in release 1.2, and scheduled a fix for the server for version 1.92 – all within one day reaction time.

, ,

Leave a Comment

C# Enum Quirks

I’ve been slashing out C# code since 2000. Just today I discovered something totally new to me.

Would you reckon that this test is valid?

public enum Direction
{
	Left,
	Right 
}

[TestClass]
public class EnumQuirks
{
	[TestMethod]
	public void Cast_any_number_to_enum()
	{
		Direction direction = (Direction)5;
		Assert.AreEqual(5, (int)direction);
	}
}

In fact, it is. An enumeration can be casted to its underlying data type, of course; by default, this is an Int32. What I hadn’t realized is that the conversion works the other way too. Any integer value can be assigned to an enum, whether the values are defined or not. In order to safely assign a numeric value, the result of Enum.IsDefined can be evaluated.

But it gets weirder. In order to convert a string value into an enum, there’s the TryParse method. But when it tries parsing really hard, look what it can do:

[TestMethod]
public void TryParse_accepts_any_number_in_string()
{
	string value = "5";
	Direction direction;
	bool canParse = Enum.TryParse(value, out direction);
	Assert.AreEqual(5, (int)direction);
	Assert.IsTrue(canParse);
}

This is even documented:

Enum.TryParse: Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.

It converts the numeric value too. And since there is no check if there is a constant defined for any numeric value, any string that can be parsed to a number is valid.

Lesson learned: you can’t trust assignments to enumerations without checking Enum.IsDefined. And if there’s code out there that parses string coming from an insecure source (like an web request), you can’t rely on Enum.TryParse.

Leave a Comment

MongoDB: when documents aren’t perfect, cope with it

MongoDB is a NoSQL database that organizes data as JSON documents in collections instead as rows in tables. One major difference between a document in a collection and a row in a table is that while all rows in a table share the same schema, each document in a collection can be completely different. With all the huge advantages this approach brings, there is a friction when strongly typed domain objects are deserialized from a collection. A document needs to match the shape of the object that it should be serialized to.

The “official” MongoDB driver for .NET – that is the one offered and supported by 10Gen, the company behind MongoDB – offers a serializer that can be told quite exactly how tolerant it needs to be with the deserialization of JSON documents in to objects:

  • When a JSON document defines extra elements that are not present in the class that it is deserialized into, the serializer can either ignore them, stash them into a “catch-all” property, or throw an exception.
  • When properties on the class are missing in the JSON document, the serializer can set the property to null, it can set a default value, or it can throw an exception.

These options are well described in the official documentation.

Handling deserialization errors in collections

The Mongo C# driver supports operations to retrieve single elements from the database as well as collections. As we’ve seen, there is a lot of flexibility in defining a level of tolerance that matches your application’s requirements for the deserialization of single objects, the policy for collections is very simple: if any element fails, the whole collection is not loaded. While this is the right behaviour for scenarios that rely on data integrity, applications that focus on availability are threatened by complete loss of functionality when just a single document in a collection does not match the deserialization requirements.

  • A scenario that relies on data integrity is a form in a line of business application that edits a list of closely related data like line items of an invoice. The line items all need to provide the same set of information, otherwise the invoice cannot reliably be calculated.
  • A scenario that focuses on availability is a search in a product catalogue in an online shop. If a single product is not correctly formatted in the database, it’s better to just not show that single product than letting the whole search page become unavailable and basically close the shop until the ill-formatted product is identified and corrected.

In the case of the search page, a better behaviour would be to exclude any document that cannot be serialized, and provide a callback that identifies the problematic documents. The approach I’ve taken is to copy the default implementation of the collection serializer, and augment the bit that iterates over the elements of the collection with an event handler that keeps the iteration running:

while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
	var elementType = discriminatorConvention.GetActualType(bsonReader, typeof(T));
	var serializer = BsonSerializer.LookupSerializer(elementType);

	T element;
	try
	{
		element = (T)serializer.Deserialize(bsonReader, typeof(T), elementType, null);
		list.Add(element);
	}
	catch (FileFormatException exc)
	{
		// Pass the exception to the provided callback
		if (HandleDeserializationError != null)
		{                           
			HandleDeserializationError(exc);
		}

		// Move the cursor to the next element after the faulted one
		while (bsonReader.State != BsonReaderState.EndOfDocument)
		{
			if (bsonReader.State == BsonReaderState.Value) bsonReader.SkipValue();
			if (bsonReader.State == BsonReaderState.Type) bsonReader.ReadBsonType();
		}
		bsonReader.ReadEndDocument();
	}                    
}
bsonReader.ReadEndArray();

You can download the full source from Bitbucket. Please note that it’s not production quality – it has not been used in production, but that could change over the next few weeks, and then it’s going to get an update with the real thing.

1 Comment

Fixing non-localizable validation messages with JavaScript

There is a nasty globalization bug in ASP.NET MVC: the client-side validation message for numeric is hard coded to a string that is not localizable.

Take the following view model class:

public class SomeViewModel
{
    public int SomeNumber { get; set; }
}

… and this view:

@model SomeViewModel
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    Html.EnableClientValidation();
    Html.EnableUnobtrusiveJavaScript();
}
@using (Html.BeginForm())
{    
    @Html.EditorFor(vm => vm.SomeNumber)
    @Html.ValidationMessageFor(vm => vm.SomeNumber)
}

If the user enters a non-numeric value into the text box form SomeNumber, client side validation kicks in and displays a message. This is excellent, you can’t have validation for less effort. This message is “The field SomeNumber must be a number.”. Umm. That might need some rewording, especially when you wish to translate the user interface into a language other than English. The trouble is: you can’t.

The string “The field {0} must be a number.” is baked into ASP.NET MVC in a resource file. There is no way to define a different resource file, or overwrite the message in any other way (like there is for server-side default messages by setting DefaultModelBinder.ResourceClassKey). For other messages, like the message for required fields, this is not a big deal because a custom message can be applied by setting a DataAnnotation attribute – however, this is not possible for data type based validation.

The options at hand are:

  • Do without the integration of data types and client side validation, and not use EditorFor. Instead, declare the text box directly, and set the data-val-number attribute that controls the message directly. Yuck – that feels like gearing the progeamming model around a platform bug. I’m not desperate for that yet.
  • Fix ASP.NET MVC. It’s open sourced, you can run your own fixed branch, or at least replace the part that causes the problem. Yes, I might do this, but it causes some maintenance problem – each time a new version of the framework is released without a fix, I’d need to recompile. Sounds like a lot of work.
  • Use AOP to basically do the same. That solutions yields a lot less code, but still has a very tight dependency on the current version of the framework; in addition, I’d need to throw another framework into the project. Maybe.
  • The problem lies within ASP.NET MVC, but in the outcome, it’s just a client-side message. Hence, replacing the non-localized message with a localized one does not have to happen on the server. Let’s see where these messages are applied!

The validation rules and messages for client side validation are defined in HTML5-style “data-” attributes that are attached to the form’s input elements. As an example, this is what ASP.NET emits for the integer value from the example above:

 <input 
	class="text-box single-line" 
	data-val="true" 
	data-val-number="The field SomeNumber must be a number." 
	data-val-required="The SomeNumber field is required." 
	id="SomeNumber" 
	name="SomeNumber"
	type="text"
	value="0" />

There are two JavaScript modules that construct the client side validation:

  • jquery.validate provides all the heavy lifting for evaluating and comparing values, and for displaying the messages.
  • jquery.validate.unobstrusive hooks the validation functionality provided by jquery.validate into the page in an “unobstrusive” way – that is, without any JavaScript code that clutters the page. Instead, attributes on the validated controls declare what should be validated.

When jquery.validate.unobstrusive is loaded, it reads all validation attributes on the current page. This is the first chance to change the content of the attribute. The script is cleverly structured; it attaches a host of adapters that match attributes and then initialize the appropriate jquery.validate function. A set of options is passed through the adapter, containing (among other things) the message. Directly after jquery.validate.unobstrusive is loaded, we can hook into its adapters and change the one for numbers:

(function ($) {
    // Walk through the adapters that connect unobstrusive validation to jQuery.validate.
    // Look for all adapters that perform number validation
    $.each($.validator.unobtrusive.adapters, function () {
        if (this.name === "number") {
            // Get the method called by the adapter, and replace it with one 
            // that changes the message to the jQuery.validate default message
            // that can be globalized. If that string contains a {0} placeholder, 
            // it is replaced by the field name.
            var baseAdapt = this.adapt;
            this.adapt = function (options) {
                var fieldName = new RegExp("The field (.+) must be a number").exec(options.message)[1];
                options.message = $.validator.format($.validator.messages.number, fieldName);
                baseAdapt(options);
            };
        }
    });
} (jQuery));

Now, instead of the message defined by ASP.NET MVC, the default message provided by jQuery.validate is used. This is much better because this one _can_ be localized:

$.validate.messages.number="Eine Nummer für {0}, bitteschön";

Instead of inventing your own messages you should maybe use the jquery-glob plugin that comes with a variety of langauges.

Using this solution in your own project

  1. Copy the JavaScript code above into a new JavaScript file named jquery.validate.unobstrusive.fixMvcNumberMessage.js in the Scripts directory of your ASP.NET MVC project.
  2. Add a reference to that file directly beneath jquery.validate.unobstrusive. The references to scripts are normally held in the _Layout shared view.

Feel free to use the code in this article as it serves you.

, ,

3 Comments

On merging project files

Merging Visual Studio Project files: there’s not much nice to say about that. It’s just a pain, and I’ve ssen it become the number one or two reason of errors when merging between code branches.

Even when a project consists of a single team, it is not unusual that one developer adds a bunch of files while another one adds some more, or deletes and moves things around in the project. The source control system and its diff/merge tooling easily copes with the added and deleted code files, and it reliably notices and auto-resolves changes within code. However, to integrate into Visual Studio and the build process, code files also need to be referenced correctly in the *.csproj (or *.vbproj) project files. Here, developers often find strange conflicts, and the diff/merge tools are often not much of a help.

Project files consist or XML that is read by Visual Studio and Msbuild. Their root element is a Project. Project has child elements that indicate properties of the project like its build configuration (and some more), but most important in this context are the ItemGroup elements that contains the content the project:

  • Compile elements that define files compiled into the assembly
  • Content elements that define files not compiled, but otherwise included in the project: MVC views, script files, images contained in a Silverlight XAP…
  • Reference elements that define the projects binary or project dependencies.

Here’s a simplified project file for an ASP.NET MVC project file:

<Project>
<!-- Project properties and extensions omitted for brevity -->
<ItemGroup>
    <Reference Include="System.Web.Mvc />
    <Reference Include="System.Web.WebPages" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Controllers\SquirrelController.cs" />
    <Compile Include="DataAccess\SquirrelRepository.cs" />
    <Compile Include="Global.asax.cs">
      <DependentUpon>Global.asax</DependentUpon>
    </Compile>
    <Content Include="Global.asax" />
    <Content Include="Content\Site.css" />
    <Content Include="Web.config" />
    <Content Include="Web.Debug.config">
      <DependentUpon>Web.config</DependentUpon>
    </Content>
    <Content Include="Web.Release.config">
      <DependentUpon>Web.config</DependentUpon>
    </Content>
    <Content Include="Scripts\jquery-1.4.1.js" />
    <Content Include="Scripts\jquery-1.4.1.min.js" />
  </ItemGroup>
  </Project>

Now why is there such a problem? First, most diff/merge tools just do not understand XML. These tools are designed to identify blocks of code. They know things about code: that outside strings, different kinds of whitespace can be considered as equivalent; that changes within a comment are less significant than elsewhere; where casing is significant or not. What most tools do not understand is that all of the following statements are equivalent:

<Squirrel name="Alex" fur="red"/>

<Squirrel fur="red" name="Alex" />

<Squirrel 
    fur="red" 
    name="Alex" />

<Squirrel fur="red" name="Alex"></Squirrel>

Secondly, the order of ItemGroup elements in the project, and of elements within them, does not matter – in programming languages, the sequence of statements is essential. Now that for project files the sequence is not significant, Visual Studio feels free to arrange items as it feels fit – and the diff/merge tool report hard to resolve conflicts even if two versions of a project file are absolutely equivalent.

Avoiding the pain

One feasible way of avoiding merging between versions of project files is to apply pessimistic locking. Only one developer can have a writable copy of the project, and there are no more conflicting version (at least within the same code branch). The downside is that developers may have to wait for their colleagues to finish their revision. The most frequent thing that a developer does with a project file is probably adding content. To get the shortest possible pessimistic lock on the project file, it should be committed as soon as the files are added – the added files should not contain any code at that point in time that could break the build until it is complete.

However, this approach does not remedy merges between code branches, and does not work with source control systems that just do not allow any pessimistic locking. And this includes each distributed source control system like Git or Mercurial.

Tooling

As we’ve seen, the root of the problem is not that project files are resistant to merging by their nature; it’s just the standard tooling that do not fit. However, there are specific tools that can help. The best candidate I’ve seen so far is a commercial yet relatively cheap (19€) tool called ProjectMerge (http://www.projectmerge.com). ProjectMerge understands XML: it recognizes equivalent XML elements and attributes despite or a possibly different serialization into text. But what is really sweet is that ProjectMerge can be told if the order of elements matters, and by which attributes equivalent elements are determined. In the case of Visual Studio project files, this attribute is the Include attribute of Compile, Content, None, and Reference elements. In effect, ProjectMerge will be able to understand these instructions are equivalent:

<Compile Include="1.cs" />
<Compile Include="2.cs" />
<Compile Include="3.cs" />

<!-- and -->

<Compile Include="3.cs" />
<Compile Include="2.cs" />
<Compile Include="1.cs" />

The necessary settings to understand a specific project file format are stored in a distributable file format. Here’s my (probably not very complete) shot at C# projects – download here (from my Dropbox) and store under %PROGRAMDATA%\Oso\ProjectMerge\Formats.

I haven’t used the tool in practice yet, but I think we’re going to try it in a >10 person team over the next week, and see how it performs.

Leave a Comment

NUnit for Silverlight: Runs on Windows Phone 7, too

Yesterday I’ve hauled the “big” laptop home from work and tried plugging the unofficial port of the NUnit unit testing framework for Silverlight into Windows Phone 7. Jeff Wilcox had added support for Windows Phone 7 into his Silverlight unit test framework bis from last May already, so it’s little surprise that there was not much of a problem.

I did not compile the whole of the NUnit port to the Windows Phone 7 runtime, but use the Silverlight 3 version that references the May 2010 unit testing framework. When these assemblies are referenced, Visual Studio complains that using a Silverlight assembly could lead to unpredictable behavior, but this does not apply to this project. Firstly, the major differences are really close to the user interface, and NUnit does not render anything anywhere, it does not even reference anything Silverlight-specific types. Secondly, the whole test suite runs without errors. Compiling the NUnit framework to the WP7 runtime would be totally feasible, but any test project on WP7 needs to reference the Silverlight unit testing framework, which is a Silverlight 3 assembly. You’ll get the warnings about unpredictable behavior anyway.

The documentation on ho to run NUnit tests on Windows Phone 7 is on the Coogle Code Wiki. The project’s binaries have not changed, but I’ve added a version of the NUnit test suite for Wp7 that you can find in the code.

, ,

Leave a Comment

Follow

Get every new post delivered to your Inbox.