Web API: Introduction to SystemDiagnosticsTraceWriter

Today ASP.NET Team released 2012.2 update. One of the cool features is the support for .NET Native tracing capability.

The idea is all the trace information that is available while request flows through message handlers and formatters and find their way to the controller is now available now for you to consume.

For users starting on New Project with this update installed, this feature will be turned on automatically. See line config.EnableSystemDiagnosticsTracing in the snippet below

config.enable

How does it work?

Let’s say you have a controller called “Values” and you do http://localhost/Webapi/api/Values. When you run it in visual studio, You get this by default in tracing window of VS. Here you find all the information that passed through various layers of WebAPI pipeline.

vs.trace.output

Now what if I want this output to be redirected to say console? It is extremely simple. Just throw this in your App.Config or Web.Config file.

Below, I’m adding ConsoleTraceListener for my Console App project. (You can add file or other tracelisteners if you like)

diagnostic.config

The output is as following and it is directed to console now.

console.trace.output

That’s cool but what about internal stuff?

This whole magic is achieved when you do config.EnableSystemDiagnosticsTracing() via an extension method. Internally it looks like this.

internal_trace

SystemDiagnosticsTraceWriter is the class which makes this possible.

Here the extension method creates an instance of SystemDiagnosticsTraceWriter and then uses an extensibility feature of WebAPI to provide the implementation for ITraceWriter.

This implementation lives in an assembly called System.Web.Http.Tracing.dll which is also available as nuget package.

If you are working with earlier versions of WebAPI and cant upgrade, then just download the nuget package and then just provide implementation of ITraceWriter manually.

Web API–How to change Default Serializer settings

Out of box, Web API provides a way to serialize using JSON and XML.
In this blog post, I will describe how you can control JSON settings for the default serializer.

JSON Settings.

e.g Say you want your date to be formatted as per Microsoft format as opposed to ISO standard (for whatever reason).
All you do is get hold of the jsonFormatter and then use SerializerSettings to set DateFormatHandling.

JsonDateFormatCode

ISO Date Format for JSON

isoFormat

Microsoft Date Format for JSON.

microsoftdateFormat

By default, Json.NET will use ISO format for your date time format serialization

WebAPI: Calculating NextPageLink for ODataResult<>

Fall update of Web API introduced ODataResult<> which is used to implement OData features like Server Driven Paging and $inlinecount. I blogged about it here 

If you look at the definition of ODataResult<>, it has following properties,

1. Items Collection
2. NextPageLink property
3. Count

Essentially calculating Items and Count are no brainer once you have access to IQueryable however NextPageLink can be interesting as there is no default implementation.

In this post, I’m going to describe how to calculate NextPageLink property. Feel free to modify the code as per your requirement, it is merely one way of achieving the goal.

Basics:

If you think about Paging support, you are thinking of two possible parameters, Batch Size & Page Number.

Usually queries are done like pageNumber=1 & batchSize=2 for first page, pageNumber=2 & batchSize=2 for the next page and so on, resulting in request made for page from 1..N but your batchSize does not change.

Here is the complete code sample which shows you how to calculate the NextPageLink.

 public static Uri CalculateNextPageLink(
            HttpRequestMessage request,
            int resultLimit)
        {
            if (resultLimit <= 0)
            {
                return null;
            }

            int topValue;
            int skipValue;

            var topValueRaw = request.GetQueryNameValuePairs()
                                     .FirstOrDefault(x => x.Key == "$top");
            if (!Int32.TryParse(
                topValueRaw.Value,
                out topValue))
            {
                return null;
            }

            var skipValueRaw = request.GetQueryNameValuePairs()
                                      .FirstOrDefault(x => x.Key == "$skip");

            if (!Int32.TryParse(
                skipValueRaw.Value,
                out skipValue))
            {
                return null;
            }

            if ((topValue > resultLimit) || (topValue <= 0 || skipValue < 0))
            {
                //return the same url
                return null;
            }

            //logic
            //skip=pageIndex*pageSize
            //top=pageSize
            var stringBuilder = new StringBuilder();
            var bothSkipAndTopSupplied = (topValue != -1 && skipValue != -1);
            var lastPageIndex = skipValue/topValue;
            //int lastPageNumber = lastPageIndex+1;

            foreach (var keyValue in request.GetQueryNameValuePairs())
            {
                switch (keyValue.Key)
                {
                    case "$top":
                        stringBuilder.AppendFormat("$top={0}",(bothSkipAndTopSupplied)? topValueRaw.Value: keyValue.Value);
                        break;
                    case "$skip":
                        if ((bothSkipAndTopSupplied))
                        {
                            stringBuilder.AppendFormat("$skip={0}",((lastPageIndex + 1)*topValue));
                        }
                        else
                        {
                            stringBuilder.AppendFormat("$skip={0}",keyValue.Value);
                        }
                        break;
                    default:
                        stringBuilder.Append(Uri.EscapeUriString(keyValue.Key));
                        stringBuilder.Append("=");
                        stringBuilder.Append(Uri.EscapeDataString((keyValue.Value)));
                        break;
                }

                stringBuilder.Append("&");
            }

            stringBuilder.Remove(
                stringBuilder.Length - 1,
                1);

            return new UriBuilder(request.RequestUri)
            {
                Query = stringBuilder.ToString()
            }.Uri;
        }

