OWIN Self-Hosted Test Server for Integration Testing of OData and Web API

A co-worker of mine and I were recently given a task to perform integration testing on OData and Web API services. You can view his posts on the subject in his series: Part 1, Part 2, and Part 3. Traditionally, one might mock the web requests and responses, but by using the TestServer found in Microsoft.Owin.Testing namespace, we can start an in-memory HTTP server for doing full integration tests. You can get the NuGet package here.

To start create a new Unit Testing project with MS Test and a new ASP.NET MVC / Web API project. In the ASP.NET MVC / Web API project install Web API 2.2 and Web API 2.2 for OData v4.0 and OData v1-3. For the unit test project, install Web API 2.2, Web API 2.2 for OData v4.0 and OData v1-3, Web API 2.2 OWIN Self Host, Web API 2.2 OWIN, and Microsoft.Owin.Testing. Below is a sample of a unit/integration test for setting up an OWIN test server for in-memory integration testing:

namespace SelfHosting.Test
{
    using Microsoft.Owin.Testing;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Owin;
    using SelfHosting.Test.Models;
    using System;
    using System.Linq;
    using System.Net.Http;
    using System.Threading.Tasks;
    using System.Web.Http;
    using System.Web.Http.Dispatcher;
    using System.Web.OData.Builder;
    using System.Web.OData.Extensions;
    using WebApp.Models;

    [TestClass]
    public class SelfHostingTest
    {
        protected TestServer server;

        [TestInitialize]
        public void Setup()
        {
            server = TestServer.Create(app =>
            {
                HttpConfiguration config = new HttpConfiguration();
                WebAppFacade.WebApiConfig.Register(config);
                app.UseWebApi(config);
            });
        }

        [TestCleanup]
        public void TearDown()
        {
            if (server != null)
                server.Dispose();
        }

        [TestMethod]
        public async Task TestODataMetaData()
        {
            HttpResponseMessage response = await server.CreateRequest("/odata/?$metadata").GetAsync();

            var result = await response.Content.ReadAsAsync<ODataMetaData>();

            Assert.IsTrue(result.value.Count > 0, "Unable to obtain meta data");
        }

        [TestMethod]
        public async Task TestWebApi()
        {
            HttpResponseMessage response = await server.CreateRequest("/api/values").GetAsync();

            var result = await response.Content.ReadAsStringAsync();

            Assert.AreEqual("\"Hello from foreign assembly!\"", result, "/api/values not configured correctly");
        }

        [TestMethod]
        public async Task TestODataPeople()
        {
            HttpResponseMessage response = await server.CreateRequest("/odata/People").GetAsync();

            var result = await response.Content.ReadAsAsync<ODataResponse<Person>>();

            Assert.AreEqual(result.value.Count, 3, "Expected 3 people to return from /odata/People");
        }
    }

}

