Encoding Kockerbeck

Friday, February 17, 2012

Applying WebInvoke without an Attribute

Haven't posted in a long time but thought I'd post this bit of WCF extensibility I was working on at home. There is nothing on the Internet about how to do this & I felt I should figure something out.

Scenario: You have legacy WCF services that you want to switch to a WebHttpBinding w/ JSON but don't want to update 1,337 service contracts to include a WebInvoke attribute, UriTemplates and Request/Response Body Types

In this scenario I'll be assuming use of IIS and Windows Process Activation Services to host your service.

The WebInvokeAttribute implements IOperationBehavior. So what we need to do is somewhere in the WCF stack apply this operation behavior programmatically to our Endpoint's Contract.Operations. Let's start from the bottom up & take a look at an extension method that does this.


/// <summary>
/// Automatically applies the Web Invoke behavior to all operations in the collection
/// </summary>
/// <param name="operations"></param>
public static void ApplyWebInvoke(this System.ServiceModel.Description.OperationDescriptionCollection operations)
{
foreach (var operation in operations)
{
var webInvoke = new System.ServiceModel.Web.WebInvokeAttribute
{
BodyStyle = System.ServiceModel.Web.WebMessageBodyStyle.Wrapped,
Method = "POST",
RequestFormat = System.ServiceModel.Web.WebMessageFormat.Json,
ResponseFormat = System.ServiceModel.Web.WebMessageFormat.Json,
UriTemplate = operation.Name
};

operation.Behaviors.Add(webInvoke);
}
}

In the above extension method I'm forcing everything to be a POST, everything to use JSON and keeping the UriTemplates simply as the Operation name. Now where do we call this method? Personally, I'm a fan of having custom ServiceHosts + ServiceHostFactories rather than BehaviorExtensions. Chances are it's OK for me to touch the code & hosting portion of the application that I don't need to rely solely on configuration to extend WCF. If I have to extend a service that I don't have access to the ServiceHost -- behavior extensions are the only way. But given the choice -- custom ServiceHostFactory every time. The biggest reason is that I find other developers can follow the stack easier. It's less confusing. "Oh this SVC file has a custom factory? Oh this custom factory applies this behavior! "

Now take a look at the Factory and the custom Service Host:

public class WebInvokeServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory
{
protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new WebInvokeServiceHost(serviceType, baseAddresses).ApplyWebInvoke();
}
}

public class WebInvokeServiceHost : System.ServiceModel.Web.WebServiceHost
{
public WebInvokeServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses) { }

internal WebInvokeServiceHost ApplyWebInvoke()
{
foreach(var endpoint in Description.Endpoints)
{
endpoint.Contract.Operations.ApplyWebInvoke();
}

return this;
}
}


I'm applying the WebInvoke behavior after the ServiceHost has been instantiated. Now just update the .svc file to use the new Factory:

<%@ ServiceHost Service="MySite.Services.TestService" Factory="MySite.ServiceModel.WebInvokeServiceHostFactory" %>


As for the client...
Given our scenario of working with a legacy application, I'll start by assuming we aren't auto-generating Service References. So we just need to apply the same IOperationBehavior to the Client Endpoint's Contract.Operations just like the Service. Below is a sample client base.

public class SampleClientBase : ClientBase<ITestService>, ITestService
{
public SampleClientBase(string config) : base(config) { }

public SampleClientBase() : this("TestService")
{
ChannelFactory.Endpoint.Behaviors.Add(new WebHttpBehavior());

ChannelFactory.Endpoint.Contract.Operations.ApplyWebInvoke();
}

public SomeObject BasicTest(SomeObject someObject)
{
return Channel.BasicTest(someObject);
}
}


And that's all there is to it! Hopefully now you see why that loop is an extension method and just how easy it really is to switch over to WebHttp from NetTcp without having to update your contracts.

Monday, September 27, 2010

MapReduce in C# using Task Parallel Library

Back in August I starting playing with a C# implementation of Google's MapReduce algorithm. The implementation was based on something Stephan Brenner did, although I completely refactored it.

Today I added a little bit of logic to split up the actual execution of Map & Reduce in this implementation using the Task Parallel Library in .NET 4.0. Check out the source code for MapReduce in C# on GitHub. Below is an excerpt from the Tests on how to implement the library.

Counting Words in Files


public static List<KeyValuePair<string, int>> Map(FileInfo document, string text)
{
var items = text.Split('\n', ' ', '.', ',','\r');
return items.Select(item => new KeyValuePair<string, int>(item, 1)).ToList();
}


public static List<int> Reduce(string word, List<int> wordCounts)
{
if (wordCounts == null) return null;

var result = new List<int> { 0 };

foreach (var value in wordCounts)
{
result[0] += value;
}

return result;
}

public static void Main()
{
var fileSearchData = new Dictionary<FileInfo, string>();
di.GetFiles().ToList().ForEach(f => fileSearchData.Add(f, File.ReadAllText(f.FullName)));

var output = MapReduce.Execute(Map, Reduce, fileSearchData);

Console.WriteLine(output["needle"][0]);
}


Let me know if you have any questions or want to share any implementation concerns.

Thursday, August 5, 2010

Require comment / message on all SVN commits for Visual SVN Server

(I've been posting a lot the last few days!)

Something that drives me nuts is not having a comment or message when looking at SVN history. It's impossible to know what went on. Even a bad comment is better than no comment.

SVN hooks are a great way to solve this. If we make a pre-commit.bat file that checks the commit data for a comment, returns an error code of 1 if it wasn't found (with some text) or returns 0 if the comment is there.

Check out the pre-commit hook that I found online (thanks, Anuj Gakhar!) or check out the hook below.


@echo off
setlocal

rem Subversion sends through the path to the repository and transaction id
set REPOS=%1
set TXN=%2

rem check for an empty log message
svnlook log %REPOS% -t %TXN% | findstr . > nul
if %errorlevel% gtr 0 (goto err) else exit 0

:err
echo. 1>&2
echo A log message or comment is required to commit 1>&2
exit 1


This works perfectly except for 1 downfall -- you have to distribute it in every freaking repository! Below is the windows batch solution I came up with to solve that problem. Pretty straight forward, but thought I'd share for those less inclined to dig through archaic windows batch programming references.

Just create a batch file of the following & schedule it to run however often you want.


@ECHO OFF
:: ------ SVN Deploy Hooks ------
:: ----- by Mark Kockerbeck ----

:: ------ Configuration ---------
SET HookDirectory=C:\CommonHooks
SET RepositoryDirectory=C:\Repositories
:: ------ /Configuration --------

pushd %HookDirectory%
for /r %%i in (*) do (
for /D %%j in (%RepositoryDirectory%\*) do (
echo Copying %%i to %%j\hooks\
copy %%i %%j\hooks\
)
)
popd