Web API Fall Update – ODataResult<T>

Another great feature  Web API team has provided support for Server side paging and $inlineCount is called oDataResult<T>

It is really easy to achieve this. Earlier you could return the data but you will have to rely on other mechanisms to achieve $inlineCount.

Step1: Change your return type of the controller action to oDataResult<T>.

Assuming your resource is available at

http://localhost:8080/webapi/Accounts.

image1

Step2: Execute the request.

image2

Now you can get the count and a navigation path both.

WebAPI Fall Update 2012 : ResultLimit support on Queryable

In case you have missed this hidden gem from the update, Queryable now supports following four properties. I will cover other properties in separate blog post.

  • ResultLimit
  • StableOrdering
  • LamdaNestingLimit
  • HandleNullPropogation

Having support for ResultLimit is really great news for people using oData support in WebAPI.

This means that you can now set the upper limit on the amount of records that will be returned for a given resource when you apply Queryable attribute. This is very important step to make sure that the client of your API don’t unnecessarily run large queries therby limiting the system scalability.

If you want this capability on an existing system, ensure that oData version of API is latest. Mine is Microsoft.AspNet.WebApi.OData.0.2.0-alpha-121031\lib\net40\System.Web.Http.OData.dll. I have also applied the fall 2012 update.

Let’s say you this method on /webapi/Accounts

image1

and you perform simple Get operation /webapi.

image2

This returns count of 3.

Now add the ResultLimit property on Queryable  Attribute and set this value to 2.

image3

Let’s execute the same query again.

image4

It returns 2 as expected.

ASP.NET Web API BSON Formatter

ASP.NET Web API includes JSON.NET which includes BSON Serializer so to support BSON format it is quite easy.

  1. Create a class called BSONFormatter and inherit from MediaTypeFormatter
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;

namespace WebAPI.Common.Formatter
{
     public class BsonFormatter : MediaTypeFormatter
    {
        public BsonFormatter()
        {
            SupportedMediaTypes
             .Add(new MediaTypeHeaderValue("application/bson"));
        }

        public MediaTypeHeaderValue MediaTypeHeaderValue
        {
            get 
           { 
               return new MediaTypeHeaderValue("application/bson"); 
            }
        }

        public override bool CanReadType(Type type)
        {
            return false;
        }

        public override bool CanWriteType(Type type)
        {
            return true;
         }

2. Make sure you include following namespace

using

3. Override WriteToStreamAsync method, using BsonWriter by passing the stream and using JsonSerializer to serialize.

public override Task WriteToStreamAsync(Type type,
 object value, Stream writeStream, HttpContent content,
            TransportContext transportContext)
        {
            Task task = Task.Factory.StartNew(
                () =>
                    {
                        content.Headers.ContentType = MediaTypeHeaderValue;
                        var serializer = new JsonSerializer();

                        // serialize product to BSON
                        var writer = new BsonWriter(writeStream);
                        serializer.Serialize(writer, value);
                    });

            return task;
        }

4. Plugging it in Web.API pipeline.

  var config = new HttpSelfHostConfiguration("http://localhost:8080");
    ILifetimeScope container = GetContainerLifeTimeScope();
     config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "WebAPI/{controller}/{id}",
                defaults: new {id = RouteParameter.Optional}
                );

      config.Formatters.Add(new BsonFormatter());
    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
      using (var server = new HttpSelfHostServer(config))
      {
                server.OpenAsync().Wait();
                Console.WriteLine("Press any key to terminate");
                Console.ReadLine();
        }

5. Bson Request/Response (in fiddler)

BsonRequest BsonResponse