Created
February 18, 2011 10:57
-
-
Save grumpydev/833540 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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