Per documentation it seems like it's only possible to add either single routes, one by one, or add all routes in annotated (attribute routing) controllers
DOCS: Routing to controller actions in ASP.NET Core
Is it possible to add only all routes belonging to single Controller?
Using UseEndpoints(e => e.MapControllers()) will add all controllers that are annotated, using UseEndpoints(e => e.MapControllerRoute(...)) seems to be able to add only single controller/action route, not all routes that are annotated in given controller
Sample controller:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class MyApiController
{
[Route("/")]
[Route("[action]")]
[HttpGet]
public ResponseType Index()
{
// ...
}
[Route("[action]")]
public ResponseType GetListing()
{
// ...
}
}
One solution I found is to build a custom MVC feature provider and implement an extension method that allows you to specify exactly which controllers you want registered.
public static class MvcExtensions
{
/// <summary>
/// Finds the appropriate controllers
/// </summary>
/// <param name="partManager">The manager for the parts</param>
/// <param name="controllerTypes">The controller types that are allowed. </param>
public static void UseSpecificControllers(this ApplicationPartManager partManager, params Type[] controllerTypes)
{
partManager.FeatureProviders.Add(new InternalControllerFeatureProvider());
partManager.ApplicationParts.Clear();
partManager.ApplicationParts.Add(new SelectedControllersApplicationParts(controllerTypes));
}
/// <summary>
/// Only allow selected controllers
/// </summary>
/// <param name="mvcCoreBuilder">The builder that configures mvc core</param>
/// <param name="controllerTypes">The controller types that are allowed. </param>
public static IMvcCoreBuilder UseSpecificControllers(this IMvcCoreBuilder mvcCoreBuilder, params Type[] controllerTypes) => mvcCoreBuilder.ConfigureApplicationPartManager(partManager => partManager.UseSpecificControllers(controllerTypes));
/// <summary>
/// Only instantiates selected controllers, not all of them. Prevents application scanning for controllers.
/// </summary>
private class SelectedControllersApplicationParts : ApplicationPart, IApplicationPartTypeProvider
{
public SelectedControllersApplicationParts()
{
Name = "Only allow selected controllers";
}
public SelectedControllersApplicationParts(Type[] types)
{
Types = types.Select(x => x.GetTypeInfo()).ToArray();
}
public override string Name { get; }
public IEnumerable<TypeInfo> Types { get; }
}
/// <summary>
/// Ensure that internal controllers are also allowed. The default ControllerFeatureProvider hides internal controllers, but this one allows it.
/// </summary>
private class InternalControllerFeatureProvider : ControllerFeatureProvider
{
private const string ControllerTypeNameSuffix = "Controller";
/// <summary>
/// Determines if a given <paramref name="typeInfo"/> is a controller. The default ControllerFeatureProvider hides internal controllers, but this one allows it.
/// </summary>
/// <param name="typeInfo">The <see cref="TypeInfo"/> candidate.</param>
/// <returns><code>true</code> if the type is a controller; otherwise <code>false</code>.</returns>
protected override bool IsController(TypeInfo typeInfo)
{
if (!typeInfo.IsClass)
{
return false;
}
if (typeInfo.IsAbstract)
{
return false;
}
if (typeInfo.ContainsGenericParameters)
{
return false;
}
if (typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.NonControllerAttribute)))
{
return false;
}
if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) &&
!typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.ControllerAttribute)))
{
return false;
}
return true;
}
}
}
Put the extensions class wherever in your project, and use like this
public void ConfigureServices(IServiceCollection services)
{
// put this line before services.AddControllers()
services.AddMvcCore().UseSpecificControllers(typeof(MyApiController), typeof(MyOtherController));
}
Source: https://gist.github.com/damianh/5d69be0e3004024f03b6cc876d7b0bd3
Courtesy of Damian Hickey.
Related
I've tried Dependency injection, but that always gives me a HttpContextAccessor.Current or ActionContext as null because I'm not in a request state (I think). So how can I get this context to just take a view, transform it to a html string (with Model if necessary) and throw it back in JS ? I even tried to call directly the Controller action, but it always gives HttpContext as null... I'm using Asp.NET Core 3.
Please, if someone has been going through, help me :-)
Thanks,
Edit:
I have an asp.net core based on Electron.net for a desktop application. I use a lot of IPC communication to retrieve data from backend c# using Electron.IpcMain.On. I register an action as listener in c# in a class. The main problem is that this class is really outside a normal HttpRequest or a Controller. Here is some sample code:
IpcBase Class
public abstract class IpcBase: IBaseIpcCommunicationClass
{
/// <summary>
/// Find a way to get rid of this and take more than the first window
/// </summary>
protected static BrowserWindow _mainWindow = Electron.WindowManager.BrowserWindows.First();
/// <summary>
/// Send an ipcEvent with a parameter class
/// </summary>
/// <param name="parameters">Parameters to fill</param>
public void RegisterIpcEvent<T>(IpcRegisterModel<T> registerModel) => Electron.IpcMain.On(registerModel.key, o => registerModel.action((T)
((JObject)o).ToObject(typeof(T))));
/// <summary>
/// Send a reply inside a registerevent
/// </summary>
/// <typeparam name="T">Type of model</typeparam>
/// <param name="model">model</param>
public void SendReply<T>(IpcSendParamsModel<T> model)
{
if (!string.IsNullOrEmpty(model.replyTo))
{
Electron.IpcMain.Send(_mainWindow, model.replyTo, model);
}
}
...
}
IpcUI (to get the controller view, just like an ajax call on a controller that retrieve the view in String (I already have that, but not with Ipc)
public class IpcUI: IpcBase
{
public IpcUI(IRazorViewToStringService razorViewRenderService)
{
Console.WriteLine("IpcUI::Constructor");
RegisterIpcEvent(new IpcRegisterModel<IpcSendParamsModel<AjaxPartialModel>>("renderPartial", async (param) =>
{
var param = new IpcSendParamsModel<AjaxPartialModel>("RenderPartial")
{
key = "renderPartial",
model = new AjaxPartialModel()
{
DataModel = "{items: [{\r\n MaterialIcon: \"\",\r\n Title: \"Games\",\r\n Selectable: true,\r\n Active: true,\r\n Key: \"GAMES\",\r\n BadgeCaption: \"new\",\r\n BadgeValue: \"123\",\r\n BadgeColor: \"red darken-1\",\r\n BadgePartialLink: \"\",\r\n BadgeContainerLink: \"\",\r\n BadgeModelLink: \"\",\r\n PartialLink: \"Home/Index\",\r\n ContainerLink: \"#body-content\",\r\n ModelLink: \"\"\r\n }] }".JsonDeserialize<MenuModelHeader>(),
PartialName = "PartialViews/_TopMenu"
}
};
try
{
param.results =
await razorViewRenderService.CreateAndResolveInstanceFromGeneric().RenderViewToStringAsync($"~/Views/{param.model.PartialName}.cshtml",
param.model.DataModel);
}
catch (Exception e)
{
IpcClasses.ExceptionManager.SendException(this, e, $"IpcUI params: {param.model.JsonSerialize()}");
}
}));
}
}
Razor Service (Mostly taken from here Generate string from view)
Added in startup:
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IRazorViewToStringService, RazorRazorViewToStringService>();
When I create an instance of IpcUI, DI gives me the service, but without any HttpContext or ActionContext... Sorry for the lack of information from my last edit :-). Hope it is a bit more specific.
Oh ! I forgot something, IpcUI is created at runtime not with a new (because that don't work) but with a custom extension function that retrieves the IServiceProvider for DI:
In startup
ExtensionsUtils.ServiceProvider = app.ApplicationServices;
In ExtensionsUtils
/// <summary>
/// This is called in configure services to get the collection of services in an extension static class
/// </summary>
public static IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// Create a reference from type T with DI
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="o"></param>
/// <returns></returns>
public static T CreateAndResolveInstanceFromGeneric<T>(this T o)
{
return (T)ActivatorUtilities.CreateInstance<T>(ServiceProvider);
}
Edit 2:
I have tried to access IRazorViewToStringService from a real controller constructor and it's null again... What Am I doing wrong ???
private readonly IRazorViewToStringService _razorViewRenderService;
public BaseController(IRazorViewToStringService razorViewRenderService)
{
_razorViewRenderService = razorViewRenderService;
}
...
/// <summary>
/// Return a success http code and a View rendered as string
/// </summary>
/// <param name="ViewName">Name of MVC View (or PartialView)</param>
/// <param name="Model">Model to pass if any</param>
/// <returns>JSON: { result: "type", description: "Html Code" }</returns>
public async Task<ActionResult> CheckAndReturnView(string ViewName, object Model = null)
{
return Ok(await _razorViewRenderService.RenderViewToStringAsync(ViewName, Model));
}
Using .NET Core 2.0 WebApi.
I have a webapi which has many endpoints where each endpoint is handling and throwing BadRequest when it fails. As below:
if(data == null)
{
return BadRequest("Data must not be blank.");
}
Now since these status codes are repetitive in my api, I was thinking to create a Helper method which would return BadRequest back to my API.
So I created a static helper class. But the issue here is BadRequest is part of ControllerBase and is not available in my helper class. What is the best way to create this method that would return a BadRequest.
--Updated---
I want something like this:
public static BadRequest GetBadRequestMessage(string message)
{
return BadRequest(message);
}
I have also tried as:
public static BadRequestResult GetBadRequestMessage(string message)
{
return new BadRequestResult(message);
}
But this gives error: Severity Code Description Project File Line Suppression State
Error CS1729 'BadRequestResult' does not contain a constructor that takes 1 arguments
You can manually initialize the action result
return new BadRequestObjectResult("error message here");
Which is basically what the ControllerBase does internally
/// <summary>
/// Creates an <see cref="BadRequestResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <returns>The created <see cref="BadRequestResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestResult BadRequest()
=> new BadRequestResult();
/// <summary>
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <param name="error">An error object to be returned to the client.</param>
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestObjectResult BadRequest(object error)
=> new BadRequestObjectResult(error);
/// <summary>
/// Creates an <see cref="BadRequestObjectResult"/> that produces a <see cref="StatusCodes.Status400BadRequest"/> response.
/// </summary>
/// <param name="modelState">The <see cref="ModelStateDictionary" /> containing errors to be returned to the client.</param>
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState)
{
if (modelState == null)
{
throw new ArgumentNullException(nameof(modelState));
}
return new BadRequestObjectResult(modelState);
}
Source
In .net core 5 project create BadRequestConfig class in your ioc folder/class library :
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
namespace Ioc
{
public static class BadRequestConfig
{
// error 400 handling - remove extra fields in error model - remove if(ModelState.IsValid)
public static IMvcBuilder AddBadRequestServices(this IMvcBuilder services)
{
services.ConfigureApiBehaviorOptions(options =>
options.InvalidModelStateResponseFactory = actionContext =>
{
var modelState = actionContext.ModelState.Values;
var allErrors = actionContext.ModelState.Values.SelectMany(v => v.Errors);
return new BadRequestObjectResult(new
{
StatusCode = 400,
Message = string.Join(" - ", allErrors.Select(e => e.ErrorMessage))
});
});
return services;
}
}
}
Then add AddBadRequestServices() method in ConfigureServices method in your startup.cs file :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbServices();
services.AddAppServices();
services.AddControllers().AddBadRequestServices(); // here
services.AddJwtAuthentication();
services.AddSwaggerServices();
}
By this solution it is not necessary to write if(ModelState.IsValid) in your actions.
I'm using Web API 2 with Ninject and i'm getting the following error when i've got multiple parallel HTTP calls.
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
I didn't get this error before, all the code that retrieves the data from the database is async so i'm thinking its a Ninject scope issue.
NinjectWebCommon
kernel.BindHttpFilter<MyAuthenticateFilter>(FilterScope.Action);
// Database context
kernel.Bind<IUnitOfWorkAsyncFactory>().To<UnitOfWorkAsyncFactory>().InRequestScope();
kernel.Bind<IUnitOfWorkAsync>().To<UnitOfWork>().InRequestScope();
kernel.Bind<IDataFactory>().To<DataFactory>().InRequestScope();
All the Application and domain handlers are also async and make use of the same DataFactory and UnitOfWorkAsyncFactory.
Seems like there's a threading issue with the IAuthenticationFilter.
The factory pattern used for the DbContext.
public class DataFactory : Disposable, IDataFactory
{
private MyContext DbContext { get; set; }
public IDataContextAsync GetDataContext()
{
return DbContext ?? (DbContext = new MyContext());
}
public void AfterDispose()
{
DbContext = null;
}
public DataFactory()
{
}
public DataFactory(MyContext context)
{
DbContext = context;
}
}
I remember some time ago having issues with filter injection in WebApi and scopes being lost.
As far as I remember using this implementation of IDependencyResolver (can not remember where I got it) made the issue go away.
using System.Web.Http.Dependencies;
using global::Ninject.Syntax;
public class DependencyResolverWebApiNinject : DependencyScopeWebApiNinject, IDependencyResolver
{
public DependencyResolverWebApiNinject(IResolutionRoot resolutionRoot)
: base(resolutionRoot)
{
}
public virtual IDependencyScope BeginScope()
{
return this;
}
}
Where DependencyScopeWebApiNinject is :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http.Dependencies;
using global::Ninject;
using global::Ninject.Infrastructure.Disposal;
using global::Ninject.Parameters;
using global::Ninject.Syntax;
public class DependencyScopeWebApiNinject : DisposableObject, IDependencyScope
{
/// <summary>
/// Initializes a new instance of the <see cref="DependencyScopeWebApiNinject"/> class.
/// </summary>
/// <param name="resolutionRoot">The resolution root.</param>
public DependencyScopeWebApiNinject(IResolutionRoot resolutionRoot)
{
this.ResolutionRoot = resolutionRoot;
}
/// <summary>
/// Gets the resolution root.
/// </summary>
/// <value>The resolution root.</value>
protected IResolutionRoot ResolutionRoot
{
get;
private set;
}
/// <summary>
/// Gets the service of the specified type.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <returns>The service instance or <see langword="null"/> if none is configured.</returns>
public object GetService(Type serviceType)
{
var request = this.ResolutionRoot.CreateRequest(serviceType, null, new Parameter[0], true, true);
return this.ResolutionRoot.Resolve(request).SingleOrDefault();
}
/// <summary>
/// Gets the services of the specifies type.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <returns>All service instances or an empty enumerable if none is configured.</returns>
public IEnumerable<object> GetServices(Type serviceType)
{
return this.ResolutionRoot.GetAll(serviceType).ToList();
}
}
hope this will help
I've been able create whatever endpoints I've wanted as long as the parameters for each one is different:
public IHttpActionResult GetFightersByWeightClass(string WeightClass)
...
public IHttpActionResult GetFighterByExactName(string NameEquals)
...
But as soon as I try to create two differently named functions that share the same parameters I am unable to use both. I have two endpoints that don't require parameters, shown below:
public class FighterController : ApiController
{
/// <summary>
/// Gets all fighters.
/// </summary>
/// <returns></returns>
[ActionName("GetAllFighters")]
public IEnumerable<Fighter> GetAllFighters()
{
return allFighters;
}
/// <summary>
/// Gets all fighters that are currently undefeated.
/// </summary>
/// <returns></returns>
[ActionName("GetAllUndefeatedFighters")]
public IHttpActionResult GetAllUndefeatedFighters()
{
var results = allFighters.FindAll(f => f.MMARecord.Losses == 0);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
}
Both URLs return this:
{"Message":"An error has occurred.","ExceptionMessage":"Multiple actions were found that match the request: \r\nGetAllFighters on type MMAAPI.Controllers.FighterController\r\nGetAllUndefeatedFighters on type MMAAPI.Controllers.FighterController","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.Http.Controllers.ApiControllerActionSelector.ActionSelectorCacheItem.SelectAction(HttpControllerContext controllerContext)\r\n at System.Web.Http.Controllers.ApiControllerActionSelector.SelectAction(HttpControllerContext controllerContext)\r\n at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"}
Not sure why this is happening they each have their own unique action and function name, so I thought they would work like this...:
http://localhost:55865/api/fighter/GetAllUndefeatedFighters -- Just shows fighters with zero losses
http://localhost:55865/api/fighter/ -- shows all fighters
...but instead neither works. If I remove one of them, they other works and vice versa. So they aren't working when they are both active. Any idea why?
Web API allows you to use Attribute routing to customize endpoint URIs.
To use it, add:
config.MapHttpAttributeRoutes();
to the Register method in your WebApiConfig class. Then you can set the endpoints to whatever you want regardless of the Action name.
[Route("getallfighters"), HttpGet, ResponseType(typeof(Fighter))]
public IHttpActionResult ThisNameDoesntMatterNow()
{
//...
}
And your URI becomes:
api/fighter/getallfighters
You can even add attribute routing to your controller:
[RoutePrefix("api/v1/fighters")]
public class FightersController : ApiController
{
//...
}
A combination of the two other answers works well for me. (I've changed the names slightly from the question.)
[RoutePrefix("api/v1/fighters")]
public class FighterController : ApiController
{
/// <summary>
/// Gets all fighters.
/// </summary>
/// <returns>An enumeration of fighters.</returns>
[Route(""), HttpGet]
public IEnumerable<Fighter> GetAllFighters()
{
return allFighters;
}
/// <summary>
/// Gets all fighters that are currently undefeated.
/// </summary>
/// <returns>An enumeration of fighters.</returns>
[Route("undefeated"), HttpGet]
public IEnumerable<Fighter> GetAllUndefeatedFighters()
{
return allFighters.FindAll(f => f.MMARecord.Losses == 0);
}
}
As such, your endpoints would be:
GET /api/v1/fighters
GET /api/v1/fighters/undefeated
Use route attribute
/// <summary>
/// Gets all fighters.
/// </summary>
/// <returns></returns>
[HttpGet]
[System.Web.Http.Route("api/GetAllFighters")]
public IEnumerable<Fighter> GetAllFighters()
{
return allFighters;
}
/// <summary>
/// Gets all fighters that are currently undefeated.
/// </summary>
/// <returns></returns>
[HttpGet]
[System.Web.Http.Route("api/GetAllUndefeatedFighters")]
public IHttpActionResult GetAllUndefeatedFighters()
{
var results = allFighters.FindAll(f => f.MMARecord.Losses == 0);
if (results == null)
{
return NotFound();
}
return Ok(results);
}
and call two method using different route
http://www.yourdomain/api/GetAllFighters
http://www.yourdomain/api/GetAllUndefeatedFighters
I'm working on a REST-based webservice using NancyFx as the underlying framework. However, my assignment requires me to use Spring.Net for dependency injection. I'm not too experienced in C# (I have mostly been working on Java code prior to this assignment) or Spring itself quite yet, and I haven't been able to find a lot of information on making a custom bootstrapper using Spring as its IoC container, nor are there preconfigured bootstrappers like there are for Ninject or Unity.
Is there a good way to make Nancy and Spring play nice, or would I be better off going back to Microsoft's MVC framework for my assignment?
Thanks a lot in advance.
Strip45,
isn't very complicated, but is toilful. Spring.Net is a declarative configuration container and TinyIoCContainer is a Register/Resolver container. Maybe on first look you can't see problems for difference of concepts, but generally, register/resolver containers will be filled automatically, on most cases discovering types to register them.
To change NancyFx IoC Container you may derive from:
NancyBootstrapperWithRequestContainerBase<TContainer>
Implementing its abstract and virtual methods, it's easy, but you will need configure more then 60 object definitions. It's very complicated because if on new releases of NancyFx they create a new optional dependency, you will not be notified about it.
I'm now working to permit work with both containers, side by side, only hosting NancyModules on Spring.Net. Any specific of NancyFx infrastructure dependency still be discovered dynamically and registered on container like in the past.
One advice: if you use same strategy of me, does not send spring proxies to TinyIoCContainer, it crashes on initialization.
So I found a solution by combining the suggestions of Christian Horsdal and Luiz Carlos Faria. I've now managed to get a working injected "Hello world" module through Nancy. What I ended up doing was make a DualContainer class with both an IApplicationContext and a TinyIoCContainer included, and implementing them into a NancyBootstrapperWithRequestContainerBase. I used the TinyIoCContainer for the majority of the operations, with the Spring container only being called if there is a definition of the module in the object XML.
The way I implemented it does assume the module is registered under its class name however, so this is something to take into account.
The DualContainer class:
using Nancy.TinyIoc;
using Spring.Context;
namespace FORREST.WebService.General.Bootstrap
{
public class DualContainer
{
public TinyIoCContainer TinyIoCContainer { get; set; }
public IApplicationContext ApplicationContext { get; set; }
public DualContainer GetChildContainer()
{
return new DualContainer
{
TinyIoCContainer = TinyIoCContainer.GetChildContainer(),
ApplicationContext = this.ApplicationContext
};
}
}
}
Spring object definition (configSections is used for the database config, not used for this example):
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/spring-objects.xsd">
<object name="appConfigPropertyHolder" type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core">
<property name="configSections">
<value>appSettings</value>
</property>
</object>
<object id="HelloWorldSpringRestModule" type="FORREST.WebService.RESTApi.Modules.HelloWorldSpringRestModule">
<property name="Message" value="Hello World!"/>
</object>
</objects>
The custom bootstrapper (not the cleanest solution most likely, but it worked for me):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Nancy.Bootstrapper;
using Nancy.TinyIoc;
using Nancy;
using Nancy.Diagnostics;
using Spring.Context;
using Spring.Context.Support;
namespace FORREST.WebService.General.Bootstrap
{
/// <summary>
/// Class enabling the use of Spring injections in modules.
/// </summary>
public abstract class HybridNancyBootstrapperBase : NancyBootstrapperWithRequestContainerBase<DualContainer>
{
/// <summary>
/// Default assemblies that are ignored for autoregister
/// </summary>
public static IEnumerable<Func<Assembly, bool>> DefaultAutoRegisterIgnoredAssemblies = new Func<Assembly, bool>[]
{
asm => asm.FullName.StartsWith("Microsoft.", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("System.", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("System,", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("CR_ExtUnitTest", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("mscorlib,", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("CR_VSTest", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("DevExpress.CodeRush", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("IronPython", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("IronRuby", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("xunit", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("Nancy.Testing", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("MonoDevelop.NUnit", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("SMDiagnostics", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("CppCodeProvider", StringComparison.InvariantCulture),
asm => asm.FullName.StartsWith("WebDev.WebHost40", StringComparison.InvariantCulture),
};
/// <summary>
/// Gets the assemblies to ignore when autoregistering the application container
/// Return true from the delegate to ignore that particular assembly, returning true
/// does not mean the assembly *will* be included, a false from another delegate will
/// take precedence.
/// </summary>
protected virtual IEnumerable<Func<Assembly, bool>> AutoRegisterIgnoredAssemblies
{
get { return DefaultAutoRegisterIgnoredAssemblies; }
}
/// <summary>
/// Configures the container using AutoRegister followed by registration
/// of default INancyModuleCatalog and IRouteResolver.
/// </summary>
/// <param name="container">Container instance</param>
protected override void ConfigureApplicationContainer(DualContainer container)
{
AutoRegister(container, this.AutoRegisterIgnoredAssemblies);
}
/// <summary>
/// Resolve INancyEngine
/// </summary>
/// <returns>INancyEngine implementation</returns>
protected override sealed INancyEngine GetEngineInternal()
{
return this.ApplicationContainer.TinyIoCContainer.Resolve<INancyEngine>();
}
/// <summary>
/// Create a default, unconfigured, container
/// </summary>
/// <returns>Container instance</returns>
protected override DualContainer GetApplicationContainer()
{
return new DualContainer
{
ApplicationContext = ContextRegistry.GetContext(),
TinyIoCContainer = new TinyIoCContainer()
};
}
/// <summary>
/// Register the bootstrapper's implemented types into the container.
/// This is necessary so a user can pass in a populated container but not have
/// to take the responsibility of registering things like INancyModuleCatalog manually.
/// </summary>
/// <param name="applicationContainer">Application container to register into</param>
protected override sealed void RegisterBootstrapperTypes(DualContainer applicationContainer)
{
applicationContainer.TinyIoCContainer.Register<INancyModuleCatalog>(this);
}
/// <summary>
/// Register the default implementations of internally used types into the container as singletons
/// </summary>
/// <param name="container">Container to register into</param>
/// <param name="typeRegistrations">Type registrations to register</param>
protected override sealed void RegisterTypes(DualContainer container, IEnumerable<TypeRegistration> typeRegistrations)
{
foreach (var typeRegistration in typeRegistrations)
{
switch (typeRegistration.Lifetime)
{
case Lifetime.Transient:
container.TinyIoCContainer.Register(typeRegistration.RegistrationType
, typeRegistration.ImplementationType).AsMultiInstance();
break;
case Lifetime.Singleton:
container.TinyIoCContainer.Register(typeRegistration.RegistrationType
, typeRegistration.ImplementationType).AsSingleton();
break;
case Lifetime.PerRequest:
throw new InvalidOperationException("Unable to directly register a per request lifetime.");
default:
throw new ArgumentOutOfRangeException();
}
}
}
/// <summary>
/// Register the various collections into the container as singletons to later be resolved
/// by IEnumerable{Type} constructor dependencies.
/// </summary>
/// <param name="container">Container to register into</param>
/// <param name="collectionTypeRegistrations">Collection type registrations to register</param>
protected override sealed void RegisterCollectionTypes(DualContainer container, IEnumerable<CollectionTypeRegistration> collectionTypeRegistrations)
{
foreach (var collectionTypeRegistration in collectionTypeRegistrations)
{
switch (collectionTypeRegistration.Lifetime)
{
case Lifetime.Transient:
container.TinyIoCContainer.RegisterMultiple(collectionTypeRegistration.RegistrationType
, collectionTypeRegistration.ImplementationTypes).AsMultiInstance();
break;
case Lifetime.Singleton:
container.TinyIoCContainer.RegisterMultiple(collectionTypeRegistration.RegistrationType
, collectionTypeRegistration.ImplementationTypes).AsSingleton();
break;
case Lifetime.PerRequest:
throw new InvalidOperationException("Unable to directly register a per request lifetime.");
default:
throw new ArgumentOutOfRangeException();
}
}
}
/// <summary>
/// Register the given module types into the container
/// </summary>
/// <param name="container">Container to register into</param>
/// <param name="moduleRegistrationTypes">NancyModule types</param>
protected override sealed void RegisterRequestContainerModules(DualContainer container, IEnumerable<ModuleRegistration> moduleRegistrationTypes)
{
foreach (var moduleRegistrationType in moduleRegistrationTypes)
{
container.TinyIoCContainer.Register(
typeof(INancyModule),
moduleRegistrationType.ModuleType,
moduleRegistrationType.ModuleType.FullName).
AsSingleton();
(container.ApplicationContext as IConfigurableApplicationContext).ObjectFactory.
RegisterResolvableDependency(moduleRegistrationType.ModuleType,
container.TinyIoCContainer.Resolve(moduleRegistrationType.ModuleType));
}
}
/// <summary>
/// Register the given instances into the container
/// </summary>
/// <param name="container">Container to register into</param>
/// <param name="instanceRegistrations">Instance registration types</param>
protected override void RegisterInstances(DualContainer container, IEnumerable<InstanceRegistration> instanceRegistrations)
{
foreach (var instanceRegistration in instanceRegistrations)
{
container.TinyIoCContainer.Register(
instanceRegistration.RegistrationType,
instanceRegistration.Implementation);
//Cast zodat het programmatisch kan worden gedaan
(container.ApplicationContext as IConfigurableApplicationContext).ObjectFactory.RegisterResolvableDependency(
instanceRegistration.RegistrationType,
instanceRegistration.Implementation);
}
}
/// <summary>
/// Creates a per request child/nested container
/// </summary>
/// <returns>Request container instance</returns>
protected override sealed DualContainer CreateRequestContainer()
{
return this.ApplicationContainer.GetChildContainer();
}
/// <summary>
/// Gets the diagnostics for initialisation
/// </summary>
/// <returns>IDiagnostics implementation</returns>
protected override IDiagnostics GetDiagnostics()
{
return this.ApplicationContainer.TinyIoCContainer.Resolve<IDiagnostics>();
}
/// <summary>
/// Gets all registered startup tasks
/// </summary>
/// <returns>An <see cref="IEnumerable{T}"/> instance containing <see cref="IApplicationStartup"/> instances. </returns>
protected override IEnumerable<IApplicationStartup> GetApplicationStartupTasks()
{
return this.ApplicationContainer.TinyIoCContainer.ResolveAll<IApplicationStartup>(false);
}
/// <summary>
/// Gets all registered request startup tasks
/// </summary>
/// <returns>An <see cref="IEnumerable{T}"/> instance containing <see cref="IRequestStartup"/> instances.</returns>
protected override IEnumerable<IRequestStartup> RegisterAndGetRequestStartupTasks(DualContainer container, Type[] requestStartupTypes)
{
container.TinyIoCContainer.RegisterMultiple(typeof(IRequestStartup), requestStartupTypes);
return container.TinyIoCContainer.ResolveAll<IRequestStartup>(false);
}
/// <summary>
/// Gets all registered application registration tasks
/// </summary>
/// <returns>An <see cref="IEnumerable{T}"/> instance containing <see cref="IRegistrations"/> instances.</returns>
protected override IEnumerable<IRegistrations> GetRegistrationTasks()
{
return this.ApplicationContainer.TinyIoCContainer.ResolveAll<IRegistrations>(false);
}
/// <summary>
/// Retrieve all module instances from the container
/// </summary>
/// <param name="container">Container to use</param>
/// <returns>Collection of NancyModule instances</returns>
protected override sealed IEnumerable<INancyModule> GetAllModules(DualContainer container)
{
var nancyModules = container.TinyIoCContainer.ResolveAll<INancyModule>(false);
return nancyModules;
}
/// <summary>
/// Retreive a specific module instance from the container
/// </summary>
/// <param name="container">Container to use</param>
/// <param name="moduleType">Type of the module</param>
/// <returns>NancyModule instance</returns>
protected override sealed INancyModule GetModule(DualContainer container, Type moduleType)
{
INancyModule module;
try
{
module = (INancyModule) container.ApplicationContext.GetObject(moduleType.Name, moduleType);
}
//Niet geregistreerd in Spring, gebruik TinyIoCContainer om op te halen
catch (Spring.Objects.Factory.NoSuchObjectDefinitionException)
{
System.Diagnostics.Debug.WriteLine("Laad " + moduleType.Name + " uit TinyIoC in plaats van Spring");
container.TinyIoCContainer.Register(typeof(INancyModule), moduleType);
module = container.TinyIoCContainer.Resolve<INancyModule>();
}
return module;
}
/// <summary>
/// Executes auto registation with the given container.
/// </summary>
/// <param name="container">Container instance</param>
private static void AutoRegister(DualContainer container, IEnumerable<Func<Assembly, bool>> ignoredAssemblies)
{
var assembly = typeof(NancyEngine).Assembly;
container.TinyIoCContainer.AutoRegister(AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !ignoredAssemblies.Any(ia => ia(a)))
, DuplicateImplementationActions.RegisterMultiple, t => t.Assembly != assembly);
}
}
}
And finally, the actual NancyModule:
using FORREST.WebService.General;
using FORREST.WebService.General.Modules;
using Nancy;
using Newtonsoft.Json;
namespace FORREST.WebService.RESTApi.Modules
{
public class HelloWorldSpringRestModule : NancyModule
{
public string Message { get; set; }
public string Route_Base
{
get { return Configuratie.Api_Root + "/Hello"; }
}
public HelloWorldSpringRestModule()
{
Get[Route_Base] = HelloSpring;
}
protected internal Response HelloSpring(dynamic parameters)
{
var _response = (Response)(JsonConvert.SerializeObject(Message));
return _response;
}
}
}
Thanks for the help guys!
For now your best bet is to read this bit of documentation and then draw inspiration from one of the other container specific bootstrappers found at the Nancy Github organization- e.g. the Ninject one.
To verify the implementation you could run the tests NancyBootstrapperBase against your new bootstrapper.
Hopefully in the future we will have better specs for the requirements for bootstrappers.