The OData meta data is serialized into a POCO (Plain old C# object):

namespace SelfHosting.Test.Models
{
    using System.Collections.Generic;

    public class ODataMetaData
    {
        public string odatacontext { get; set; }
        public List<Value> value { get; set; }
    }

    public class Value
    {
        public string name { get; set; }
        public string kind { get; set; }
        public string url { get; set; }
    }
}

By using a generic ODataResponse class, we can deserialize our OData response into any POCO:

namespace SelfHosting.Test.Models
{
    using System.Collections.Generic;

    public class ODataResponse<T>
        where T : class, new()
    {
        public string odatacontext { get; set; }
        public List<T> value { get; set; }

        public ODataResponse()
        {

        }
    }
}

The beauty about using the TestServer is that it is self-contained and the HTTP server is inaccessible outside of the process. Once the tests complete, the server is shutdown. The WebApiConfig registered with the TestServer determines which controllers and routes to load for testing. No production code needs to be changed in order to test existing Web API and OData controllers. The only problem that I have found is that attribute routes don't seem to register correctly. Perhaps I have not found the correct method of registering the attribute routes for the TestServer.

Here is the Visual Studio 2013 solution with both a web project and a unit testing project:

SelfHostingUnitTest.zip (1.39 mb)

TFS Conflicts Take Source Resolve

7. October 2014 14:17 by Cameron in Programming  //  Tags: , , ,   //   Comments

Today, I had to merge a bunch of items from one branch to another but had about 350+ conflicts and manually accepting the Take Source wasn't going to cut it. I found that if you open up the Visual Studio Command Prompt, you have access to the tf tool which allows you to work with TFS from a commandline interface. After changing to my solution's directory, the following command allowed me to merge my changes automatically by resolving to take the source branch:

tf resolve /auto:TakeTheirs

After about a minute or two, I was able to see that all conflicts were resolved and I checked in my changes. This is a huge time saver as it would have taken me several magnitudes longer to resolve them manually.

Knockout Observable Array Performance Improvments

Today, I was working on a project that makes AJAX calls to an API and noticed that the application was very slow and unresponsive during the AJAX calls and ko updates. I checked that the requests weren't taking too long in Chrome's developer tools and found they were at max taking 300ms and minimum of 20ms to return. This was very odd behavior since the AJAX requests weren't blocking.

After some research, I found that when updating ko.observableArrays, you don't want to push each individual item to the array if you can help it. Each time an item is pushed to a ko.observableArray, it notifies its subscribers and depending on how many DOM elements you have on your page, that can be rather taxing. I had code like this:

$.ajax({
	url: 'api/Document/',
	method: 'GET',
	dataType: 'json',
	success: function(data) {
		Documents([])
		for(var i = 0; i < data.length; i++)
			Documents.push(data[i]);
	}
});

To fix this, you need to create temporary arrays to push your items to and then assign the ko.observableArray to the contents of the temporary array. Your code should look more like this:

$.ajax({
	url: 'api/Document/',
	method: 'GET',
	dataType: 'json',
	success: function(data) {
		var documents = [];
		for(var i = 0; i < data.length; i++)
			documents.push(data[i]);	

		Documents(documents);
	}
});

Gamer Footprint Update

4. March 2014 11:27 by Cameron in Gamer Footprint, Programming  //  Tags: , ,   //   Comments

Some of you may be wondering what is the current status of Gamer Footprint? Development is live and well! In fact, there is a slightly functional proof of concept available to test at https://www.gamerfootprint.com. I have also bought the domain www.gamrfp.com but this domain doesn't have any SSL configured yet. I mainly bought it for a shorter domain.

Please do note that the website is rapidly changing with new features several times a week and I sometimes have to clear out the database when my domain model changes or I find bugs that cause duplicate data. Therefore, the site is not 100% stable yet. I'm working very hard on making the user experience enjoyable. Some features that have been implemented are:

  • User profiles
    • The profiles are very basic right now, but I plan to implement a full timeline-like profile that includes a gamer's events such as played games or achievements/trophies
    • Your avatar is currently pulled from Gravatar and if you don't have a Gravatar, you should get one! It's a very nice service to have the same avatar across multiple websites. If no Gravatar can be found for your account email, it will display a generic image in place of your avatar.
    • If you have entered a PlayStation Network and/or an Xbox LIVE account, your profile will show your current presence on PlayStation Network and Xbox LIVE
    • Your information is safe and secured and transmitted over SSL. You only have to login once for your PSN or Xbox LIVE accounts. We don't store any passwords for accounts on our servers. I'm working on providing a method for not requiring account credentials, but you won't be able to send messages. If messaging your Xbox LIVE or PSN friends from a PC is important, you'll need to enter your account credentials.
  • Xbox LIVE games and achievements (Xbox One games and challenges support to come soon!)
  • Data APIs that directly interface with Xbox LIVE, PlayStation Network, and Steam
    • Get Xbox and Xbox One games, achievements, challenges, friends, presence and profile information
    • Get PlayStation games, trophies, friends, presence information and profile information - supports PSVITA, PS3, and PS4
    • These APIs have not yet been made public, but they should be available very soon. I will post documentation on how to request an API key and API endpoints for PSN and Xbox LIVE. I will not provide a data API for Steam since Valve provides their own and I don't want to use up my daily requests to the API with a wrapper API. There might be a way to let developers aggregate everything from all supported game networks (including Steam), but it would involve providing individual Valve issued API keys and I don't want to do that as it might be against their terms of service.
Planned features:
  • PlayStation Network games and trophies
    • The core for interfacing with PSN has been finished, but I have yet to write the data access layer and data persistence layer for this part
  • Timeline system - user level and global level
  • Message friends on Xbox LIVE and PSN from the website
  • Invite people via Xbox LIVE or PSN message
  • and much more!
Here are some preview screenshots:
 

On-the-Fly Windows Impersonation with C#

6. February 2014 09:37 by Cameron in Programming  //  Tags:   //   Comments

Occasionally I need to perform tasks under another user so I created a nice wrapper class that allows me to impersonate as another user on the fly:

using System;

using System.Runtime.InteropServices;

using System.Security.Permissions;

using System.Security.Principal;

using Microsoft.Win32.SafeHandles;

using System.Runtime.ConstrainedExecution;

using System.Security;

using System.Configuration;



namespace Tinksoft.Tools

{

    /// <summary>

    /// Facilitates impersonation of a Windows User.

    /// </summary>

    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]

    public class Impersonation : IDisposable

    {

        public string Environment { get; set; }



        public string UserName { get; set; }



        public string Password { get; set; }



        public string DomainName { get; set; }



        public enum LogonType

        {

            Interactive = 2,

            Network = 3,

            Batch = 4,

            Service = 5,

            Unlock = 7,

            NetworkClearText = 8,

            NewCredentials = 9

        }



        public enum LogonProvider

        {

            Default = 0,

            WinNT35 = 1,

            WinNT40 = 2,

            WinNT50 = 3

        }



        /// <summary>

        /// Windows Token.

        /// </summary>

        private readonly SafeTokenHandle _handle;



        /// <summary>

        /// The impersonated User.

        /// </summary>

        private WindowsImpersonationContext impersonatedUser;



        public Impersonation()

        {

        }



        /// <summary>

        /// Initializes a new instance of the Impersonation class. Provides domain, user name, and password for impersonation.

        /// </summary>

        /// <param name="domainName">Domain name of the impersonated user.</param>

        /// <param name="userName">Name of the impersonated user.</param>

        /// <param name="password">Password of the impersonated user.</param>

        /// <remarks>

        /// Uses the unmanaged LogonUser function to get the user token for

        /// the specified user, domain, and password.

        /// </remarks>

        public Impersonation(AccountCredentials credentials)

        {            

            string[] splitName = WindowsIdentity.GetCurrent().Name.Split('\\');

            string name = (splitName.Length > 0) ? splitName[0] : null;



            LogonType logonType = LogonType.Interactive;

            LogonProvider logonProvider = LogonProvider.Default;



            if (name != credentials.Domain)

            {

                logonType = LogonType.NewCredentials;

                logonProvider = LogonProvider.WinNT50;

            }



            // Call LogonUser to obtain a handle to an access token.

            bool returnValue = LogonUser(

                                credentials.UserName,

                                credentials.Domain,

                                credentials.Password,

                                (int)logonType,

                                (int)logonProvider,

                                out this._handle);



            if (false == returnValue)

            {

                // Something went wrong.

                int ret = Marshal.GetLastWin32Error();

                throw new System.ComponentModel.Win32Exception(ret);

            }



            this.impersonatedUser = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle());    

        }



        /// <summary>

        /// Initializes a new instance of the Impersonation class. Provide domain, user name, and password for impersonation.

        /// </summary>

        /// <param name="domainName">Domain name of the impersonated user.</param>

        /// <param name="userName">Name of the impersonated user.</param>

        /// <param name="password">Password of the impersonated user.</param>

        /// <remarks>

        /// Uses the unmanaged LogonUser function to get the user token for

        /// the specified user, domain, and password.

        /// </remarks>

        public Impersonation(string domainName, string userName, string password)

        {

            string[] splitName = WindowsIdentity.GetCurrent().Name.Split('\\');

            string name = (splitName.Length > 0) ? splitName[0] : null;



            LogonType logonType = LogonType.Interactive;

            LogonProvider logonProvider = LogonProvider.Default;



            if (name != domainName)

            {

                logonType = LogonType.NewCredentials;

                logonProvider = LogonProvider.WinNT50;

            }



            // Call LogonUser to obtain a handle to an access token.

            bool returnValue = LogonUser(

                                userName,

                                domainName,

                                password,

                                (int)logonType,

                                (int)logonProvider,

                                out this._handle);



            if (false == returnValue)

            {

                // Something went wrong.

                int ret = Marshal.GetLastWin32Error();

                throw new System.ComponentModel.Win32Exception(ret);

            }



            this.impersonatedUser = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle());

        }



        [DllImport("advapi32.dll", SetLastError = true)]

        private static extern bool LogonUser(

                string lpszUsername,

                string lpszDomain,

                string lpszPassword,

                int dwLogonType,

                int dwLogonProvider,

                out SafeTokenHandle phToken);



        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]

        private static extern bool CloseHandle(IntPtr handle);



        public void Dispose()

        {

            this.impersonatedUser.Dispose();

            this._handle.Dispose();

        }



        private static string[] GetAccountInfo(string accountInfo)

        {

            return accountInfo.Split(' ');

        }

    }



    public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid

    {

        private SafeTokenHandle()

            : base(true) { }



        [DllImport("kernel32.dll")]

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

        [SuppressUnmanagedCodeSecurity]

        [return: MarshalAs(UnmanagedType.Bool)]

        private static extern bool CloseHandle(IntPtr handle);



        protected override bool ReleaseHandle()

        {

            return CloseHandle(handle);

        }

    }

}
 

