Realtime ASP.NET Web API tracing with SignalR

Β· 769 words Β· 4 minutes to read

The latest ASP.NET Fall Update, released recently, introduced the Web API tracing package into the Web API core. It is the default implementation of ITraceWriter, built around the System.Diagnostics, allowing you to easily trace what’s going on inside your API and output that into the debugger’s console.

On this very blog, we discussed a similar approach a couple of months ago already, by building an NLog powered trace writer for your Web API.

Today, let’s take all this a step further, and build something much more exciting - a realtime Web API trace, powered by SignalR.

The concept of realtime log πŸ”—

The idea is to implement a custom version of ITraceWriter, around a SignalR hub. This will then be plugged into the Web API global services and used by the internal Web API procesess to trace its activites.

You can then navigate to a webpage on which the SignalR hub is consumed and see the realtime log streaming down as your users continue to use the Web API.

SignalR hub and base hub integration class πŸ”—

To begin with, we start with the usual MVC4 project, Web API template. We need to install SignalR, and the easiest way to do so is to use package manager console - we will get the latest SignalR (part of ASP.NET family already, Microsoft.AspNet.SignalR):

PM> Install-Package Microsoft.AspNet.SignalR -Pre  

We add an empty SignalR Hub to provide an endpoint for our SignalR outgoing connections. This will be the hub we’ll use in our custom trace writer to broadcast the messages to the clients.

[HubName("trace")]  
public class TraceHub : Hub  
{}  

Next, we will create a base abstract class which will allow us to integrate a SignalR hub into any class - in our case it will be the ITraceWriter but that same base can be used to integrate a hub into i.e. our controllers.

public abstract class SignalRBase<T> where T : IHub  
{  
private Lazy<IHubContext> hub = new Lazy<IHubContext>(  
() => GlobalHost.ConnectionManager.GetHubContext<T>()  
);

public IHubContext Hub  
{  
get { return hub.Value; }  
}  
}  

Creating a SignalR friendly trace writer πŸ”—

Now really the only thing left to do is to introduce a class that implements an ITraceWriter and can be passed into the Web API GlobalConfiguration.

Let’s do that:

public class DynamicTrace : SignalRBase<TraceHub>, ITraceWriter  
{  
public void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction)  
{  
if (level != TraceLevel.Off)  
{  
TraceRecord record = new TraceRecord(request, category, level);  
traceAction(record);  
Log(record);  
}  
}

private void Log(TraceRecord record)  
{  
var message = new StringBuilder();  
message.AppendMessage(record.Level.ToString().ToUpper());  
message.AppendMessage(DateTime.Now.ToString());

if (record.Request != null)  
message.AppendMessage(record.Request.Method.ToString(), notEmpty).AppendMessage(record.Request.RequestUri.ToString(), notEmpty);

message.AppendMessage(record.Category, notEmpty).AppendMessage(record.Operator, notEmpty)  
.AppendMessage(record.Operation).AppendMessage(record.Message, notEmpty);

if (record.Exception != null)  
message.AppendMessage(record.Exception.GetBaseException().Message, notEmpty);

Hub.Clients.All.logMessage(message.ToString());  
}

Func<string, bool> notEmpty = (text) => !string.IsNullOrWhiteSpace(text);  
}  

What happens here, is we implement the aforementioned ITraceWriter interface, and inherit from our base SignalR class at the same time, which gives us a possibility to broadcast messages using our hub. We grab the trace record from Web API and translate it into a user friendly, string-based output and flush to the SignalR hub with a dynamic logMessage() - which implies the matching method needs to be implemented on the client side.

You may have noticed that I used a little helper StringBuilder extension method to build the output:

public static StringBuilder AppendMessage(this StringBuilder sb, string text, Func<string, bool> predicate = null)  
{  
if (predicate != null)  
{  
if (predicate(text))  
{  
sb.Append(" ");  
sb.Append(text);  
}  
}  
else  
{  
sb.Append(" ");  
sb.Append(text);  
}

return sb;  
}  

We can now plug in our trace, in the WebApiConfig.cs:

config.Services.Replace(typeof(ITraceWriter), new DynamicTrace());  

Client side πŸ”—

On the client side, all we need is an HTML page which will connect to the SignalR hub (our “trace” hub) and wait for the broadcasts - which will come, if you recall in the form of the remote invocation of logMessage().

So let’s add it:

  
  
  
</p> 

<ul id="messages">
</ul>

  
  
  
  
</body>  
</html>  

This is all we need - so let’s run our application. When I browse my Web API, I can see the window with the trace page fill in with trace data in real time:

Video demo πŸ”—

I have also recorded a short little video to show this in action:

If you have problems with this embed (it’s Flash), click HERE to watch it.

Summary and source code πŸ”—

You can see that it is extremely easy to integrate SignalR into Web API - and not just for client services but even for infrastructure related purposes. SignalR can bring tremendous interactivity to your apps, and if you haven’t used it yet - you should definitely embrace it.

I’ve uploaded the source code to this little demo app to Github.

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP