This post will serve as a follow-up to our earlier NancyFX exploration. A few weeks ago, I posted Building an (awesome) API with NancyFX 2.0 + Dapper where we covered all the basics of Nancy and running on .NET Core.
If you have not read our previous post, not to worry, there will be very little to no overlap between the 2 posts. Here, we will focus in on and deep dive into the use of Async
Modules in NancyFX as well as how the Before
and After
action method hooks work. Since there is not much additional code, we will use the same repository to demonstrate usage.
Github Repo: https://github.com/nandotech/VSNancyDemo
If you are unfamiliar with .NET Core or Nancy, then I do reccomend checking out some of the other posts available on this blog related to .NET Core and specifically our post on Building an API w/ NancyFX 2.0 & Dapper.
Unfortunately, there are not a ton of resources covering NancyFX 2.0 just yet, so here I am doing my part. Scott Hanselman recently also wrote a similar post (but using Entity Framework Core 1.0) available here.
The Nancy repository has a ton of samples including one showing off Async
: Nancy.Demo.Async. Once again, very unfortunately, the grand majority of what you see is syntax for version 1.x
and therefore does not help us.
If you’ve read this far along, I’m sure you’re ready to get down and dirty.
NancyFX makes it incredibly easy to declare async modules, making the Modules
appear nearly indistinguishable from any other Nancy Module. That previous link covers all the basics, however using version 1.x
style route declarations. The announcement and wiki detailing the Nancy v2 upgrade notes explain some of the differences between 1.x
and 2.x
as of now.
AsyncModule.cs
public class AsyncModule : NancyModule
{
public AsyncModule()
{
Get("/async", async (args, ct) =>
{
await Task.Delay(1000);
await Task.Delay(1000);
var client = new HttpClient();
var res = await client.GetAsync("http://nancyfx.org");
var content = await res.Content.ReadAsStringAsync();
return (Response)content;
});
}
}
While extremely contrived, the above example shows how to declare Async Modules
, await
several responses and then return our content
in the from of a string. That is, if you are following along and have entered the above as a Module
in your Nancy project, you should see this:
Upon closer inspection, that is clearly the plain HTML
from http://www.nancyfx.org. Nonetheless, the point here was to highlight the proper syntax for an asynchronous Nancy Module
and demonstrate using await
to simulate some possibly long-running process.
Speaking of asynchronicity, NancyFX also introduces an incredibly useful tool in the Before
and After
Request Hooks that allow you to define functions and/or code that you would like to run either before or after any request in the entire Module
. These are also known as the BeforePipeline
and AfterPipeline
–the final one, which we will not cover here, is the OnError
pipeline.
These pipelines for requests provide us with a lot of lattitude and the possibility of causing all types of different behaviors. As an example, if you have authentication/authorization on your application and there are only specific spots with certain priviledges: it is perfectly feasible and would make sense to add some type of authenticator that could run in the Before
method of any Module
. By the same token, you are capable of completely intercepting a request from either the Before
or After
module (or allowing OnError
to execute, if there were errors, let’s say).
To illustrate, below is our DispoModule.cs
public class DispoModule : NancyModule
{
public DispoModule(IDispoRepository _repo)
: base("/dispo")
{
Get("/", args =>
{
return _repo.GetAll();
});
Get("Id={id}", args =>
{
return _repo.Get(args.id);
});
Post("/Name={name}&Desc={description}", args =>
{
var posted = new Disposition();
posted.Name = args.Name;
posted.Description = args.Description;
posted.Timestamp = DateTime.Now;
_repo.Add(posted);
return posted;
});
Delete("Id={id}", args =>
{
_repo.Remove(args.id);
return $"{args.id} Removed";
});
}
}
If you read our other post this probably looks very familiar–it is the module that allows GET
and POST
requests to accomplish different things. For further clarification, feel free to check out the Github repo here. So what if we wanted to run a Before
hook that intercepted the request under certain circumstances?
DispoModule.cs
public class DispoModule : NancyModule
{
public DispoModule(IDispoRepository _repo)
: base("/dispo")
{
Before += (ctx) => {
return "Intercepted";
};
//Remainder of code stays same
}
}
We navigate to /dispo/
and exactly as expected, the browser simply returns the string
“Intercepted”, indicating that our Before
hook did hijack the request. So, in other words, using Before
executes that specified action before executing the requested Route, right?
This is not entirely true. Because of the manner which Nancy operates, when you first hit a module (ANY Module) with a request, Nancy steps through the Module
to ensure the current route is valid/matches. I imagine this has in large part to do with the fact the routes are dynamically constructed and also for Nancy to determine the Route with the highest weight to receive the request.
EDIT 11/11/2016: Thanks to Phillip Haydon from the NancyFX Team for some clarification on how this works. Reach him on Twitter at @philliphaydon. Quoted from him:
“This is because we scan all modules and the routes and build a cache. Once built then we just match based on route and invoke the correct Func on the module for that request.”
Awesome. I was actually not aware Nancy built and kept a cache of all routes, that is really rather ingenious and makes subsequent executions extremely efficient.
Once Nancy finds a matching route, then it jumps over to the Before
hook, if there is one available. See the execution of our DispoModule.cs
currently, first breaking at the matching Route (“/”) and upon pressing F5
it jumps right into the before hook.
There is one minor difference in syntax between async
and non-async
Modules when implementing Before/After
. As you just saw, in a synchronous controller, your Before
& After
lifecycle hooks should look like these:
Synchronous
Before += (ctx) =>
{
return "Interception";
};
After += (ctx) =>
{
ctx.Resposne = "Post Interception";
};
Now having added this to our DispoModule.cs, let’s see how Nancy treats this. Not surprisingly, very similar to what we just saw a minute ago. Nancy steps through every route (including Before
and After
) to figure out which is the correctly matching route (or pattern). Once the framework decides the request is properly formed, it enters the Before
method and sets our Response
, however our request is not done yet.
From here, (since we performed a GET
request on /dispo/
), we enter Get("/", args)
from the DispoModule.cs. Now here’s the interesting part, or at the very least something to be conscious of: inside of our GET
we do return _repo.GetAll();
which is supposed to return a JSON object of all the Dispo
’s currently saved in the database. Our lovely NancyFX
framework then moves on to the After
action (ctx.Response = "Post-Interception"
) which re-writes the current Response
and leaves us with the new simple string.
Like I mentioned, using Before
/After
in async
modules has a slightly different syntax. In these, not only are you forced to declare them as explicity async
, but you also have the option of using a “Cancellation Token” (ct
) in order to cause and/or track failures.
Asynchronous
Before += async (ctx, ct) =>
{
this.AddToLog("Before Hook Delay\n");
await Task.Delay(5000);
return null;
};
After += async (ctx, ct) =>
{
this.AddToLog("After Hook Delay\n");
await Task.Delay(5000);
this.AddToLog("After Hook Complete\n");
ctx.Response = this.GetLog();
};
In both the synchronous and asynchronous examples, if your eye is better than mine you also noticed one other glaring difference. Personally, I had to figure it out the hard way when error after error kept popping up.
In your Before
pipeline you are capable of return
ing anything and short-circuiting a request in that way, so long as you do not also have an After
method (or so long as your After
pipeline does not edit the ctx.Response
as your response would then be overwritten).
Please note: You may not return
values from the After
pipeline. In order to achieve this you may use ctx.Response =
and set it as you please.
Nonetheless, these are fantastic abilities offered up for free by the framework and are extremely easy to take advantage of. Moreover, even though I didn’t really go over it, these code examples all also show the ctx
or NancyContext
that comes baked into the framework, accessible from nearly any request.
So with that said, here is our AsyncModule
that just shows off some of the features available to us as well as a decent example of using built-in NancyContext
to make a simple logger.
AsyncModule.cs
public class AsyncModule : NancyModule
{
public AsyncModule()
{
Before += async (ctx, ct) =>
{
this.AddToLog("Before Hook Delay\n");
await Task.Delay(5000);
return null;
};
After += async (ctx, ct) =>
{
this.AddToLog("After Hook Delay\n");
await Task.Delay(5000);
this.AddToLog("After Hook Complete\n");
ctx.Response = this.GetLog();
};
Get("/async", async (args, ct) =>
{
this.AddToLog("Delay 1\n");
await Task.Delay(1000);
this.AddToLog("Delay 2\n");
await Task.Delay(1000);
this.AddToLog("Executing async http client\n");
var client = new HttpClient();
var res = await client.GetAsync("http://nancyfx.org");
var content = await res.Content.ReadAsStringAsync();
this.AddToLog("Response: " + content.Split('\n')[0] + "\n");
return (Response)this.GetLog();
});
}
private void AddToLog(string logLine)
{
if (!this.Context.Items.ContainsKey("Log"))
{
this.Context.Items["Log"] = string.Empty;
}
this.Context.Items["Log"] = (string)this.Context.Items["Log"] + DateTime.Now + " : " + logLine;
}
private string GetLog()
{
if (!this.Context.Items.ContainsKey("Log"))
{
this.Context.Items["Log"] = string.Empty;
}
return (string)this.Context.Items["Log"];
}
}
As you can see, this is designed to really show off some of the Async
features as well as using Before
and After
hooks in tandem with an Async
Module.
Since our GET
request is marked async
, so are our Before
& After
pipelines. By that same token, they both now operate just like any other asynchronous method. We can await
any long-running action which we simulate here using Task.Delay()
.
If you run the above code, here is what you will see in your browser:
11/10/2016 6:47:15 PM : Before Hook Delay
11/10/2016 6:47:20 PM : Delay 1
11/10/2016 6:47:21 PM : Delay 2
11/10/2016 6:47:22 PM : Executing async http client
11/10/2016 6:47:23 PM : Response: <!DOCTYPE html>
11/10/2016 6:47:23 PM : After Hook Delay
11/10/2016 6:47:28 PM : After Hook Complete
We see “Before Hook Delay” is triggered in the Before
method exactly 5 seconds prior to the next statement. Then there are “Delay 1” and “Delay 2”, followed by “Executing async http client” where we are simply using HttpClient
to run a GET
request to http://nancyfx.org. This is also output to the “Log” (using AddToLog
) and we see on Line 5, 11/10/2016 6:47:23 PM : Response: <!DOCTYPE html>
. The final 2 messages that display are both clearly in the After
hook of the pipeline.
In any case, I hope this helps someone and further helps to clarify any questions you may have regarding the framework. Some up-to-date documentation may be hard to find, but please do not allow that to discourage you from trying Nancy out and finding out if it’s for you.
If you like what you see and would like to either build applications using Nancy or contribute to the project (Nancy is open source), please do not hesitate.
There is an awesome community of NancyFX
developer’s Slack that you may join to interact with and ask questions, 100% free to join. The actual Nancy developers also frequent the chat very regularly.
Thank you for reading and please do come visit us @ http://nancyfx.slack.com and let us know what you think! Questions? Concerns? Need help implementing a feature? Come on and stop by.