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.
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));
}
In my Web api project I am having a controller DocumentViewerV1Controller which has a code as:
/// <summary>
///
/// </summary>
/// <param name="documentViewerService"></param>
public DocumentViewerV1Controller(IDocumentViewerService<HtmlViewInformation> documentViewerService)
{
_documentViewerHtmlService = documentViewerService;
}
/// <summary>
///
/// </summary>
/// <param name="documentViewerService"></param>
public DocumentViewerV1Controller(IDocumentViewerService<PageImage> documentViewerService)
{
_documentViewerImageService = documentViewerService;
}/// <summary>
/// Rendering Document as Html
/// </summary>
/// <returns></returns>
[HttpGet]
[Route(WebApiConfig.RootApiUri + "/v1/viewashtml/{docid}")]
public string ViewAsHtml(string docId)
{
var documentInfo = new DocumentInfo { DocumentName = HostingEnvironment.MapPath("~/Uploads/") + docId };
var response = _documentViewerHtmlService.RenderDocument(documentInfo, DocumentRenderType.Html);
return GenerateResponse(response);
}
When I run my service, make a call and debug the constructor initialization, It doesn't goes through the initialization which makes _documentViewerHtmlService as null, eventually fails returning Null reference Exception.
Is it possible to have service Interface as IDocumentViewerService?
Yes, but you'll need to remove one of the constructors or tell it which one to use.
container.RegisterType<IViewerInformation, HtmlViewerInformation>();
container.RegisterType<IDocumentViewerService<IViewerInformation>, DocumentViewerServce<IViewerInformation>>();
You may / may not also need to initialize some of the constructors which you can do by
container.RegisterType<IDocumentViewerService<IViewerInformation>, DocumentViewerServce<IViewerInformation>>(
new InjectionConstructor(...));
Given the following registration code...
ILogger takes a single string parameter as a constructor argument.
IJob implementations all take an ILogger as a constructor argument.
// register the 'default' logger.
builder.RegisterType<Logger>().As<ILogger>()
.WithParameter(new NamedParameter("category", "default"));
Lots of registrations clipped out for brevity...
// register the 'job' logger. (this is a problem, a duplicate...)
builder.RegisterType<Logger>().As<ILogger>()
.WithParameter(new NamedParameter("category", "job"));
// register some classes that need a different logger parameter.
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(_ => typeof(IJob).IsAssignableFrom(_))
// how to do this...
.WithLoggerWithCategoryJob();
How do I use the category=default logger for all my class registrations except the IJob ones.
I would like to use the category=job logger for all those.
This solution demonstrates how to provide a default parameter for the logger, and to override the logging parameter just for IRepository<> implementations.
The code can quite easily be enhanced to provide a set of overrides depending on the implemented interfaces.
/// <summary>
/// The logging module.
/// </summary>
public class LoggingModule : Module
{
/// <summary>
/// Override to add registrations to the container.
/// </summary>
/// <remarks>
/// Note that the ContainerBuilder parameter is unique to this module.
/// </remarks>
/// <param name="builder">The builder through which components can be registered.</param>
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<NewLogger>()
.As<ILogger>()
.WithParameter(new NamedParameter("category", "Default"));
base.Load(builder);
}
/// <summary>
/// Override to attach module-specific functionality to a component registration.
/// </summary>
/// <remarks>
/// This method will be called for all existing <i>and future</i> component
/// registrations - ordering is not important.
/// </remarks>
/// <param name="componentRegistry">The component registry.</param><param name="registration">The registration to attach functionality to.</param>
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing += (sender, args) =>
{
var type = args.Component.Activator.LimitType;
if (type.GetInterfaces().All(_ => !_.IsGenericType || _.GetGenericTypeDefinition() != typeof(IRepository<>)))
return;
var pm = new ResolvedParameter(
(p, c) => p.ParameterType == typeof(ILogger),
(p, c) => c.Resolve<ILogger>(new NamedParameter("category", "RepositoryLogger")));
args.Parameters = args.Parameters.Union(new[] { pm });
};
base.AttachToComponentRegistration(componentRegistry, registration);
}
}
Assuming that the REST and Data access interactions will be somewhat standard, I want to be able to create an abstract type to represent entity objects to pass back and forth from REST client (e.g. JavaScript in a browser) to the persistence layer without writing/registering/routing a bunch of different WebAPI controllers.
How can one create one controller base type implementation to manage persistence of several different entity type REST interactions? I.e. how can I make this work with WebAPI?
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web;
using System.Web.Http;
namespace DynamicEntityApiControllers
{
/// <summary>
///
/// </summary>
public class EntityController<T> : ApiController
where T : EntityObject
{
private readonly IDaoProvider daoProvider;
/// <summary>
/// Initializes a new instance of the <see cref="EntityController{T}"/> class.
/// </summary>
/// <param name="daoProvider">The DAO provider.</param>
protected EntityController(IDaoProvider daoProvider)
{
this.daoProvider = daoProvider;
}
/// <summary>
/// Gets the name of the collection.
/// </summary>
/// <value>
/// The name of the collection.
/// </value>
protected string CollectionName
{
get
{
return (string)this.RequestContext.RouteData.Values["controller"];
}
}
/// <summary>
/// Gets the specified entity by key from the collection.
/// </summary>
/// <param name="key">The key.</param>
/// <returns></returns>
public T Get(string key)
{
return this.daoProvider.Output<T>(this.CollectionName, key).Data;
}
/// <summary>
/// Creates or updates the specified entity by key in the collection.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="updating">The updating.</param>
/// <returns></returns>
public T Put(string key, [FromBody] T updating)
{
var daoObject = DaoEntity.Create(this.CollectionName, key, updating);
return this.daoProvider.Input<T>(daoObject).Data;
}
/// <summary>
/// Creates or Updates the collection specified entirely.
/// </summary>
/// <param name="replacingCollection">The replacing collection.</param>
/// <returns></returns>
public IEnumerable<T> Put([FromBody] IEnumerable<T> replacingCollection)
{
return this.daoProvider.Input<T>(replacingCollection
.Select(o => DaoEntity.Create(this.CollectionName, o)));
}
/// <summary>
/// creates a new entity in the specified collection.
/// </summary>
/// <param name="creating">The creating.</param>
/// <returns></returns>
public T Post([FromBody] T creating)
{
var daoEntity = DaoEntity.Create(this.CollectionName, creating);
return this.daoProvider.Input(daoEntity).Data;
}
/// <summary>
/// Deletes the specified entity by key from the collection.
/// </summary>
/// <param name="key">The key.</param>
public void Delete(string key)
{
this.daoProvider.Delete(this.CollectionName, key);
}
}
}
the JavaScript client must look/act something like this:
$.ajax({url: '/Book', method: 'POST', data: {title: 'This StackOverflow Post', pageCount: '124' })
.done(function(data) {
$.get('/Book/'+data.Key).done(function(data) {
// this will return the book you just created
console.dir(data);
})
})
$.ajax({url: '/Book', method: 'POST', data: {title: 'Some other StackOverflow Post', pageCount: '236' })
.done(function(data) {
$.get('/Magazine/'+data.Key).done(function(data) {
// this will return 404 status code because you're asking for the book's key
// in the Magazines collection
console.dir(data);
})
})
the sample solution has a bunch TODO here (like actually do right with ETag header, if-match, etc...) but I had to do about 4 queries across StackOverflow to find these bits.
Prerequisites:
Autofac (IoC) integration with WebAPI
WebAPI Controller Location / DependencyResolver (these are not the same things)
C# Generics
Reflection IL Emitting
very minimal REST understanding and what an ETag is.
GIT source folder
briefly, here is the logic:
create a base type (entityobject)
identify all those entities at IoC Registration time via reflection (Implementer is responsible for specifying which assemblies to scan)
create concrete implementations of abstract class EntityController via IL Emitting
Tell the SupportsDynamicControllerTypeResolver about these dynamic types
IDaoProvider implementation is the "persistence" layer. implement your persistence logic here.. in this sample, an in memory dictionary is used.
And what happens when a request is made:
- RouteTable is interrogated (finds {controller}/{key} route which is an HttpRoute
- Controller name matches EntityObject implementer name minus Entity. I.e. BookEntity => BookController
- SupportsDynamicControllerTypeResolver is required here to get dynamic assembly types as valid candidates for WebApi to try to activate.
- HTTP Put/Get/Post/Delete methods find the correct method
- persistence layer does the rest
I've run into a bit of an issue trying to unit test an MVC site I have: I require a lot of the ASP.NET environment to be running (generation of httpcontexts, sessions, cookies, memberships, etc.) to fully test everything.
Even to test some of the less front end stuff needs memberships in order to properly work, and it's been finicky to get this all spoofed by hand.
Is there a way to spin up an application pool inside of NUnit tests? That seems like the easiest way.
If written properly, you shouldn't need to have a real context, real session, cookies, etc. The MVC framework by default provides a HttpContext that can be mocked/stubbed. I'd recommend using a mocking framework like Moq or Rhino Mocks and creating a MockHttpContext class that creates a mock context with all of the properties that you need to test against set up. Here's a mock HttpContext that uses Moq
/// <summary>
/// Mocks an entire HttpContext for use in unit tests
/// </summary>
public class MockHttpContextBase
{
/// <summary>
/// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
/// </summary>
public MockHttpContextBase() : this(new Mock<Controller>().Object, "~/")
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
/// </summary>
/// <param name="controller">The controller.</param>
public MockHttpContextBase(Controller controller) : this(controller, "~/")
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
/// </summary>
/// <param name="url">The URL.</param>
public MockHttpContextBase(string url) : this(new Mock<Controller>().Object, url)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MockHttpContextBase"/> class.
/// </summary>
/// <param name="controller">The controller.</param>
/// <param name="url">The URL.</param>
public MockHttpContextBase(ControllerBase controller, string url)
{
HttpContext = new Mock<HttpContextBase>();
Request = new Mock<HttpRequestBase>();
Response = new Mock<HttpResponseBase>();
Output = new StringBuilder();
HttpContext.Setup(x => x.Request).Returns(Request.Object);
HttpContext.Setup(x => x.Response).Returns(Response.Object);
HttpContext.Setup(x => x.Session).Returns(new FakeSessionState());
Request.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
Request.Setup(x => x.QueryString).Returns(new NameValueCollection());
Request.Setup(x => x.Form).Returns(new NameValueCollection());
Request.Setup(x => x.ApplicationPath).Returns("~/");
Request.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns(url);
Request.Setup(x => x.PathInfo).Returns(string.Empty);
Response.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
Response.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns((string path) => path);
Response.Setup(x => x.Write(It.IsAny<string>())).Callback<string>(s => Output.Append(s));
var requestContext = new RequestContext(HttpContext.Object, new RouteData());
controller.ControllerContext = new ControllerContext(requestContext, controller);
}
/// <summary>
/// Gets the HTTP context.
/// </summary>
/// <value>The HTTP context.</value>
public Mock<HttpContextBase> HttpContext { get; private set; }
/// <summary>
/// Gets the request.
/// </summary>
/// <value>The request.</value>
public Mock<HttpRequestBase> Request { get; private set; }
/// <summary>
/// Gets the response.
/// </summary>
/// <value>The response.</value>
public Mock<HttpResponseBase> Response { get; private set; }
/// <summary>
/// Gets the output.
/// </summary>
/// <value>The output.</value>
public StringBuilder Output { get; private set; }
}
/// <summary>
/// Provides Fake Session for use in unit tests
/// </summary>
public class FakeSessionState : HttpSessionStateBase
{
/// <summary>
/// backing field for the items in session
/// </summary>
private readonly Dictionary<string, object> _items = new Dictionary<string, object>();
/// <summary>
/// Gets or sets the <see cref="System.Object"/> with the specified name.
/// </summary>
/// <param name="name">the key</param>
/// <returns>the value in session</returns>
public override object this[string name]
{
get
{
return _items.ContainsKey(name) ? _items[name] : null;
}
set
{
_items[name] = value;
}
}
}
There's a few things that you could add further like a HTTP Headers collection, but hopefully it demonstrates what you can do.
To use
var controllerToTest = new HomeController();
var context = new MockHttpContextBase(controllerToTest);
// do stuff that you want to test e.g. something goes into session
Assert.IsTrue(context.HttpContext.Session.Count > 0);
With regards to Membership providers or other providers, you've hit on something that can be hard to test. I would abstract the usage of the provider behind an interface such that you can provide a fake for the interface when testing a component that relies on it. You'll still have trouble unit testing the concrete implementation of the interface that uses the provider however but your mileage may vary as to how far you want/have to go with regards to unit testing and code coverage.
I'm not aware of a way to do that since your code isn't in that process and requires a host that isn't in aspnet either. (I've been wrong before though haha)
Theres an older HttpSimulator from Phil Haack, have you given that a whirl?
http://haacked.com/archive/2007/06/19/unit-tests-web-code-without-a-web-server-using-httpsimulator.aspx
You need to build wrapper interfaces for those services. The original MVC2 and MV3 starter project templates did this by default, but for some reason they dropped that in the latest versions.
You can try to find samples of the original AccountController code to give you a starting place. They used IMembershipService and IFormsAuthenticationService
It's relatively straightforward to mock session, context, etc..
Take a look at the MVCContrib project (http://mvccontrib.codeplex.com/) as they have a helper for creating controllers that have all the various contextual objects populated (like HttpContext).