I have had a situation arise today where I need to construct a complex type from a source of a NameValueCollection. A little while back I submitted a patch for the Agatha Project to include REST (JSON and XML) support for the service contract. I realized today that as useful as it is, it did not actually support true REST conformance, as REST should support GET so that you can use JSONP from JavaScript directly meaning you can query cross domain services. My original implementation for POX and JSON used the POST method and this immediately rules out JSONP as from reading, JSONP only works with GET Requests. This then raised another issue. The current operation contract of Agatha and one of its main benefits is that you can supply an array of Request objects in a single request, limiting the about of server requests you need to make. Now, at the present time I am thinking that this will not be the case for the REST imlementation but will yield the benefits of the fact that : The same Request objects can be used for SOAP and RST (POX, JSON) The construct of the JavaScript functions will be simpler and more readable It will enable the use of JSONP for cross domain REST Services The current contract for the Agatha WcfRequestProcessor is at time of writing the following: [ServiceContract]
public interface IWcfRequestProcessor
{
[OperationContract(Name = "ProcessRequests")]
[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
[TransactionFlow(TransactionFlowOption.Allowed)]
Response[] Process(params Request[] requests);
[OperationContract(Name = "ProcessOneWayRequests", IsOneWay = true)]
[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
void ProcessOneWayRequests(params OneWayRequest[] requests);
}
My current proposed solution, and at the very early stages of my concept is as follows:
[ServiceContract]
public interface IWcfRestJsonRequestProcessor
{
[OperationContract(Name="process")]
[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
[TransactionFlow(TransactionFlowOption.Allowed)]
[WebGet(UriTemplate = "process/{name}/{*parameters}", BodyStyle = WebMessageBodyStyle.WrappedResponse, ResponseFormat = WebMessageFormat.Json)]
Response[] Process(string name, NameValueCollection parameters);
[OperationContract(Name="processoneway",IsOneWay = true)]
[ServiceKnownType("GetKnownTypes", typeof(KnownTypeProvider))]
[WebGet(UriTemplate = "process-one-way/{name}/{*parameters}", BodyStyle = WebMessageBodyStyle.WrappedResponse, ResponseFormat = WebMessageFormat.Json)]
void ProcessOneWayRequests(string name, NameValueCollection parameters);
}
Now this part I have not yet implemented, it is the preliminart step which I have developed which will allow me to take the name of the Request Type and the NameValueCollection and construct the complex type which is that of the Request which I can then supply to a nested instance of the original IWcfRequestProcessor and work as it should normally. To give an example of some of the urls which you I envisage with this method are:
http://www.url.com/service.svc/json/process/getweather/?location=london
http://www.url.com/service.svc/json/process/getproductsbycategory/?categoryid=1
http://www.url.om/service.svc/json/process/sayhello/?name=andy
Another reason why my direction has gone to a single request for the REST implementation is because of restrictions which are imposed by browsers on the length of the url. From what I have read this is on average 2000 characters. I think that this is a very acceptable usage limit in the context of using 1 request, but I do not think this is acceptable for accommodating multiple requests chained together. I would love to be corrected on that one, I really would but unfortunately from what I have read I have come to the conclusion that this is not the case.
The mapping function
So, as I say this is just the first pass I have made at this, and I am not overly happy with the try catch for detecting types without default constructors. I know there is a better way but for the minute, it escapes me. I would also like to know the correct way for adding mapping functions and not using the anonymous way that I have used. To achieve this I have used recursion which I am sure is what other mapping function use. As you do have to go as deep as the complex type is.
public static object RecurseType(NameValueCollection collection, Type type, string prefix)
{
try
{
var returnObject = Activator.CreateInstance(type);
foreach (var property in type.GetProperties())
{
foreach (var key in collection.AllKeys)
{
if (String.IsNullOrEmpty(prefix) || key.Length > prefix.Length)
{
var propertyNameToMatch = String.IsNullOrEmpty(prefix) ? key : key.Substring(property.Name.IndexOf(prefix) + prefix.Length + 1);
if (property.Name == propertyNameToMatch)
{
property.SetValue(returnObject, Convert.ChangeType(collection.Get(key), property.PropertyType), null);
}
else if(property.GetValue(returnObject,null) == null)
{
property.SetValue(returnObject, RecurseType(collection, property.PropertyType, String.Concat(prefix, property.PropertyType.Name)), null);
}
}
}
}
return returnObject;
}
catch (MissingMethodException)
{
//Quite a blunt way of dealing with Types without default constructor
return null;
}
}
Another thing is performance, I have not measured this in anyway, it is as I say the first pass, so I hope this can be the start of a more perfected implementation. I tested this out with a complex type of three levels, there is no intended logical meaning to the properties, they are simply for the purposes of example. You could call this a spiking session, as from here on in, now I know what I am building I would take a more TDD approach. OK, purists, why did I not do this from the start, well I didn’t, this was a brain dump and now I know what I am building I can.
The console test and how I used with AutoMapper is as follows:
static void Main(string[] args)
{
var collection = new NameValueCollection();
collection.Add("Name", "Andrew Rea");
collection.Add("Number", "1");
collection.Add("AddressLine1", "123 Street");
collection.Add("AddressNumber", "2");
collection.Add("AddressPostCodeCountry", "United Kingdom");
collection.Add("AddressPostCodeNumber", "3");
AutoMapper.Mapper.CreateMap<NameValueCollection, Person>()
.ConvertUsing(x =>
{
return(Person) RecurseType(x, typeof(Person), null);
});
var person = AutoMapper.Mapper.Map<NameValueCollection, Person>(collection);
Console.WriteLine(person.Name);
Console.WriteLine(person.Number);
Console.WriteLine(person.Address.Line1);
Console.WriteLine(person.Address.Number);
Console.WriteLine(person.Address.PostCode.Country);
Console.WriteLine(person.Address.PostCode.Number);
Console.ReadLine();
}
Notice the convention that I am using and that this method requires you do use. Each property is prefixed with the constructed name of its parents combined. This is the convention used by AutoMapper and it makes sense.
I can also think of other uses for this including using with ASP.NET MVC ModelBinders for creating a complex type from the QueryString which is itself is a NameValueCollection.
Hope this is of some help to people and I would welcome any code reviews you could give me.
References:
Agatha : http://code.google.com/p/agatha-rrsl/
AutoMapper : http://automapper.codeplex.com/
Cheers for now,
Andrew
P.S. I will have the proposed solution for a more complete REST implementation for AGATHA very soon.