Skip to content

Instantly share code, notes, and snippets.

@grumpydev
Created February 18, 2011 10:57
Show Gist options
  • Save grumpydev/833540 to your computer and use it in GitHub Desktop.
Save grumpydev/833540 to your computer and use it in GitHub Desktop.
namespace Nancy.Bootstrapper
{
using System;
using System.Collections.Generic;
using System.Linq;
using Nancy.Routing;
using Nancy.Extensions;
/// <summary>
/// Base class for container based Bootstrappers.
///
/// There are two component lifecycles, an application level one which is guaranteed to be generated at least once (on app startup)
/// and a request level one which should be guaranteed to be generated per-request. Depending on implementation details the application
/// lifecycle components may also be generated per request (without any critical issues), but this isn't ideal.
///
/// Doesn't have to be used (only INancyBootstrapper is required), but does provide a nice consistent base if possible.
///
/// The methods in the base class are all Application level are called as follows:
///
/// CreateContainer() - for creating an empty container
/// GetModuleTypes() - getting the module types in the application, default implementation grabs from the appdomain
/// RegisterModules() - register the modules into the container
/// ConfigureApplicationContainer() - register any application lifecycle dependencies
/// GetEngineInternal() - construct the container (if required) and resolve INancyEngine
///
/// Request level implementations may use <see cref="INancyBootstrapperPerRequestRegistration{TContainer}"/>, or implement custom
/// lifetime logic. It is preferred that users have the ability to register per-request scoped dependencies, and that instances retrieved
/// via <see cref="INancyModuleCatalog.GetModuleByKey"/> are per-request scoped.
/// </summary>
/// <typeparam name="TContainer">Container tyope</typeparam>
public abstract class NancyBootstrapperBase<TContainer> : INancyBootstrapper
where TContainer : class
{
/// <summary>
/// Pipeline for pre-request hooks
/// </summary>
private readonly PreRequestHooksPipeline preRequestPipeline = new PreRequestHooksPipeline();
/// <summary>
/// Type passed into RegisterDefaults - override this to switch out default implementations
/// </summary>
protected virtual Type DefaultRouteResolver { get { return typeof(DefaultRouteResolver); } }
/// <summary>
/// Type passed into RegisterDefaults - override this to switch out default implementations
/// </summary>
protected virtual Type DefaultRoutePatternMatcher { get { return typeof (DefaultRoutePatternMatcher); } }
/// <summary>
/// Type passed into RegisterDefaults - override this to switch out default implementations
/// </summary>
protected virtual Type DefaultTemplateEngineSelector { get { return typeof(DefaultTemplateEngineSelector); } }
/// <summary>
/// Type passed into RegisterDefaults - override this to switch out default implementations
/// </summary>
protected virtual Type DefaultNancyEngine { get { return typeof(NancyEngine); } }
/// <summary>
/// Type passed into RegisterDefaults - override this to switch out default implementations
/// </summary>
protected virtual Type DefaultModuleKeyGenerator { get { return typeof(DefaultModuleKeyGenerator); } }
/// <summary>
/// Type passed into RegisterDefaults - override this to switch out default implementations
/// </summary>
protected virtual Type DefaultRouteCache { get { return typeof(RouteCache); } }
/// <summary>
/// Type passed into RegisterDefaults - override this to switch out default implementations
/// </summary>
protected virtual Type DefaultRouteCacheProvider { get { return typeof(DefaultRouteCacheProvider); } }
/// <summary>
/// <para>
/// The pre-request hook
/// </para>
/// <para>
/// The PreRequest hook is called prior to processing a request. If a hook returns
/// a non-null response then processing is aborted and the response provided is
/// returned.
/// </para>
/// </summary>
protected PreRequestHooksPipeline PreRequest { get { return preRequestPipeline; } }
/// <summary>
/// Gets the configured INancyEngine
/// </summary>
/// <returns>Configured INancyEngine</returns>
public INancyEngine GetEngine()
{
var container = CreateContainer();
ConfigureApplicationContainer(container);
RegisterDefaults(container, BuildDefaults());
RegisterModules(GetModuleTypes(GetModuleKeyGenerator()));
var engine = GetEngineInternal();
engine.PreRequest = this.PreRequest;
return engine;
}
private IEnumerable<TypeRegistration> BuildDefaults()
{
return new[]
{
new TypeRegistration(typeof(IRouteResolver), DefaultRouteResolver),
new TypeRegistration(typeof(ITemplateEngineSelector), DefaultTemplateEngineSelector),
new TypeRegistration(typeof(INancyEngine), DefaultNancyEngine),
new TypeRegistration(typeof(IModuleKeyGenerator), DefaultModuleKeyGenerator),
new TypeRegistration(typeof(IRouteCache), DefaultRouteCache),
new TypeRegistration(typeof(IRouteCacheProvider), DefaultRouteCacheProvider),
new TypeRegistration(typeof(IRoutePatternMatcher), DefaultRoutePatternMatcher)
};
}
/// <summary>
/// Resolve INancyEngine
/// </summary>
/// <returns>INancyEngine implementation</returns>
protected abstract INancyEngine GetEngineInternal();
/// <summary>
/// Get the moduleKey generator
/// </summary>
/// <returns>IModuleKeyGenerator instance</returns>
protected abstract IModuleKeyGenerator GetModuleKeyGenerator();
/// <summary>
/// Returns available NancyModule types
/// </summary>
/// <returns>IEnumerable containing all NancyModule Type definitions</returns>
protected virtual IEnumerable<ModuleRegistration> GetModuleTypes(IModuleKeyGenerator moduleKeyGenerator)
{
var moduleType = typeof(NancyModule);
var locatedModuleTypes =
from assembly in AppDomain.CurrentDomain.GetAssemblies()
where !assembly.ReflectionOnly
where !assembly.IsDynamic
from type in assembly.SafeGetExportedTypes()
where !type.IsAbstract
where moduleType.IsAssignableFrom(type)
select new ModuleRegistration(type, moduleKeyGenerator.GetKeyForModuleType(type));
return locatedModuleTypes;
}
/// <summary>
/// Create a default, unconfigured, container
/// </summary>
/// <returns>Container</returns>
protected abstract TContainer CreateContainer();
/// <summary>
/// Register the default implementations of internally used types into the container as singletons
/// </summary>
/// <param name="container">Container</param>
/// <param name="typeRegistrations">Type registrations to register</param>
protected abstract void RegisterDefaults(TContainer container, IEnumerable<TypeRegistration> typeRegistrations);
/// <summary>
/// Configure the container (register types) for the application level
/// <seealso cref="ConfigureRequestContainer"/>
/// </summary>
/// <param name="container">Container instance</param>
protected virtual void ConfigureApplicationContainer(TContainer container)
{
}
/// <summary>
/// Register the given module types into the container
/// </summary>
/// <param name="moduleRegistrationTypes">NancyModule types</param>
protected abstract void RegisterModules(IEnumerable<ModuleRegistration> moduleRegistrationTypes);
}
}
namespace Nancy
{
using System;
using Nancy.Routing;
public class NancyEngine : INancyEngine
{
private readonly IRouteResolver resolver;
private readonly IRouteCache routeCache;
/// <summary>
/// Initializes a new instance of the <see cref="NancyEngine"/> class.
/// </summary>
/// <param name="resolver">An <see cref="IRouteResolver"/> instance that will be used to resolve a route, from the modules, that matches the incoming <see cref="Request"/>.</param>
/// <param name="routeCache"></param>
public NancyEngine(IRouteResolver resolver, IRouteCache routeCache)
{
if (resolver == null)
{
throw new ArgumentNullException("resolver", "The resolver parameter cannot be null.");
}
if (routeCache == null)
{
throw new ArgumentNullException("routeCache", "The routeCache parameter cannot be null.");
}
this.resolver = resolver;
this.routeCache = routeCache;
}
/// <summary>
/// <para>
/// Gets or sets the pre-request hook.
/// </para>
/// <para>
/// The PreRequest hook is called prior to processing a request. If a hook returns
/// a non-null response then processing is aborted and the response provided is
/// returned.
/// </para>
/// </summary>
public Func<Request, Response> PreRequest { get; set; }
/// <summary>
/// <para>
/// Gets or sets the post-request hook.
/// </para>
/// <para>
/// The post-request hook is called after the engine processes a request, but before it
/// is returned. A hook must return a response non-null response, even if no additional
/// processing/manipulation has taken place.
/// </para>
/// </summary>
public Func<Request, Response, Response> PostRequest { get; set; }
public Response HandleRequest(Request request)
{
if (request == null)
{
throw new ArgumentNullException("request", "The request parameter cannot be null.");
}
if (this.PreRequest != null)
{
var preRequestResponse = this.PreRequest.Invoke(request);
if (preRequestResponse != null)
{
return preRequestResponse;
}
}
var resolvedRouteAndParameters = this.resolver.Resolve(request, this.routeCache);
var response = resolvedRouteAndParameters.Item1.Invoke(resolvedRouteAndParameters.Item2);
if (request.Method.ToUpperInvariant() == "HEAD")
{
response = new HeadResponse(response);
}
return response;
}
}
}
namespace Nancy.Bootstrapper
{
using System;
using System.Collections.Generic;
public class PreRequestHooksPipeline
{
public PreRequestHooksPipeline()
{
this.pipelineItems = new List<Func<Request, Response>>();
}
private List<Func<Request, Response>> pipelineItems;
public IEnumerable<Func<Request, Response>> PipelineItems
{
get
{
return this.pipelineItems.AsReadOnly();
}
}
public static implicit operator Func<Request, Response>(PreRequestHooksPipeline pipeline)
{
return pipeline.Invoke;
}
public static implicit operator PreRequestHooksPipeline(Func<Request, Response> func)
{
var pipeline = new PreRequestHooksPipeline();
pipeline.AddItemToEndOfPipeline(func);
return pipeline;
}
public static PreRequestHooksPipeline operator +(PreRequestHooksPipeline pipeline, Func<Request, Response> func)
{
pipeline.AddItemToEndOfPipeline(func);
return pipeline;
}
public static PreRequestHooksPipeline operator +(PreRequestHooksPipeline pipelineToAddTo, PreRequestHooksPipeline pipelineToAdd)
{
pipelineToAddTo.pipelineItems.AddRange(pipelineToAdd.pipelineItems);
return pipelineToAddTo;
}
public Response Invoke(Request request)
{
Response returnValue = null;
using (var enumerator = this.PipelineItems.GetEnumerator())
{
while (returnValue == null && enumerator.MoveNext())
{
returnValue = enumerator.Current.Invoke(request);
}
}
return returnValue;
}
public void AddItemToStartOfPipeline(Func<Request, Response> item)
{
this.InsertItemAtPipelineIndex(0, item);
}
public void AddItemToEndOfPipeline(Func<Request, Response> item)
{
this.pipelineItems.Add(item);
}
public void InsertItemAtPipelineIndex(int index, Func<Request, Response> item)
{
this.pipelineItems.Insert(index, item);
}
}
}
namespace Pipelines
{
using System;
using System.Linq;
using Xunit;
public class PreRequestHooksPipelineTests
{
private PreRequestHooksPipeline pipeline;
private static Response CreateResponse()
{
return new Response();
}
private static Request CreateRequest()
{
return new Request();
}
public PreRequestHooksPipelineTests()
{
pipeline = new PreRequestHooksPipeline();
}
[Fact]
public void When_invoked_pipeline_member_returning_a_response_stops_pipeline_execution()
{
var item1Called = false;
Func<Request, Response> item1 = (r) => { item1Called = true; return null; };
var item2Called = false;
Func<Request, Response> item2 = (r) => { item2Called = true; return CreateResponse(); };
var item3Called = false;
Func<Request, Response> item3 = (r) => { item3Called = true; return null; };
pipeline.AddItemToEndOfPipeline(item1);
pipeline.AddItemToEndOfPipeline(item2);
pipeline.AddItemToEndOfPipeline(item3);
pipeline.Invoke(CreateRequest());
Assert.True(item1Called);
Assert.True(item2Called);
Assert.False(item3Called);
}
[Fact]
public void When_invoked_pipeline_member_returning_a_response_returns_that_response()
{
var response = CreateResponse();
var item1Called = false;
Func<Request, Response> item1 = (r) => null;
var item2Called = false;
Func<Request, Response> item2 = (r) => response;
var item3Called = false;
Func<Request, Response> item3 = (r) => null;
pipeline.AddItemToEndOfPipeline(item1);
pipeline.AddItemToEndOfPipeline(item2);
pipeline.AddItemToEndOfPipeline(item3);
var result = pipeline.Invoke(CreateRequest());
Assert.Same(response, result);
}
[Fact]
public void When_invoked_pipeline_members_all_return_null_returns_null()
{
Func<Request, Response> item1 = (r) => null;
Func<Request, Response> item2 = (r) => null;
Func<Request, Response> item3 = (r) => null;
pipeline.AddItemToEndOfPipeline(item1);
pipeline.AddItemToEndOfPipeline(item2);
pipeline.AddItemToEndOfPipeline(item3);
var result = pipeline.Invoke(CreateRequest());
Assert.Null(result);
}
[Fact]
public void AddItemToEndOfPipeline_adds_to_the_end_of_the_pipeline()
{
Func<Request, Response> item1 = (r) => { return null; };
Func<Request, Response> item2 = (r) => { return CreateResponse(); };
pipeline.AddItemToEndOfPipeline(item2);
pipeline.AddItemToEndOfPipeline(item1);
Assert.Equal(2, pipeline.PipelineItems.Count());
Assert.Same(item1, pipeline.PipelineItems.Last());
}
[Fact]
public void AddItemToStartOfPipeline_adds_to_the_end_of_the_pipeline()
{
Func<Request, Response> item1 = (r) => { return null; };
Func<Request, Response> item2 = (r) => { return new Response(); };
pipeline.AddItemToEndOfPipeline(item2);
pipeline.AddItemToStartOfPipeline(item1);
Assert.Equal(2, pipeline.PipelineItems.Count());
Assert.Same(item1, pipeline.PipelineItems.First());
}
[Fact]
public void InsertItemAtPipelineIndex_adds_at_correct_index()
{
Func<Request, Response> item1 = (r) => null;
Func<Request, Response> item2 = (r) => null;
Func<Request, Response> item3 = (r) => null;
pipeline.AddItemToEndOfPipeline(item1);
pipeline.AddItemToEndOfPipeline(item3);
pipeline.InsertItemAtPipelineIndex(1, item2);
Assert.Same(item1, pipeline.PipelineItems.ElementAt(0));
Assert.Same(item2, pipeline.PipelineItems.ElementAt(1));
Assert.Same(item3, pipeline.PipelineItems.ElementAt(2));
}
[Fact]
public void PlusEquals_with_func_add_item_to_end_of_pipeline()
{
Func<Request, Response> item1 = (r) => { return null; };
Func<Request, Response> item2 = (r) => { return CreateResponse(); };
pipeline.AddItemToEndOfPipeline(item2);
pipeline += item1;
Assert.Equal(2, pipeline.PipelineItems.Count());
Assert.Same(item1, pipeline.PipelineItems.Last());
}
[Fact]
public void PlusEquals_with_another_pipeline_adds_those_pipeline_items_to_end_of_pipeline()
{
Func<Request, Response> item1 = (r) => { return null; };
Func<Request, Response> item2 = (r) => { return CreateResponse(); };
pipeline.AddItemToEndOfPipeline(item1);
pipeline.AddItemToEndOfPipeline(item2);
Func<Request, Response> item3 = (r) => { return null; };
Func<Request, Response> item4 = (r) => { return CreateResponse(); };
var pipeline2 = new PreRequestHooksPipeline();
pipeline2.AddItemToEndOfPipeline(item3);
pipeline2.AddItemToEndOfPipeline(item4);
pipeline += pipeline2;
Assert.Equal(4, pipeline.PipelineItems.Count());
Assert.Same(item3, pipeline.PipelineItems.ElementAt(2));
Assert.Same(item4, pipeline.PipelineItems.Last());
}
[Fact]
public void When_cast_to_func_and_invoked_members_are_invoked()
{
var item1Called = false;
Func<Request, Response> item1 = (r) => { item1Called = true; return null; };
var item2Called = false;
Func<Request, Response> item2 = (r) => { item2Called = true; return null; };
var item3Called = false;
Func<Request, Response> item3 = (r) => { item3Called = true; return null; };
pipeline.AddItemToEndOfPipeline(item1);
pipeline.AddItemToEndOfPipeline(item2);
pipeline.AddItemToEndOfPipeline(item3);
Func<Request, Response> func = pipeline;
func.Invoke(CreateRequest());
Assert.True(item1Called);
Assert.True(item2Called);
Assert.True(item3Called);
}
[Fact]
public void When_cast_from_func_creates_a_pipeline_with_one_item()
{
Func<Request, Response> item1 = (r) => null;
PreRequestHooksPipeline castPipeline = item1;
Assert.Equal(1, castPipeline.PipelineItems.Count());
Assert.Same(item1, castPipeline.PipelineItems.First());
}
[Fact]
public void Pipeline_containing_another_pipeline_will_invoke_items_in_both_pipelines()
{
var item1Called = false;
Func<Request, Response> item1 = (r) => { item1Called = true; return null; };
var item2Called = false;
Func<Request, Response> item2 = (r) => { item2Called = true; return null; };
var item3Called = false;
Func<Request, Response> item3 = (r) => { item3Called = true; return null; };
var item4Called = false;
Func<Request, Response> item4 = (r) => { item4Called = true; return null; };
pipeline += item1;
pipeline += item2;
var subPipeline = new PreRequestHooksPipeline();
subPipeline += item3;
subPipeline += item4;
pipeline.AddItemToEndOfPipeline(subPipeline);
pipeline.Invoke(CreateRequest());
Assert.True(item1Called);
Assert.True(item2Called);
Assert.True(item3Called);
Assert.True(item4Called);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment