I've testing some new CLR 4.0 behavior in method inlining (cross-assembly inlining) and found some strage results:
Assembly ClassLib.dll:
using System.Diagnostics;
using System;
using System.Reflection;
using System.Security;
using System.Runtime.CompilerServices;
namespace ClassLib
{
public static class A
{
static readonly MethodInfo GetExecuting =
typeof(Assembly).GetMethod("GetExecutingAssembly");
public static Assembly Foo(out StackTrace stack) // 13 bytes
{
// explicit call to GetExecutingAssembly()
stack = new StackTrace();
return Assembly.GetExecutingAssembly();
}
public static Assembly Bar(out StackTrace stack) // 25 bytes
{
// reflection call to GetExecutingAssembly()
stack = new StackTrace();
return (Assembly) GetExecuting.Invoke(null, null);
}
public static Assembly Baz(out StackTrace stack) // 9 bytes
{
stack = new StackTrace();
return null;
}
public static Assembly Bob(out StackTrace stack) // 13 bytes
{
// call of non-inlinable method!
return SomeSecurityCriticalMethod(out stack);
}
[SecurityCritical, MethodImpl(MethodImplOptions.NoInlining)]
static Assembly SomeSecurityCriticalMethod(out StackTrace stack)
{
stack = new StackTrace();
return Assembly.GetExecutingAssembly();
}
}
}
Assembly ConsoleApp.exe
using System;
using ClassLib;
using System.Diagnostics;
class Program
{
static void Main()
{
Console.WriteLine("runtime: {0}", Environment.Version);
StackTrace stack;
Console.WriteLine("Foo: {0}\n{1}", A.Foo(out stack), stack);
Console.WriteLine("Bar: {0}\n{1}", A.Bar(out stack), stack);
Console.WriteLine("Baz: {0}\n{1}", A.Baz(out stack), stack);
Console.WriteLine("Bob: {0}\n{1}", A.Bob(out stack), stack);
}
}
Results:
runtime: 4.0.30128.1
Foo: ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
at ClassLib.A.Foo(StackTrace& stack)
at Program.Main()
Bar: ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
at ClassLib.A.Bar(StackTrace& stack)
at Program.Main()
Baz:
at Program.Main()
Bob: ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
at Program.Main()
So questions are:
Why JIT does not inlined Foo and Bar calls as Baz does? They are lower than 32 bytes of IL and are good candidates for inlining.
Why JIT inlined call of Bob and inner call of SomeSecurityCriticalMethod that is marked with the [MethodImpl(MethodImplOptions.NoInlining)] attribute?
Why GetExecutingAssembly returns a valid assembly when is called by inlined Baz and SomeSecurityCriticalMethod methods? I've expect that it performs the stack walk to detect the executing assembly, but stack will contains only Program.Main() call and no methods of ClassLib assenbly, to ConsoleApp should be returned.
The CLR 4.0 has ETW events for most of things and here is the ETW trace for Jit which should give out MethodJitInliningFailed reason and here is how you view the trace information
Related
I have a library which I use in both an ASP.NET app and a .NET Core app.
In both apps, I need to load settings from web.config(asp) in a virtual directory /CMSContent/Settings/web.config and appsettings.json(core).
I set an enviromentvariable in both apps named SystemType to WebForms(asp) and .NET Core (core), and build a function which reads data in the config file.
public static string SolutionDB()
{
string SystemType = System.Environment.GetEnvironmentVariable("SystemType", EnvironmentVariableTarget.Process);
switch (SystemType)
{
case "NetCore":
using (System.IO.StreamReader sr = new System.IO.StreamReader("appsettings.json", Encoding.UTF8))
{
var json = sr.ReadToEnd();
}
return "ComitoCMS_1";
case "WebForms":
System.Configuration.Configuration config = WebConfigurationManager.OpenWebConfiguration("/CMSContent/Settings/");
return config.AppSettings.Settings["SolutionDB"].Value;
break;
default:
return string.empty;
}
return string.empty;
}
When accessing the function from .net core it always returns the error:
TypeLoadException: Could not load type 'System.Web.Configuration.WebConfigurationManager' from assembly 'System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
Even though the code doesn't get into the case "WebForms".
Is there any other way to read either web.config when running the asp.net app and from appsettings.json when running the .net core app
I would suggest to create library with an abstraction. For example ConfigurationValueProvider class.
public abstract class ConfigurationValueProvider
{
public abstract GetValue(string key);
}
Then create another two libvraries. One with implementation for .NET Core and second with implementation for WebForms.
NET Core
public class AppSettingsValueProvider : ConfigurationValueProvider
{
public override GetValue(string key)
{
// Load value for NET Core apps
}
}
WebForms
public class WebConfigValueProvider : ConfigurationValueProvider
{
public override GetValue(string key)
{
// Load value for WebForms apps
}
}
Each project type should reference just the one it is supposed to be used.
It is an idea how to do it. You should change it according to your needs.
I am registering a type in MVVMLight SimpleIoc,
SimpleIoc.Default.Register<MyInjectingClass>();
Then I do a constructor injection of this type as a Func,
public class MyConsumerClass
{
readonly Func<MyInjectingClass> _injectingClassFactory;
public MyConsumerClass(Func<MyInjectingClass> injectingClassFactory)
{
_injectingClassFactory = injectingClassFactory;
}
}
But at runtime, I get this error,
Type not found in cache:
System.Func`1[[<...>.MyInjectingClass,
<...>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].
How can I constructor inject a type as a Func?
Note:
I'm doing this stuff in a Xamarin.IOs project.
The NuGet I use for MVVMLight SimpleIoc is this.
I figured out that, to inject a type as a Func, then you have to
Register the type first
Then register it as a Func (consuming the above registered type)
That is,
SimpleIoc.Default.Register<MyInjectingClass>(); // should happen first
SimpleIoc.Default.Register<Func<MyInjectingClass>>(
() => () => SimpleIoc.Default.GetInstance<MyInjectingClass>(
Guid.NewGuid().ToString()
));
The type is simply registered in a way telling it to return a Func object of the MyInjectingClass, that retrieves a new object each time.
Guid.NewGuid().ToString() makes sure a new MyInjectingClass object is returned for the SimpleIoc.Default.GetInstance<MyInjectingClass> method.
Imagine that I have interfaces like below which all inherits from ICacheManager<>
public interface ICacheManagerRuntime<T> : ICacheManager<T>
public interface ICacheManagerRedis<T> : ICacheManager<T>
public interface ICacheManagerRedisWithRuntime<T> : ICacheManager<T>
I want to inject ICacheManager{CacheType} interfaces to implemantation of Cache classes like:
CacheRuntime, CacheRedis, CacheRedisWithRuntime
With unity I want to inject them like below:
container.RegisterType<ICacheManagerRuntime<object>>(
new ContainerControlledLifetimeManager(),
new InjectionFactory((c, t, n) =>
{
return CacheFactory.Build... // Return CacheManager just with RuntimeCacheHandle
})));
container.RegisterType<ICacheManagerRedis<object>>(
new ContainerControlledLifetimeManager(),
new InjectionFactory((c, t, n) =>
{
return CacheFactory.Build... // Return CacheManager just with RedisCacheHandle
})));
container.RegisterType<ICacheManagerRedisWithRuntime<object>>(
new ContainerControlledLifetimeManager(),
{
return CacheFactory.Build... // Return CacheManager just with RuntimeCacheHandleWithRedisBackPlane
})));
What ever I have done, I am getting this exception:
An unhandled exception of type 'Microsoft.Practices.Unity.ResolutionFailedException' occurred in Microsoft.Practices.Unity.dll
Additional information: Resolution of the dependency failed, type = "Solid.Play.Business.Interfaces.IProductService", name = "(none)".
Exception occurred while: Resolving parameter "cache" of constructor Solid.Play.Cache.Caches.CacheRuntime(Solid.Play.Cache.Interfaces.ICacheManagerRuntime`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] cache).
Exception is: InvalidCastException - Unable to cast object of type 'CacheManager.Core.BaseCacheManager`1[System.Object]' to type 'Solid.Play.Cache.Interfaces.ICacheManagerRuntime`1[System.Object]'.
-----------------------------------------------
At the time of the exception, the container was:
Resolving Solid.Play.Business.Services.ProductService,(none) (mapped from Solid.Play.Business.Interfaces.IProductService, (none))
Resolving Solid.Play.Cache.Interception.CachingInterceptorBehavior,(none)
Resolving parameter "cache" of constructor Solid.Play.Cache.Interception.CachingInterceptorBehavior(Solid.Play.Cache.Interfaces.ICacheSolid cache)
Resolving Solid.Play.Cache.Caches.CacheSolid,(none) (mapped from Solid.Play.Cache.Interfaces.ICacheSolid, (none))
Resolving parameter "cacheRuntime" of constructor Solid.Play.Cache.Caches.CacheSolid(Solid.Play.Cache.Interfaces.ICacheRuntime cacheRuntime, Solid.Play.Cache.Interfaces.ICacheRedis cacheRedis, Solid.Play.Cache.Interfaces.ICacheRedisWithRuntime cacheRedisWithRuntime)
Resolving Solid.Play.Cache.Caches.CacheRuntime,(none) (mapped from Solid.Play.Cache.Interfaces.ICacheRuntime, (none))
Resolving parameter "cache" of constructor Solid.Play.Cache.Caches.CacheRuntime(Solid.Play.Cache.Interfaces.ICacheManagerRuntime`1[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] cache)
The cast does not work because you are trying to cast an instance to an interface it does not implement.
Simplified, what you are trying looks like this:
public interface IBase
{
}
public interface ISub : IBase { }
public class BaseClass : IBase
{
}
var sub = (ISub)new BaseClass();
If you want to inject different kind of instances of the same interfaces, the Unity DI framework provides a way to do that via named injection.
Example:
container.RegisterType<ICacheManager<object>>("runtimeCache",
new ContainerControlledLifetimeManager(),
new InjectionFactory((c, t, n) =>
{
return CacheFactory.Build<object>(s =>
{
s.WithSystemRuntimeCacheHandle("cache.runtime");
});
}));
container.RegisterType<ICacheManager<object>>("redisCache",
new ContainerControlledLifetimeManager(),
new InjectionFactory((c, t, n) =>
{
return CacheFactory.Build<object>(s =>
{
s.WithRedisConfiguration("cache.redis", config =>
{
config
.WithAllowAdmin()
.WithDatabase(0)
.WithEndpoint("localhost", 6379);
})
.WithRedisCacheHandle("cache.redis");
});
}));
To resolve the first one, you'd use
var runtimeCache = container.Resolve<ICacheManager<object>>("runtimeCache");
You can inject the ICacheManager interface to constructors with attributes for example.
public YourClass([Dependency("runtimeCache")] ICacheManager<object> cache)
{
}
I am using a Ninject DI in my web application with a bunch of technoligies from Asp.Net stack (MVC, Web Api 2, SignalR).
I have managed to make DI work for all technologies in use with the following approach:
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
internal static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
return kernel;
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel);
DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel));
// Binding services here
}
}
So far so good.
All this was working with Web Api being initialized in Global.asax.
Now I'm switching to OWIN pipeline. So I removed GlobalConfiguration.Configure(WebApiConfig.Register); from Global.asax and added
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
to my OwinStartup class. DI for Web Api stoped working.
I started searching for appropriate solution and found Ninject.Web.WebApi.OwinHost package. So in order to have a single Kernel resolving dependencies for all technologies, I've made the following changes:
in OwinStartup:
app.UseNinjectMiddleware(NinjectWebCommon.CreateKernel);
app.UseNinjectWebApi(config);
in NinjectWebCommon:
//[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(App.TradingServer.ConfiguratorApp.App_Start.NinjectWebCommon), "Start")]
//[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(App.TradingServer.ConfiguratorApp.App_Start.NinjectWebCommon), "Stop")]
These lines were disabled to avoid initializing kernel twice.
This fixed DI for Web Api but not for SignalR. When client tries to connect to hub I get the following exception:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.AspNet.SignalR.PersistentConnection.ProcessNegotiationRequest(HostContext context)
at Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequest(HostContext context)
at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.ProcessRequest(HostContext context)
at Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequest(IDictionary`2 environment)
at Microsoft.AspNet.SignalR.Owin.Middleware.HubDispatcherMiddleware.Invoke(IOwinContext context)
at Microsoft.Owin.Infrastructure.OwinMiddlewareTransition.Invoke(IDictionary`2 environment)
at Microsoft.Owin.Mapping.MapMiddleware.<Invoke>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at System.Web.Http.Owin.HttpMessageHandlerAdapter.<InvokeCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Ninject.Web.Common.OwinHost.OwinBootstrapper.<Execute>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContextStage.<RunApp>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.<DoFinalWork>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.Owin.Host.SystemWeb.Infrastructure.ErrorState.Rethrow()
at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar)
at Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.IntegratedPipelineContext.EndFinalWork(IAsyncResult ar)
at System.Web.HttpApplication.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) | RuntimeMethodInfo.UnsafeInvokeInternal => RuntimeMethodHandle.InvokeMethod => Application.Application_Error
I am kind of lost. I'we read around two dozens of articles but none of them gave me the solution. Would appreciate any help.
My final aim is to have a single kernel that serves Web Api, MVC and SignalR and supports OWIN pipeline.
Edit: Since I've got a comment that my case might be a duplicate of another question I believe I need to give some explanations.
I have three scenarios.
WebApi initialization in Global.asax with GlobalConfiguration.Configure(WebApiConfig.Register), Ninject initialization with NinjectWebCommon and Bootstrapper.
This gives me both injection in WebApi and SignalR. But since I would like to move WebApi initialization to OWIN startup this approach is obsolete.
WebApi initialization with OWIN Startup, Ninject initialization with NinjectWebCommon and Bootstrapper.
SignalR injection works, WebApi injection does not work.
WebApi initialization with OWIN Startup, Ninject initialization with UseNinjectMiddleware, UseNinjectWebApi.
WebApi injection works, SignalR injection does not work.
So basically I need to figure out how to put this together so that both WebApi and SignalR injection work when I initialize WebApi on OWIN pipeline.
The code for NinjectWebCommon is in original question below. It contains code for creating SignalR resolver but it does not help in scenario 3.
Edit 2: After a couple of hours of trial and error method I came to a conclusion that calling
app.UseNinjectMiddleware(NinjectWebCommon.CreateKernel);
app.UseNinjectWebApi(config);
conflicts with this call:
GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel);
So problem description narrows to this. When I use the following pattern SignalR stops working:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseNinjectMiddleware(CreateKernel);
app.UseNinjectWebApi(config);
GlobalHost.HubPipeline.AddModule(new GlobalSignalRExceptionHandler());
app.MapSignalR();
}
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel);
DependencyResolver.SetResolver(new Ninject.Web.Mvc.NinjectDependencyResolver(kernel));
return kernel;
}
But if I comment the line
//GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel);
SignalR starts working again. But no injection inside hubs of course.
Finally I managed to get the working Ninject configuration that supports OWIN pipe, WebApi, MVC and SignalR.
By the time when I posted the question I had a work-around (which was disabling DI in SignalR hubs) so I decided to waste no more time on this and moved on.
But when I tried running OWIN in-memory Test Server with my Startup class it occurred that DI was not working. CreateKernel method was called too late which resulted in creating several instances of an object that was used in sengleton scope.
After playing with different variations of initialization I've made DI work for OWIN Test Server and it also fixed the SignalR DependencyResolver.
The Solution:
I stopped using packages Ninject.Web.Common.OwinHost and Ninject.Web.WebApi.OwinHost so these calls were removed from my Configuration method:
//app.UseNinjectMiddleware(NinjectWebCommon.CreateKernel);
//app.UseNinjectWebApi(config);
Instead I do the following:
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
NinjectWebCommon.Start();
config.DependencyResolver = new NinjectDependencyResolver(NinjectWebCommon.bootstrapper.Kernel);
app.UseWebApi(config);
app.MapSignalR();
}
public static class NinjectWebCommon
{
private static bool _isStarted;
internal static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
// When creating OWIN TestService instances during unit tests
// Start() method might be called several times
// This check ensures that Ninject kernel is initialized only once per process
if (_isStarted)
return;
_isStarted = true;
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
internal static IKernel CreateKernel()
{
var kernel = new StandardKernel();
RegisterServices(kernel);
return kernel;
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
// DI for SignalR
GlobalHost.DependencyResolver = new Microsoft.AspNet.SignalR.Ninject.NinjectDependencyResolver(kernel);
// DI for MVC
DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel));
// Binding code here
kernel.Bind<Something>().ToSelf().InSingletonScope();
}
}
in order to use a dependency resolver for both WebApi and SignalR you need to implement a class that looks like this:
public class NinjectDependencyResolver : Microsoft.AspNet.SignalR.DefaultDependencyResolver,
System.Web.Http.Dependencies.IDependencyResolver
{
public readonly IKernel Kernel;
public NinjectDependencyResolver(string moduleFilePattern)
: base()
{
Kernel = new StandardKernel();
Kernel.Load(moduleFilePattern);
}
public override object GetService(Type serviceType)
{
var service = Kernel.TryGet(serviceType) ?? base.GetService(serviceType);
return service;
}
public override IEnumerable<object> GetServices(Type serviceType)
{
IEnumerable<object> services = Kernel.GetAll(serviceType).ToList();
if (services.IsEmpty())
{
services = base.GetServices(serviceType) ?? services;
}
return services;
}
public System.Web.Http.Dependencies.IDependencyScope BeginScope()
{
return this;
}
public void Dispose()
{ }
}
then in your startup class you should register NinjectDependencyResolver for both WebApi and SignalR, like this:
public void Configuration(IAppBuilder app)
{
var dependencyResolver = new NinjectDependencyResolver("*.dll");
var httpConfiguration = new HttpConfiguration();
httpConfiguration.DependencyResolver = dependencyResolver;
app.UseWebApi(httpConfiguration);
var hubConfig = new HubConfiguration { Resolver = dependencyResolver };
app.MapSignalR(hubConfig);
}
SignalR must be configured after your dependency injection configuration. So, in your OWIN Startup class, make sure that app.MapSignalR() is called after setting the MVC dependency resolver (System.Web.MVC.DependencyResolver), the WebApi dependency resolver (System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver) and the SignalR dependency resolver (Microsoft.AspNet.SignalR.GlobalHost.DependencyResolver).
http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-signalr-20-self-host
In my case, my hubs are in a project referenced from the project code that spins up the self-hosted application.
On the line connection.Start().Wait(); I get an exception. The following is the sequence of exceptions thrown at that line:
The specified registry key does not exist System.IO.IOException
'MessageHub' Hub could not be resolved InvalidOperationException
The remote server returned an error: (500) Internal Server Error WebException
The signature of the message hub class in the referenced project is public class MessageHub : Hub.
Update: To test the theory, I moved the hub class from the referenced project into my test project and updated the namespace. It worked. So I think the theory here is sound... default hub resolution does not find hubs in referenced project or in separate namespace.
How can I convince MapHubs to find the test hub in the referenced project?
I think that I have found an answer to this.
After doing some digging around the source code it seems that SignalR uses the following method to designate an IAssemblyLocator to locate Hubs.
internal static RouteBase MapHubs(this RouteCollection routes, string name, string path, HubConfiguration configuration, Action<IAppBuilder> build)
{
var locator = new Lazy<IAssemblyLocator>(() => new BuildManagerAssemblyLocator());
configuration.Resolver.Register(typeof(IAssemblyLocator), () => locator.Value);
InitializeProtectedData(configuration);
return routes.MapOwinPath(name, path, map =>
{
build(map);
map.MapHubs(String.Empty, configuration);
});
}
public class BuildManagerAssemblyLocator : DefaultAssemblyLocator
{
public override IList<Assembly> GetAssemblies()
{
return BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList();
}
}
public class DefaultAssemblyLocator : IAssemblyLocator
{
public virtual IList<Assembly> GetAssemblies()
{
return AppDomain.CurrentDomain.GetAssemblies();
}
}
This got me to try to simply add my external assembly to current domain because although it was referenced it was not being loaded.
So before calling WebApp.Start I call the following line.
static void Main(string[] args)
{
string url = "http://localhost:8080";
// Add this line
AppDomain.CurrentDomain.Load(typeof(Core.Chat).Assembly.FullName);
using (WebApp.Start<Startup>(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
Where Core.Chat is simply the Hub class I'm using.
And then the hubs defined in referenced assembly are loaded.
There might be a more straight forward way to go about this but I could not find anything in the documentation.
Hope this helps.