Happy New Year 2014!

6. January 2014 22:48 by Cameron in Programming  //  Tags:   //   Comments

I know I'm a few days late for posting on New Year's day, but I was away in a cabin in northern California with limited Internet. 2014 is a new year and I plan to write at least one blog post a month. Last year, I didn't write a post each month, but I don't want to become uninterested in blog writing and learning new technologies. The best action a developer can take for his/her career is learning new skills - constantly. The world of technology is forever changing and evolving and those who sit complacently will fall behind and lose their competitive edge. Each year, as new students graduate college, they will be sharper and brighter with the newest tech in the industry. Anyone who fails to embrace new technologies will not survive in the coming wave of millenials entering the workforce, eventually replacing the vast majority of the workforce. People who fail to think about the future will sadly not have a future in IT. If you're in your mid to late 30's and haven't picked up a new skill lately, it's time to do so. Time is of the essence and the future of IT lies in the hearts and minds of the millenials and their children.

Neo4j and SQL Server 2008 R2

Lately, I've been researching graph databases (NOSQL) as an alternative to traditional relational databases. The performance increase from a graph database compared to a relational database is phenomenal. To get my website rolling, I'm planning on using SQL Server 2008 R2 with the Microsoft Entity Framework. Once the data models are in place, I can then work on writing a SQL to graph database server application for migrating my data model into a graph database. There are several .NET libraries available for interfacing with Neo4j's REST API and the data migration should be trivial. My database migration tool will consist of using the Entity Framework to connect to SQL Server 2008 R2 and one of the .NET frameworks for manipulating Neo4j's REST API. I'll continue to post updates as they become available.

