In the previous post in this series, we looked at how Tinyweb can be used to build HTTP APIs and examined each of the result types a handler may return.
The examples in the last post used made-up dependencies and you may have wondered how does the handler get these dependencies? In this post, we’ll look at how easy it is to have dependencies injected into handlers. We’ll also take a look at the use of filters to facilitate things like logging and error handling.
Dependency Injection
We probably all know what dependency injection is, but let’s just clarify. Assume we have a UserService which implements IUserService, allowing us to invert the dependency (SOLID). In a handler (or filter), we need access to an instance of UserService, but we can’t create one directly as we’d violate DIP. Instead we need a container to create the dependency chain for us. We simply depend on the interface and leave the dependency construction to the container.
Let’s see how we can do this in Tinyweb.
In line with its goal of keeping things as simple as possible, Tinyweb takes a dependency on StructureMap in order to support dependency injection out of the box. This means that all you need to do is create StructureMap registries and tell Tinyweb about them – the rest will be handled by the framework.
Using the UserService example from above, let’s assume we have the following:
public interface IUserService
{
User GetById(int id);
}
public class UserService : IUserService
{
public User GetById(int id)
{
// find the user and return it
}
}
The next thing we need to do is create a StructureMap registry which associates these two, like so:
public class ServiceRegistry : Registry
{
public ServiceRegistry()
{
Scan(x =>
{
x.TheCallingAssembly();
x.WithDefaultConventions();
});
}
}
The ServiceRegistry tells StructureMap that interfaces IFoo and IBar in the current assembly should have concrete types called Foo and Bar respectively.
We need to tell Tinyweb about this registry for it to be able to construct handlers that have constructor dependencies. Registering them is as easy as passing them to Tinyweb.Init, like so:
protected void Application_Start(object sender, EventArgs e)
{
Tinyweb.Init(new ServiceRegistry());
}
Internally Tinyweb associates any registries with StructureMap, which it then uses to create handlers when requests come in. This allows us to create handlers with abstract dependencies and have the framework deal with it for us:
public class UsersAddHandler
{
IUserService _userService;
public UsersAddHandler(IUserService userService)
{
_userService = userService;
}
public IResult Post(User user)
{
var added = _userService.Add(user);
return Result.JsonOrXml(added);
}
}
When a POST request is made to /users/add, the framework will create an instance of the UsersAddHandler using StructureMap, and because we have registered the ServiceRegistry the IUserService dependency will be supplied as a concrete UserService.
Filters
Moving on, let’s take a look at filters and see how they let us plug into the execution pipeline of a request. Tinyweb supports both handler filters and global filters. A handler filter is a Before or After method on the handler which is run before or after the intended HTTP method is run, like so:
public class RootHandler
{
public IResult Before()
{
return Result.String("Before the request...");
}
public IResult Get()
{
return Result.String("Root request");
}
public IResult After()
{
return Result.String("After the request...");
}
}
Notice how the two filters also return IResult, allowing them to affect the response. Filters can have void return types if they don’t wish to add anything to the response. The example above, predictably, outputs the following:
Before the request...
Root request
After the request...
Filters also have the same model binding support that handler methods have, allowing a filter to log events using data from the request, for example:
public class UsersDeleteHandler
{
public IResult Post(int id)
{
var deleted = UserService.Delete(id);
return Result.JsonOrXml(deleted);
}
public void After(int id)
{
Logger.Log("Deleted user " + id);
}
}
Since we’re simply logging that a user has been deleted, we don’t use the IResult return type on the After filter.
To recap, handler filters are just well defined ways of running code just prior or just after a handler method is executed. Let’s move on and have a look at global filters.
Tinyweb also supports global filters that apply to all handlers, which is useful for system-level logging or performance monitoring. Use a global filter when something needs to happen before or after every request.
Like handler filters, global filters also support model binding – though it may not be very useful for situations where you’re logging every request (as you wouldn’t know which request and hence what required data has been sent).
Global filters are just like handler filters except they must exist in a separate class which ends with the word Filter. MyLoggingFilter would be discovered as a valid filter (as long as it had a Before or After method), whereas MyLoggerFilterClass would not be found because it doesn’t end with the word Filter.
Let’s see an example:
public class TimingFilter
{
Stopwatch timer;
public void Before()
{
timer = Stopwatch.StartNew();
}
public IResult After()
{
timer.Stop();
return Result.String("Time: " + timer.ElapsedMilliseconds);
}
}
This filter adds the Time: xxxx message to the bottom of every request, allowing us to see how quickly each request executes.
Global filters can also get access to the underlying RequestContext (from ASP.NET) and the HandlerData – Tinyweb’s internal structure used to identify the intended handler. If we wanted to create a list of every handler that was requested, we could use a global filter with HandlerData to do it:
public class RequestLoggingFilter
{
public void After(HandlerData handler)
{
Logger.Log("Request completed to " + handler.Type);
}
}
Finally, in terms of logging errors, this can be dealt with by supplying a delegate to Tinyweb.OnError which will receive the exception, the RequestContext and the HandlerData for the offending handler, like so:
protected void Application_Start(object sender, EventArgs e)
{
Tinyweb.Init();
Tinyweb.OnError = (exception, request, handler) =>
{
Logger.Log(exception + " error for " + handler.Type);
}
}
See the documentation for more details about error logging with the OnError delegate.
In This Post
We saw how Tinyweb makes injecting dependencies into handlers (and filters) easy through its internal use of StructureMap. We then moved onto filters and saw how Tinyweb supports filtering at both the handler level and globally.
We then walked through a couple of demos showing how we can use global filters for logging and timing requests. Finally we saw a brief example of how error logging can be achieved using Tinyweb.OnError.
In The Next Post
We’ll see how to use the Spark view engine and take a closer look at model binding.