Until recently I was into ASP.NET MVC because of it’s possibility to extending, modifying and doing pretty much whatever you want with it. And while I’ve got rather proficient in plugging stuff in it’s pipeline and/or doing custom stuff like routing, binding or results, it never changed one fact about MVC – it’s big, really big. And the truth is, some times we don’t want so much. That’s why I wanted to give Nancy a go for some time, below you’ll find some of my first thoughts. And i must tell you now, it will not end up as one night stand.
This post is not a guide, you should treat it as summary of one evening with Nancy and my thoughts about it. Feel free to ask me some questions in comments if you’re interested and I’ll probably write something more about Nancy. While it’s not core of my project and that’s why I’m just covering basics, I’m fascinated by Nancy and I’d like to write more about it in the future.
How do I start?
Short version is – you just start empty ASP.NET project(without MVC, WebAPI and other stuff) in VS and install one package from NuGet. Poof, it’s done. Now you can use Nancy.
In reality you’ll need to install Nancy.Hosting.AspNet package (for ASP.NET hosting and yes, there are others), it’s very possible you’ll need to install Nancy.Owin package for obvious reasons. It’s not hard and with empty project you’ll get everything you’ll need with NuGets.
Adding Nancy to existing MVC project is simple too. You just need to do some application startup code and web.config magic.
First you’ll need to ignore routes you’ll use with Nancy in MVC routing. Just add some code like that:
1 |
routes.IgnoreRoute("nancyPath/{*catchall}"); |
And your web.config should have this code (probably it was added during NuGet installation):
1 2 3 4 5 6 7 |
<system.web> <!-- Other stuff --> <httpHandlers> <!-- Do not have catchall handler BEFORE specific ones --> <add name="Nancy" verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" path="nancyPath/*"/> </httpHandlers> </system.web> |
You’ll also need similar code in other part of your web.config(probably added with NuGet too):
1 2 3 4 5 6 7 |
<system.webServer> <modules runAllManagedModulesForAllRequests="true"/> <validation validateIntegratedModeConfiguration="false"/> <handlers> <add name="Nancy" verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" path="nancyPath/*"/> </handlers> </system.webServer> |
And… that’s all. You’re ready to go.
Well, if you like and want to use Razor syntax in views you’ll need install Nancy.ViewEngines.Razor
Modules and routing
Nancy use modules in similiar way MVC uses Controllers. While it’s not exactly same, you could think of NancyModules as controllers. Module is any class derived from NancyModule and it’s location does not matter. Most simple module(I still haven’t worked on endpoints at this point) would look like that:
1 2 3 4 5 6 7 8 9 10 |
public class DashboardModule : NancyModule { public DashboardModule() { Get["/"] = p => { return View["dashboard", new DashboardViewModel(Context.CurrentUser)]; }; } } |
And first thing you should see is… routing. Yes, this Get[“/”] is a routing. You could provide any path you want you can even pass parameters in this indexer like:
1 2 3 4 5 6 7 8 |
Get["/Item/{id}"] = param => ... Post["/Item/{name}"] = param => ... //You can even do conditional routing Post["/Files", context => context.Request.Files.Any()] = params => ... //Or add constraint for parameter type Get["/{id:long}"] => p => .... |
You won’t need those route config classes for normal and API controllers anymore. I really liked this idea because it’s just so much simpler. You just need to remember that unlike in MVC, module name is not part of the routing by default. You just define some path in indexer and add lambda expression with desired action. If you want to add routing segment for entire module you nned to pass it to base constructor like that:
1 2 3 4 |
public DashboardModule() : base("/dashboard") { //... } |
So… how you would you use passed parameters in this kind of “actions”? It’s rather simple. Our lambda parameter is of type dynamic. So if you want to access some basic values passed in URL you just need to define it in your route and use it.
1 2 3 4 5 |
Get["/Item/{id}"] = param => { var id = param.id; //... } |
For more complex types you can use NancyModel binding which is by default almost as good as MVC ModelBinding. You could also bind your own, custom model binder which is much simpler in Nancy than in MVC. I’ll not cover binding here cause it’s just a tiny, little bit too muuch to summarize like routing and basic params.
Pipeline
While in MVC we have shitload of events during request lifecycle, most of the time we’re not using them. Many developers don’t know you can put stuff into it other that authenthication and authorization, even less know about more that overriding OnActionExecuted or OnActionExecuting. Truth is, most of the time we don’t need that. We just need to do some things before doing our magic or after that. And in Nancy we have those two events in our module (and one for errors). So we can add events to our Before and After events in scope of one module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public DashboardModule() { Before += ctx => { var currentUserDTO = new UserDTO(System.Security.Claims.ClaimsPrincipal.Current); if (currentUserDTO.IsValid) { currentUserDTO.NotifyUserManagerAboutLoggingIn(); ctx.CurrentUser = new UserIdentity(currentUserDTO); } return null; }; //... } |
Or we can do in in scope of entire project, for every request. We can do it in bootstrappers and those are a little to much to include in summary but you can do pretty much everything you would do in global.asax.cs in them. Since my authenthication layer is pretty simple for now (just fetching some data from claims) it was included in bootstrapper and added to pipeline. It looks like that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class UserIdentityProviderBootstrapper : IRequestStartup { public void Initialize(IPipelines pipelines, NancyContext context) { pipelines.BeforeRequest.AddItemToStartOfPipeline(ctx => { var currentUserDTO = new UserDTO(System.Security.Claims.ClaimsPrincipal.Current); if (currentUserDTO.IsValid) { currentUserDTO.NotifyUserManagerAboutLoggingIn(); context.CurrentUser = new UserIdentity(currentUserDTO); } return null; }); } } |
It’s simple because I need simple. I know that in my project I won’t need entire MVC. I would probably use just one or two controllers, tiny auth filter and some API controllers. And this is one of greatest things in Nancy – it does not give you anything that you won’t need out of the box. You want to extend Nancy? Install some NuGet or extend(for me extending Nancy seems simpler than MVC) it yourself whenever you want.
When to use Nancy instead of MVC?
It’s hard question. I would use Nancy in projects where you don’t need entire MVC and WebAPI pipelines. If you want to add simple API endpoint to your service it probably doesn’t need everything WebAPI has to offer and Nancy will be faster and simpler way (if you know how to use it of course). Everything depends on complexity of your Controller part in MVC.
You should also remember that while Nancy routing looks great and clear, it may get complicated and hard to maintain if you don’t plan it. It’s great to have your routing in front of you when you’re developing few modules, but IMHO you would want to have your routing in one place while mainaining dozens of modules.
Wrap-up
Nancy is simple and it’s entry threshold is very low but it has lots of great features like customisable conventions, bootstrappers, various hosting possibilities and other cool stuff. It’s one of the most fun frameworks that I’ve worked with because it does have everything that I’ve needed without unnecessary compilactions.
This is one of posts that covering some basics in technology stack I’m using in Get Noticed contest. It’s not super important part of my project so this post is just encouragement to try and have some good time with Nancy(Yep, I know how that sounds). However if you want to read about some of Nancy’s features feel free to ask about it in the comments. I’ll be more than happy to provide more content about this great framework.