Unity 3D Game Project

5. March 2012 13:11 by Cameron in Programming  //  Tags: , , , , , , , ,   //   Comments

This semester, at Louisiana State University, I'm taking a video game design class and our goal is to make a 3D stereoscopic game using the Unity 3D engine. I'm working with a group of students from the University of Illinois at Chicago and one other member from LSU. So far, it's proven to be a very interesting class. We've pitched our storyboard ideas in front of our peers and our professors to get a good sense of what we should do next. Ultimately, we decided on doing a hang gliding game influenced by the PilotWings series. I have always loved the Pilot Wings games and I can remember the hours of fun I used to have playing PilotWings for the SNES. Today, I have Pilot Wings Resort for the Nintendo 3DS and thoroughly enjoy the game play. Our game, while having some similarities to Pilot Wings, will be unique and have its own twist. We're still working out all of the logistics of the game play, but we have some good ideas thus far.

I've been focusing on simulating hang glider flight physics using Unity 3D's physics engine and later I will help with composing the soundtrack of the game. I've setup a prototype of the game where the player is positioned in the middle of a canyon and has the ability to tilt left and right as well as climb and descend. I'm still working on making the movement more fluid, but the basic concept of flight is represented in the prototype. My colleagues are working on randomizing the terrain and managing the world objects for powerups and obstacles.

