Monday 12 February 2018

WebApi controller - using anonymous types as return values

On a couple of projects I’ve worked on recently both have used anonymous objects to return data to a SPA UI as Json from WebApi controllers.

Microsofts recommended return type for WebApi controller methods is IHttpActionResult and they provide a variety of the helper methods to make the creation of the response easy e.g. Ok(), BadRequst(), etc

To return an anonymous object as Json is as easy as using the Json method and create the anonymous object as the parameter to the method:

public IHttpActionResult Get()
{
    return Json(new { id = 1 });
}

Why do this?

There is a real advantage in using this technique as it gives you a lot of flexibility in what you return, there is no need to define different view model classes for each "shape" of data you need so it cuts down on the amount of boiler plate code needed.

Unit Testing

Not everybody writes unit tests for controller methods which return data but if you do the use of IHttpActionResult as a return type makes it a little trickier than you would anticipate to be able to look at the Json returned.

If you try to use the strongly typed classes (IHttpActionResult, HttpContent, etc) you'll most likely find yourself going down a rabbit hole trying to get to the content of the response which eventually either leads to having to use reflection to get the data or using dynamic.

However, if we take a short cut and make use of dynamic straight away we can vastly simplify the code which gives us a test that looks like this:

[Test]
public void Should_return_id_in_reponse()
{
    var target = new DataController();
 
    dynamic response = target.Get();
 
    dynamic content = response.Content;
 
    Assert.That(content.id, Is.EqualTo(1));
}

By setting the return from the controller method to dynamic we avoid the need to explicitly call ExecuteAsync and using dynamic to access the content makes it easier for us to get hold of the content without the need for any Json wrangling.

At this point if you've created the test in the same assembly as the controller it will pass - success!

But, if you've created your test in a separate project when you run the test you'll get an error thrown when trying to check the id in the assert:

'object' does not contain a definition for 'Content'

If you've used dynamic before chances are you'll have seen this error previously as it occurs anytime you try to access a property that it isn't able to find or access on the type that has been returned.

At this point you might be scratching your head wondering why since you're able to access the content and you know exactly what should be in it, especially if you debug the test as you'll see the anonymous object created correctly.

As it turns out the reason you get this error is that when c# constructs an anonymous object at runtime it creates it as an internal class and becauses its internal the project holding the tests will not be allowed to access its properties.

To remedy this all you need to do is to go into the WebApi project and add an attribute to its AssemblyInfo.cs:

[assemblyInternalsVisibleTo("insert the name of your test assembly here")]

doing this now allows your test project to see internal classes in your WebApi project, if you run the test again it will pass.

Code

A repo with code for this can be found here if you want to see it in action

No comments:

Post a Comment