Once we have a presentable prototype, it will be fun to compose the soundtrack for the game. I've been playing piano for the past 15 years and I love to compose my own music. Some of the songs I compose are strictly solo piano while others have multiple parts to add depth to the sound.

I will keep this blog updated as more progress is made on the game project front. I'll also be sure to upload some images of the prototype at some point.

Simple Options Parser for PhantomJS

18. November 2011 14:33 by Cameron in javascript, PhantomJS, Programming  //  Tags: , , ,   //   Comments

Recently I needed a way to parse command line options with PhantomJS. I didn't see anything else on the web that allowed for abitrary ordering of command line arguments to PhantomJS scripts so I made my own. Here's the code for those interested:

// argument results
var a1, a2, a3, a4;

function optionParser() {	
	var opt = 0;
	while((opt < phantom.args.length) && (phantom.args[opt][0]=='-')) {
		var sw = phantom.args[opt];
		switch(sw) {
			case '-a1':
				opt++;
				a1 = phantom.args[opt];
				break;
			case '-a2':
				opt++;
				a2 = phantom.args[opt];
				break;
			case '-a3':
				opt++;
				a3 = phantom.args[opt];
				break;
			case '-a4':
				opt++;
				a4 = phantom.args[opt];
				break;
			default:
				console.log('Unknown switch: ' + phantom.args[opt]);
				phantom.exit();
				break;
		}
		opt++;
	}
}

This can easily be modified to work with an array of argument results or you can simply read in each argument into its own variable. Also, you can read in integers and in your application logic, use isNaN() to check if the input is a valid integer.

Take Screenshot of all HTML documents in a folder using PhantomJS

26. September 2011 01:14 by Cameron in javascript, PhantomJS, Programming  //  Tags: , , , ,   //   Comments

Recently I came across a question on stackoverflow that asked about how to take screenshots of all HTML files in a local folder. I have been playing with PhantomJS quite a bit lately and felt comfortable answering the question. Here is the code for those interested:

var page = require('webpage').create(), loadInProgress = false, fs = require('fs');
var htmlFiles = new Array();
console.log('working directory: ' + fs.workingDirectory);
var curdir = fs.list(fs.workingDirectory);

// loop through files and folders
for(var i = 0; i< curdir.length; i++)
{
	var fullpath = fs.workingDirectory + fs.separator + curdir[i];
	// check if item is a file
	if(fs.isFile(fullpath))
	{
		if(fullpath.indexOf('.html') != -1)
		{
			// show full path of file
			console.log('File path: ' + fullpath);
			htmlFiles.push(fullpath);
		}
	}
}

console.log('Number of Html Files: ' + htmlFiles.length);

// output pages as PNG
var pageindex = 0;

var interval = setInterval(function() {
	if (!loadInProgress && pageindex < htmlFiles.length) {
		console.log("image " + (pageindex + 1));
		page.open(htmlFiles[pageindex]);
	}
	if (pageindex == htmlFiles.length) {
		console.log("image render complete!");
		phantom.exit();
	}
}, 250);

page.onLoadStarted = function() {
	loadInProgress = true;
	console.log('page ' + (pageindex + 1) + ' load started');
};

page.onLoadFinished = function() {
	loadInProgress = false;
	page.render("images/output" + (pageindex + 1) + ".png");
	console.log('page ' + (pageindex + 1) + ' load finished');
	pageindex++;
}

The process is quite simple. First, I loop through all objects in the current working directory and check to see if each item is a file and whether it has the .html extension. Then I add each html file's filepath to an array that I later loop through to take the screenshots. A screenshot must be taken after the page is fully loaded so that the screenshot will contain actual content and not a blank image. This is done by saving the image on the page.onLoadFinished callback. The application loop for taking the screenshots inserts small 250ms delays between each request so that pages may fully load into the headless browser before advancing to the next page.

Month List

Tag cloud