SignalR cannot call client function when using StructureMap dependency resolver - c#

When I include a structuremap dependency resolver in my global.asax for signalR any calls to client functions no longer arrive at the browser although I can see them if in the logging pipeline.
Here's an extract of my global.asax:
var container = ObjectFactory.Container;
// Now configure SignalR, MVC, ASP.Net and SharpRepository
GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule());
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
// TODO: Work out why this breaks the connection between the server and the client.
GlobalHost.DependencyResolver =
ObjectFactory.GetInstance<IDependencyResolver>();
RouteTable.Routes.MapHubs();
DependencyResolver.SetResolver(
new StructureMapDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver =
new StructureMapDependencyResolver(container);
RepositoryDependencyResolver.SetDependencyResolver(
new SharpRepository.Ioc.StructureMap.StructureMapDependencyResolver(container));
Here the implementation of the structuremap resolver:
public class StructureMapSignalRDependencyResolver
: DefaultDependencyResolver
{
private readonly IContainer _container;
public StructureMapSignalRDependencyResolver(
IContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
_container = container;
}
public override object GetService(Type serviceType)
{
object result = null;
try
{
result =
!serviceType.IsAbstract &&
!serviceType.IsInterface &&
serviceType.IsClass
? _container.GetInstance(serviceType)
: (_container.TryGetInstance(serviceType)
?? base.GetService(serviceType));
}
catch (Exception ex)
{
Tracing.Error(
"[StructureMapSignalRDependencyResolver]",
Tracing.SerializeException(ex));
}
if (result == null)
{
Tracing.Information(
"[StructureMapSignalRDependencyResolver]",
"Could retrieve object of type {0}",serviceType.ToString());
}
return result;
}
public override IEnumerable<object> GetServices(
Type serviceType)
{
IEnumerable<object> result = null;
try
{
result = _container.GetAllInstances(serviceType)
.Cast<object>().Concat(
base.GetServices(serviceType));
}
catch (Exception ex)
{
Tracing.Error(
"[StructureMapSignalRDependencyResolver]",
Tracing.SerializeException(ex));
}
if (result == null)
{
Tracing.Information(
"[StructureMapSignalRDependencyResolver]",
"Could retrieve object of type {0}", serviceType.ToString());
}
return result;
}
public override void Register(Type serviceType,
Func<object> activator)
{
Tracing.Information(
"[StructureMapSignalRDependencyResolver]",
"Registering object of type {0}",
serviceType.ToString());
base.Register(serviceType, activator);
}
public override void Register(Type serviceType,
IEnumerable<Func<object>> activators)
{
Tracing.Information(
"[StructureMapSignalRDependencyResolver]",
"Registering object of type {0}",
serviceType.ToString());
base.Register(serviceType, activators);
}
}
Here is the implementation of StructureMap Registry (there are many more which include the application assemblies).
[RegistryOrder(Order = 6)]
public class SignalRRegistry : Registry
{
public SignalRRegistry()
{
For<IDependencyResolver>().Singleton()
.Use<StructureMapSignalRDependencyResolver>();
For<IHubConnectionContext>().Singleton()
.Use(GlobalHost.ConnectionManager
.GetHubContext<BOSSHub>().Clients);
}
//public void Configure()
//{
// GlobalHost.DependencyResolver =
// ObjectFactory.GetInstance<IDependencyResolver>();
// RouteTable.Routes.MapHubs();
//}
}
I've rolled back the Hub class so that it no longer has an dependencies; the output from WhatDoIHave seems to have some entries for Microsoft.AspNet.SignalR but without any concrete relationships.
I wondering if I missed a step in my registry ? I get no exceptions anywhere; it just stops working, if I comment out the GlobalHost.DependencyResolver line from global.asax all is well.
I hope someone could share with me a DI implementation for signalR that is working for them.
Many Thanks
UPDATE: Thanks for sorting out the bad formatting for me - appreciate that.
UPDATE: I've put together a small test project which shows the issue. I was surprised if I'm honest I was able to reproduce it as the application that I'm working on it pretty big and complex with lot's of StructureMap stuff going on. I've uploaded into GitHub : https://github.com/johnk27stars/SignalRTest.git - Thanks to anyone who could spare a minute to take a look.

After some honest advice from Jeremy Miller we switched to AutoFac and it working fine.

Related

Implement log4net in asp.net core webapi

I have a asp.net core web api. As of now I'm using ILogger to log the messages. But ILogger doesn't have Fatal loglevel in it. There is Critical level, but our team requires Fatal word instead of Critical word.Is there any way I can tweak the work which gets printed to logs?
If not, I want to replace ILogger with log4Net which has Fatal level in it.So this is what I have done , but somehow it is not working.
I have multi layer architecture : WebApplication1, WebApplication1.Helper . All these are different projects with in a solution.
In WebApplication1:
I have added Microsoft.Extensions.Logging.Log4Net.AspNetCore reference.
In startup.cs
public void ConfigureServices(IServiceCollection apiServices)
{
var provider = apiServices.BuildServiceProvider();
var factory = new LoggerFactory()
.AddConsole().AddLog4Net().AddApplicationInsights(provider, LogLevel.Information);
apiServices.AddSingleton(factory);
apiServices.AddLogging();
apiServices.AddMvc();
apiServices.AddOptions();
}
HomeController.cs
[Route("api/[controller]")]
[ApiController]
public class HomeController : Controller
{
private readonly ILog4NetHelper _logHelper = new Log4NetHelper();
[HttpGet]
public virtual IActionResult GetData()
{
try
{
_logHelper.Log4NetMessage("Info", "Start GetData");
return new OkObjectResult("Your in Home Controller");
}
catch (Exception ex)
{
_logHelper.Log4NetMessage("Error", "Exception in GetData" + ex.Message);
throw;
}
}
}
WebApplication1.Helper project
And in WebApplication1.Helper project , I have added a interface ILog4NetHelper and class which implements this interface Log4NetHelper. Also I have added log4Net config file.
public class Log4NetHelper : ILog4NetHelper
{
readonly ILog _log =log4net.LogManager.GetLogger(typeof(Log4NetHelper));
public void Log4NetMessage(string type,string message)
{
string logMessage = message;
switch (type)
{
case "Info":
_log.Info(logMessage);
break;
case "Error":
_log.Error(logMessage);
break;
case "Fatal":
_log.Fatal(logMessage);
break;
default:
_log.Info(logMessage);
break;
}
}
}
When I host this application and run this, it is giving me a 500 internal server error. The error message is this :
InvalidOperationException: Unable to resolve service for type
'WebApplication1.Helper.Log4NetHelper' while attempting to activate
'WebApplication1.Helper.Log4NetHelper'.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type
serviceType, Type implementationType, CallSiteChain callSiteChain,
ParameterInfo[] parameters, bool throwIfCallSiteNotFound)
How can I resolve this?
ASP.Net Core built-in logging was Microsoft's stab at doing logging the Microsoft, dependency-injected way. It follows the basic principles and tenets of the Log4Net approach (which has been standardized across .Net, Java, and Javascript, among others). So, the two approaches are not entirely at odds with one another.
However, in this particular case, the implementation appears to actually conflict with the intent of both approaches to logging.
Log4Net separates out the two acts of recording and writing log output. The first is done via the ILog interface. The second is done via one of the Appenders.
Similarly, the ASP.net Core API uses an ILogger and one or more Providers to emit log messages.
As I am more comfortable with log4net, and also don't see much of a point in having loggers added via dependency injection in EVERY CLASS, I used log4net's approach of LogManager.GetLogger(typeof(MyClass)) rather than doing it via Microsoft DI. My appenders also run through log4net. Thus, my implementation focused on translating the Microsoft logging outputs into the log4net format, which appears to be the what your team would like but the opposite of what you are doing here. My approach was based on this article. The code I used is below.
Implementation Notes:
I set up a custom appender via log4net which writes my logs out to a logging database (commonly-used databases for this are loki and/or elasticsearch).
In the Configure() method on startup.cs, you'll need to have the following line (note that I instantiate the customAppender in the ConfigureServices and then add it to the DI, but you wouldn't have to do it this way):
loggerFactory.AddLog4Net(_serverConfig.LoggingSettings, customAppender);
It is also necessary to have the following in ConfigureServices() (not sure why, but it seems to ensure that the regular .net core logging kicks in).
services.AddLogging(config => {
config.AddDebug();
config.AddConsole();
});
Log4NetLogger.cs
/// <summary>
/// Writes ASP.net core logs out to the log4net system.
/// </summary>
public class Log4NetLogger : ILogger
{
private readonly ILog _logger;
public Log4NetLogger(string name)
{
_logger = LogManager.GetLogger(typeof(Log4NetProvider).Assembly, name);
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
switch (logLevel) {
case LogLevel.Critical:
return _logger.IsFatalEnabled;
case LogLevel.Debug:
case LogLevel.Trace:
return _logger.IsDebugEnabled;
case LogLevel.Error:
return _logger.IsErrorEnabled;
case LogLevel.Information:
return _logger.IsInfoEnabled;
case LogLevel.Warning:
return _logger.IsWarnEnabled;
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
if (!this.IsEnabled(logLevel)) {
return;
}
if (formatter == null) {
throw new ArgumentNullException(nameof(formatter));
}
string message = null;
if (null != formatter) {
message = formatter(state, exception);
}
if (!string.IsNullOrEmpty(message) || exception != null) {
switch (logLevel) {
case LogLevel.Critical:
_logger.Fatal(message);
break;
case LogLevel.Debug:
case LogLevel.Trace:
_logger.Debug(message);
break;
case LogLevel.Error:
_logger.Error(message);
break;
case LogLevel.Information:
_logger.Info(message);
break;
case LogLevel.Warning:
_logger.Warn(message);
break;
default:
_logger.Warn($"Encountered unknown log level {logLevel}, writing out as Info.");
_logger.Info(message, exception);
break;
}
}
}
Log4NetProvider.cs
/// <summary>
/// Returns new log4net loggers when called by the ASP.net core logging framework
/// </summary>
public class Log4NetProvider : ILoggerProvider
{
private readonly LoggingConfig _config;
private readonly ConcurrentDictionary<string, Log4NetLogger> _loggers =
new ConcurrentDictionary<string, Log4NetLogger>();
private readonly ILoggerRepository _repository =
log4net.LogManager.CreateRepository(typeof(Log4NetProvider).Assembly, typeof(log4net.Repository.Hierarchy.Hierarchy));
public Log4NetProvider(LoggingConfig config, MyCustomAppender otherAppender)
{
_config = config;
BasicConfigurator.Configure(_repository, new ConsoleAppender(), otherAppender);
LogManager.GetLogger(this.GetType()).Info("Logging initialized.");
}
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, this.CreateLoggerImplementation(categoryName));
}
public void Dispose()
{
_loggers.Clear();
}
private Log4NetLogger CreateLoggerImplementation(string name)
{
return new Log4NetLogger(name);
}
}
Log4NetExtensions.cs
/// <summary>
/// A helper class for initializing Log4Net in the .NET core project.
/// </summary>
public static class Log4netExtensions
{
public static ILoggerFactory AddLog4Net(this ILoggerFactory factory, LoggingConfig config, MyCustomAppender appender)
{
factory.AddProvider(new Log4NetProvider(config, appender));
return factory;
}
}
I ran into multiple issues with Log4Net few years ago - i don't remember exactly what, since then i switched to my own implementation for log, it is not complicated to write your own. my implementation is copied below.
When i had to change the log class, it was nightmare since i had to replace all its usage with the new implementation.
There are 2 benefits of custom class.
A. It will match your application's requirement.
B. It acts as a wrapper for any underlying logging framework that you may use. So now even if i have to change logging in future, i just have to change the internal implementation of this custom class.
Since writing this wrapper, i have changed from log4net to Microsofts log extensions and now 100 % my own implementation.
using System;
using System.Text;
using System.IO;
namespace BF
{
//This is a custom publisher class use to publish the exception details
//into log file
public class LogPublisher
{
private static object _lock;
static LogPublisher()
{
_lock = new object();
}
//Constructor
public LogPublisher()
{
}
//Method to publish the exception details into log file
public static void Debug(string message)
{
if (ClientConfigHandler.Config.IsDebugMode())
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#DEBUG");
}
}
public static void DebugBackgroundAction(string message)
{
if (ClientConfigHandler.Config.IsDebugMode())
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#DEBUG #BG");
}
}
public static void BackgroundAction(string message)
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#BG");
}
public static void Publish(string message)
{
Exception eMsg = new Exception(message);
Publish(eMsg, "");
}
public static void Publish(Exception fException)
{
Publish(fException, "");
}
public static void Publish(Exception fException, string prefix)
{
if (fException == null) return;
// Load Config values if they are provided.
string m_LogName = ResourceConfig.LogFileName;
// Create StringBuilder to maintain publishing information.
StringBuilder strInfo = new StringBuilder();
// Record required content of the AdditionalInfo collection.
strInfo.AppendFormat("{0}**T {1} {2} ", Environment.NewLine, CommonConversions.CurrentTime.ToString(CommonConversions.DATE_TIME_FORMAT_LOG), prefix);
// Append the exception message and stack trace
strInfo.Append(BuildExceptionLog(fException, false));
try
{
lock (_lock)
{
FileStream fs = File.Open(m_LogName, FileMode.Append, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
sw.Write(strInfo.ToString());
sw.Close();
fs.Close();
}
}
catch
{
//ignore log error
}
}
private static string BuildExceptionLog(Exception fException, bool isInnerExp)
{
StringBuilder strInfo = new StringBuilder();
if (fException != null)
{
string msgType;
if (isInnerExp)
{
msgType = "#IN-ERR";
}
else if (fException.StackTrace == null)
{
msgType = "#INF";
}
else
{
msgType = "#ERR";
}
strInfo.AppendFormat("{0}: {1}", msgType, fException.Message.ToString());
if (fException.StackTrace != null)
{
strInfo.AppendFormat("{0}#TRACE: {1}", Environment.NewLine, fException.StackTrace);
}
if (fException.InnerException != null)
{
strInfo.AppendFormat("{0}{1}", Environment.NewLine, BuildExceptionLog(fException.InnerException, true));
}
}
return strInfo.ToString();
}
}
}
The Microsoft.Extensions.Logging system doesn't contain Fatal level as an accepted LogLevel.
But, if you are interested in treat the Critical messages as log4net's Fatal level, starting at v.2.2.5, there is a property on Microsoft.Extensions.Logging.Log4Net.AspNetCore nuget package that allow you to decide if the Critical Level is managed as Fatal or not.
Please, review the original issue to check why the implementation was done as it was.
I see one issue in your code. Your class Log4NetHelper requires in constructor an instance of Log4NetHelper. So it can't create Log4NetHelper. Why you passing Log4NetHelper in constructor, it smells. You should provide default constructor, or constructor with parameters which are registered in DI service.
Please try add parameterless constructor and check if it works, if not check exception and/or error message.

ResolutionException - Getting "Required dependency of type *********** could not be resolved"

Following is the exact scenario in my application.
I have used ServiceStack 3.9.48 and AutoFac 4.6.0 to develop a REST service.
Following is the code of AppHost which is inherited from AppHostBase
public AppHost()
:base("My Service Host", typeof(NotificationService).Assembly)
{
var builder = new ContainerBuilder();
builder.RegisterType<ConfigurationProvider>().As<IConfigurationProvider>();
builder.RegisterType<Logging>().As<ILogging>();
IoCContainer = builder.Build();
}
public override void Configure(Container container)
{
using (var scope = IoCContainer.BeginLifetimeScope())
{
var _logging = scope.Resolve<ILogging>();
JsConfig.EmitCamelCaseNames = true;
base.RequestFilters.Add(delegate (IHttpRequest req, IHttpResponse res, object dto)
{
HandleUncaughtExceptionDelegate uncaughtExceptionDelegate = null;
if (DateTime.Now.Year <= 2019)
{
if (uncaughtExceptionDelegate == null)
{
uncaughtExceptionDelegate = delegate (IHttpRequest request, IHttpResponse response, string operationName, Exception ex)
{
res.StatusCode = 0x191;
res.Write("Error: This service is unavailable till 2019: " + operationName);
};
}
base.ExceptionHandler = uncaughtExceptionDelegate;
HttpResponse originalResponse = res.OriginalResponse as HttpResponse;
originalResponse.SuppressFormsAuthenticationRedirect = false;
res.End();
}
});
base.ServiceExceptionHandler = delegate (object request, Exception exception)
{
_logging.Log(exception);
return DtoUtils.HandleException(this, request, exception);
};
}
}
I can see this code working fine, and logging the exception if the condition is not satisfied.
However, there is an issue when I try to make a call to the API endpoint which invokes following:
public class NotificationService: Service
{
private IConfigurationProvider _configurationProvider;
public NotificationService(IConfigurationProvider _configurationProvider)
{
_configurationProvider = configurationProvider;
}
public object Post(SendEventNotification request)
{
return new SendEventNotificationResponse { SentStatus = SendNotification(_configurationProvider.GetValue("EncId")) };
}
}
It gives me an error saying -
Required dependency of type IConfigurationProvider could not be
resolved.
Can anyone please suggest what could be the reason here? I believe the instances which were initialized during AppHost have not been persisted.
I am sure, something is missing, but unable to figure it out.
Any help on this will be much appreciated.
Thanks and Regards,
Nirman
I figured it out an issue of ServiceStack only. There was no need to use Autofac as Servicestack itself provides DI resolution. Also, I had to use "RegisterAutoWiredAs" method of ServiceStack's Container object.

SignalR target a specific user from server with custom IUserIdProvider

I've an application using SignalR & WebAPI. I've a custom Authorization context based on a token, which I give on each SignalR requests using the QueryString.
I've implement and IUserIdProvider, in order to retrieve my User from the Token.
And finally, I want to call a client method from the server, for a specific User (with is ID), I'm using a HubContext from the GlobalHost.ConnectionManager.
My problem is that my User is never find from my HubContext, but it is from the Hub itself...
Here is my IUserIdProvider implementation
public class SignalRUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
var token = request.QueryString["token"];
var scope = GetUnprotectedScope(token);
if (scope == null)
return null;
return scope.Id_User.ToString();
}
}
Here is my Hub imlementation
[HubName("notifier")]
public class NotifierHub : Hub
{
public void Notify(string message)
{
Clients.User("1").Notify(message); //When call from a client, this works very well, only the User with the Id = 1 receive the notification
}
}
And finally, I use this to call the client method from my server:
GlobalHost
.ConnectionManager
.GetHubContext<NotifierHub>()
.Clients
.User("1")
.Notify(notification.Message);
// This does nothing...
I'm out of solution at this point, I don't understand what happens, does anyone have already achieve this ?
I've finally figured the issue, but I don't know how to fix it...
The actual issue is simple, the Hub itself has a good context with the clients and everything, but the GlobalHost.ConnectionManager have nothing.
If I change my Hub to something like this:
[HubName("notifier")]
public class NotifierHub : Hub
{
public void Notify(string message)
{
Clients.User("1").Notify(message + " from Hub itself");
GlobalHost
.ConnectionManager
.GetHubContext<NotifierHub>()
.Clients
.User("1")
.Notify(message + " from ConnectionManager");
}
}
My client receive "My message from Hub itself", but never receive "My message from ConnectionManager".
In conclusion, I've a problem with my DependencyInjection... I'm using Structuremap, with this DependencyResover :
public class StructureMapSignalRDependencyResolver : DefaultDependencyResolver
{
private IContainer _container;
public StructureMapSignalRDependencyResolver(IContainer container)
{
_container = container;
}
public override object GetService(Type serviceType)
{
if (serviceType == null)
return null;
var service = _container.TryGetInstance(serviceType) ?? base.GetService(serviceType);
if (service != null) return service;
return (!serviceType.IsAbstract && !serviceType.IsInterface && serviceType.IsClass)
? _container.GetInstance(serviceType)
: _container.TryGetInstance(serviceType);
}
public override IEnumerable<object> GetServices(Type serviceType)
{
var objects = _container.GetAllInstances(serviceType).Cast<object>();
return objects.Concat(base.GetServices(serviceType));
}
}
My Startup file:
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", RegisterSignalR);
}
public static void RegisterSignalR(IAppBuilder map)
{
var resolver = new StructureMapSignalRDependencyResolver(IoC.Initialize());
var config = new HubConfiguration { Resolver = resolver };
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR(config);
}
And finally my Registry...
For<Microsoft.AspNet.SignalR.IDependencyResolver>().Add<StructureMapSignalRDependencyResolver>();
For<INotifier>().Use<SignalRNotifier>();

Failing to resolve dependencies with Unity and WebAPI

I'm receiving this odd error when trying to run my controller action in WebAPI:
An error occurred when trying to create a controller of type 'PostController'. Make sure that the controller has a parameterless public constructor.
Resolution of the dependency failed, type = "Example.Controllers.PostController", name = "(none)".
Exception occurred while: Calling constructor Example.Models.PostRepository().
Exception is: NullReferenceException - Object reference not set to an instance of an object.
Here's the problematic code:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var container = new UnityContainer();
container.RegisterType<IPostRepository, PostRepository>(new HierarchicalLifetimeManager());
config.DependencyResolver = new UnityResolver(container);
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ExampleApi",
routeTemplate: "api/{controller}"
);
}
}
public class PostController : ApiController
{
IPostRepository _repository;
public PostController(IPostRepository repository)
{
_repository = repository;
}
public IEnumerable<Post> GetAllProducts()
{
return _repository.GetAll();
}
}
public class PostRepository : IPostRepository
{
private IDbConnection _connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
public IEnumerable<Post> GetAll()
{
return _connection.Query<Post>("SELECT * FROM Posts").ToList();
}
}
public class UnityResolver : IDependencyResolver
{
protected IUnityContainer Container;
public UnityResolver(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.Container = container;
}
public object GetService(Type serviceType)
{
if (!Container.IsRegistered(serviceType))
{
if (serviceType.IsAbstract || serviceType.IsInterface)
{
return null;
}
}
return Container.Resolve(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return Container.ResolveAll(serviceType);
}
catch (ResolutionFailedException)
{
return new List<object>();
}
}
public IDependencyScope BeginScope()
{
var child = Container.CreateChildContainer();
return new UnityResolver(child);
}
public void Dispose()
{
Container.Dispose();
}
}
Does anyone have any idea of what might be the cause? I followed this tutorial: http://www.asp.net/web-api/overview/advanced/dependency-injection
In my experience when I see this message its normally some dependency is not able to be constructed or is missing a dependency. You have IPostRepository registered to PostRepository so that looks good. What about the SqlConnection created in PostRepository? Could you run some test code against just the repository to see if it constructs OK on its own?
Also just in browsing through your code is the check for interfaces blocking your resolution of the IPostRepository in the UnityResolver class?
if (serviceType.IsAbstract || serviceType.IsInterface)
{
return null;
}
The following link has a working project which is based on the same tutorial that you mentioned.
Not much difference to your code except the database will be targeting localdb, and on the UnityResolver there is no checks for Abstract class, which doesn't make any difference I think.
Now, you can use the project as a startup point to add your logic, and know exactly when it fails and why.
hope this helps.

How do I connect the various pieces of my Web API Castle Windsor DI code?

How do I connect the various pieces of my Web API Castle Windsor DI code so that the Controller's routing selects the correct interface implementation?
Note: After several false starts/dead ends and partial victories (here and here and here), I am going to bountify this ASAP for the maximum 500 points. But I'm only going to award a really good answer - IOW, one that is clear enough that I can understand it and "plug it in" to my project so that I can hook a given concrete class to a particular Controller.
Here goes nothing: I have a Web API ("MVC") project. Really, though, this server project has no "V" (View), so maybe a better acronym would be MRC (Model/Repository/Controller).
At any rate, I'm trying to add DI to it using Castle Windsor.
I grok, and dig, the concept of swapping out concrete classes via constructor interface args. Just how to implement this functionality, though,
has been a beast I've been wrestling with, and I'm quite bruised and bloody at present, with mussed-up hair and mud-encrusted nostrils to boot.
I have, I think, most of the code I need - to start with, anyway. With DI in mind, I've now got a "DIPlumbing" folder, and a "DIInstallers" folder.
The "DIPlumbing" folder contains two classes: WindsorCompositionRoot.cs, and WindsorControllerFactory.cs.
The "DIInstallers" folder has, for now, three files, namely ISomethingProvider.cs, SomethingProvider.cs, and SomethingProviderInstaller.cs
SomethingProviderInstaller seems to be key in connecting the interfaces/classes in DIInstallers to the stuff in the DIPlumbing folder.
(I have also modified Global.asax.cs to replace the default controller routing business with the Castle Windsor replacement).
But I'm confused as to what classes I should be placing in the DIInstallers folder. Are these supposed to take the place of my Repositories (which likewise have an interface and a concrete class that implements that interface for each model)? IOW, should I basically move my Repository code into the DIInstallers folder - and then get rid of the IRepository and Repository units?
This, of course, would cause necessary changes to be made in the Controller classes, which as of now reference Repository classes.
Or do the Repositories and DIInstallers classes coexist? If so, what is the connection/affiliation between the Controllers, Installers, and Repositories?
It seems the more I read up on DI and Castle Windsor, the more confused I get. I don't know if I'm too dense for it, or if I'm trying to make it harder than it is, or if conflicting styles of using/presenting it are the problem. The bottom line is: I'm stuck in quicksand and need Johnny Quest to extend a sturdy bamboo rod.
The best answer of all, perhaps, and probably too much to ask for, would be a visual representation of how all these components - Controllers, Models, Repositories, Installers, Global.asax.cs, composition roots, factories, providers, etc., relate to each other.
For purposes of "full disclosure," I will add what I hope are the key elements of my code below to show what I've got and how it (hopefully) connects to each other.
Composition Root:
public class WindsorCompositionRoot : IHttpControllerActivator
{
private readonly IWindsorContainer container;
public WindsorCompositionRoot(IWindsorContainer container)
{
this.container = container;
}
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var controller =
(IHttpController)this.container.Resolve(controllerType);
request.RegisterForDispose(
new Release(
() => this.container.Release(controller)));
return controller;
}
private class Release : IDisposable
{
private readonly Action release;
public Release(Action release)
{
this.release = release;
}
public void Dispose()
{
this.release();
}
}
}
Controller Factory:
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly IKernel kernel;
public WindsorControllerFactory(IKernel kernel)
{
this.kernel = kernel;
//According to my understanding of http://docs.castleproject.org/Windsor.Typed-Factory-Facility.ashx, I might need this:
kernel.AddFacility<TypedFactoryFacility>();
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
}
return (IController)kernel.Resolve(controllerType);
}
public override void ReleaseController(IController controller)
{
kernel.ReleaseComponent(controller);
}
// Note: The "Something" below will hopefully eventually be "Departments" and then other classes now represented in Models and their corresponding Repositories and Controllers
ISomethingProvider:
public interface ISomethingProvider
{
// These are placeholder methods; I don't know which I will need yet...
//bool Authenticate(string username, string password, bool createPersistentCookie);
//void SignOut();
}
SomethingProvider:
public class SomethingProvider : ISomethingProvider
{
// TODO: Implement methods in ISomethingProvider, once they have been added
}
SomethingProviderInstaller:
public class SomethingProviderInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.BasedOn(typeof(ISomethingProvider))
.WithServiceAllInterfaces());
// From http://app-code.net/wordpress/?p=676; see also http://devlicio.us/blogs/krzysztof_kozmic/archive/2009/12/24/castle-typed-factory-facility-reborn.aspx
container.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<IMyFirstFactory>().AsFactory());
}
}
Controller:
public class DepartmentsController : ApiController
{
private readonly IDepartmentRepository _deptsRepository;
public DepartmentsController(IDepartmentRepository deptsRepository)
{
if (deptsRepository == null)
{
throw new ArgumentNullException("deptsRepository is null");
}
_deptsRepository = deptsRepository;
}
public int GetCountOfDepartmentRecords()
{
return _deptsRepository.Get();
}
public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch)
{
return _deptsRepository.Get(ID, CountToFetch);
}
. . .
}
IRepository:
public interface IDepartmentRepository
{
int Get();
IEnumerable<Department> Get(int ID, int CountToFetch);
}
Repository:
public class DepartmentRepository : IDepartmentRepository
{
private readonly List<Department> departments = new List<Department>();
public DepartmentRepository()
{
using (var conn = new OleDbConnection(
#"Provider=Microsoft.ACE.OLEDB.12.0;[bla]"))
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT td_department_accounts.dept_no, IIF(ISNULL(t_accounts.name),'No Name provided',t_accounts.name) AS name FROM t_accounts INNER JOIN td_department_accounts ON t_accounts.account_no = td_department_accounts.account_no ORDER BY td_department_accounts.dept_no";
cmd.CommandType = CommandType.Text;
conn.Open();
int i = 1;
using (OleDbDataReader oleDbD8aReader = cmd.ExecuteReader())
{
while (oleDbD8aReader != null && oleDbD8aReader.Read())
{
int deptNum = oleDbD8aReader.GetInt16(0);
string deptName = oleDbD8aReader.GetString(1);
Add(new Department { Id = i, AccountId = deptNum, Name = deptName });
i++;
}
}
}
}
}
public int Get()
{
return departments.Count;
}
private Department Get(int ID) // called by Delete()
{
return departments.First(d => d.Id == ID);
}
public IEnumerable<Department> Get(int ID, int CountToFetch)
{
return departments.Where(i => i.Id > ID).Take(CountToFetch);
}
. . .
}
Global.asax.cs:
public class WebApiApplication : System.Web.HttpApplication
{
private static IWindsorContainer container;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
BootstrapContainer();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
private static void BootstrapContainer()
{
container = new WindsorContainer().Install(FromAssembly.This());
var controllerFactory = new WindsorControllerFactory(container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerActivator), new WindsorCompositionRoot(container));
}
protected void Application_End()
{
container.Dispose();
}
UPDATE
In trying to run the server, so that it could test it with Fiddler2 to see just what is being passed around, it failed on this line in WindsorControllerFactory:
public WindsorControllerFactory(IKernel kernel)
{
this.kernel = kernel;
kernel.AddFacility<TypedFactoryFacility>(); <-- throws exception here
}
...with "System.ArgumentException was unhandled by user code
HResult=-2147024809
Message=Facility of type 'Castle.Facilities.TypedFactory.TypedFactoryFacility' has already been registered with the container. Only one facility of a given type can exist in the container.
Source=Castle.Windsor
StackTrace:
at Castle.MicroKernel.DefaultKernel.AddFacility(String key, IFacility facility)
at Castle.MicroKernel.DefaultKernel.AddFacility(IFacility facility)
at Castle.MicroKernel.DefaultKernel.AddFacilityT
at HandheldServer.DIPlumbing.WindsorControllerFactory..ctor(IKernel kernel) in c:\HandheldServer\HandheldServer\DIPlumbing\WindsorControllerFactory.cs:line 28
at HandheldServer.WebApiApplication.BootstrapContainer() in c:\HandheldServer\HandheldServer\Global.asax.cs:line 69
at HandheldServer.WebApiApplication.Application_Start() in c:\HandheldServer\HandheldServer\Global.asax.cs:line 39"
UPDATE 2
In response to Cristiano's answer:
So are you saying I should add the following two files to my Installers folder (I do have a DIInstallers folder already)
PlatypusInstallerFactory.cs:
public class PlatypusInstallerFactory : InstallerFactory
{
public override IEnumerable<Type> Select(IEnumerable<Type> installerTypes)
{
var windsorInfrastructureInstaller = installerTypes.FirstOrDefault(it => it == typeof(WindsorInfrastructureInstaller));
var retVal = new List<Type>();
retVal.Add(windsorInfrastructureInstaller);
retVal.AddRange(installerTypes
.Where(it =>
typeof(IWindsorInstaller).IsAssignableFrom(it) &&
!retVal.Contains(it)
));
return retVal;
}
}
WindsorInfrastructureInstaller.cs:
public class WindsorInfrastructureInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility<TypedFactoryFacility>();
}
}
In your global.asax you'll create&use you installer factory as following
var installerFactory = new PlatypusInstallerFactory();
container.Install(FromAssembly.This(installerFactory));
If yes, what will that do for me? Does the above automagically register my Controller and/or Repository classes?
UPDATE 3
I am now using a lot of code from [http://blog.kerbyyoung.com/2013/01/setting-up-castle-windsor-for-aspnet.html#comment-form]
The key parts are, I think:
global.asax.cs:
private static IWindsorContainer _container;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ConfigureWindsor(GlobalConfiguration.Configuration);
}
public static void ConfigureWindsor(HttpConfiguration configuration)
{
_container = new WindsorContainer();
_container.Install(FromAssembly.This());
_container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));
var dependencyResolver = new WindsorDependencyResolver(_container);
configuration.DependencyResolver = dependencyResolver;
}
WindsorDependencyResolver.cs:
namespace HandheldServer
{
public class WindsorDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver
{
private readonly IWindsorContainer _container;
public WindsorDependencyResolver(IWindsorContainer container)
{
_container = container;
}
public IDependencyScope BeginScope()
{
return new WindsorDependencyScope(_container);
}
public object GetService(Type serviceType)
{
if (!_container.Kernel.HasComponent(serviceType))
{
return null;
}
return this._container.Resolve(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
if (!_container.Kernel.HasComponent(serviceType))
{
return new object[0];
}
return _container.ResolveAll(serviceType).Cast<object>();
}
public void Dispose()
{
_container.Dispose();
}
}
public class WindsorDependencyScope : IDependencyScope
{
private readonly IWindsorContainer _container;
private readonly IDisposable _scope;
public WindsorDependencyScope(IWindsorContainer container)
{
this._container = container;
this._scope = container.BeginScope();
}
public object GetService(Type serviceType)
{
if (_container.Kernel.HasComponent(serviceType))
{
return _container.Resolve(serviceType);
}
else
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this._container.ResolveAll(serviceType).Cast<object>();
}
public void Dispose()
{
this._scope.Dispose();
}
}
public class ApiControllersInstaller : IWindsorInstaller
{
public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly() // should it be Types instead of Classes?
.BasedOn<ApiController>()
.LifestylePerWebRequest());
}
}
// This idea from https://github.com/argeset/set-locale/blob/master/src/client/SetLocale.Client.Web/Configurations/IocConfig.cs
public class ServiceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDeliveryItemRepository>().ImplementedBy<DeliveryItemRepository>().LifestylePerWebRequest(),
Component.For<IDeliveryRepository>().ImplementedBy<DeliveryRepository>().LifestylePerWebRequest(),
Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository>().LifestylePerWebRequest(),
Component.For<IExpenseRepository>().ImplementedBy<ExpenseRepository>().LifestylePerWebRequest(),
Component.For<IInventoryItemRepository>().ImplementedBy<InventoryItemRepository>().LifestylePerWebRequest(),
Component.For<IInventoryRepository>().ImplementedBy<InventoryRepository>().LifestylePerWebRequest(),
Component.For<IItemGroupRepository>().ImplementedBy<ItemGroupRepository>().LifestylePerWebRequest());
}
}
}
UPDATE 4
This question is probably too general for SO, so I posted it on "Programmers"
UPDATE 5
Note: According to "The DI Whisperer" (Mark Seemann), IDependencyResolver should not be used, because it lacks a Release method (p. 207 of his book)
You should not mix installation vs resolving.
IOW your should not have
kernel.AddFacility<TypedFactoryFacility>();
in the WindsorControllerFactory
But the generic container configuration such registering TypedFactoryFacility should be executed in an installer called as earlier as possible.
In order to drive installer execution, you should use an Installer factory
public class YourInstallerFactory : InstallerFactory
{
public override IEnumerable<Type> Select(IEnumerable<Type> installerTypes)
{
var windsorInfrastructureInstaller = installerTypes.FirstOrDefault(it => it == typeof(WindsorInfrastructureInstaller));
var retVal = new List<Type>();
retVal.Add(windsorInfrastructureInstaller);
retVal.AddRange(installerTypes
.Where(it =>
typeof(IWindsorInstaller).IsAssignableFrom(it) &&
!retVal.Contains(it)
));
return retVal;
}
}
Where windsorInfrastructureInstaller will be somenthing like this
public class WindsorInfrastructureInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
// Resolvers
//container.Kernel.Resolver.AddSubResolver(new ArrayResolver(container.Kernel));
// TypedFactoryFacility
container.AddFacility<TypedFactoryFacility>();
}
}
In your global.asax you'll create&use you installer factory as following
var installerFactory = new YourInstallerFactory();
container.Install(FromAssembly.This(installerFactory));
Your "FrontEnd"(for example the mvc/webapi) project has a folder containing all installers(WindsorInfrastructureInstaller will be one of those) and the installer factory as well or at least that's the way I'm use to organize my solution.
In answer to my own question, I would simply say: There are no shortcakes! Without stopping go or further ado, go here and get this book. Resign yourself to take the time necessary to read it carefully.
So I'm not the only one; here's a quote from Jeff Beck, who wrote that book's foreword: "Often those who start using DI quickly find themselves lost in a sea of confusion."
Don't want to repeat everything again, so just check out my answer on How do I get Web API / Castle Windsor to recognize a Controller?.
As another note - I would advise against doing anything in your repository constructors if you can help it. The reason I say this is that the constructors get called as Windsor is trying to instantiate the correct instance of your repository. What this means is that if any kind of error occurs, it happens as WebApi is trying to create the controller. This can make it a bit tricky to track down the problem sometimes, and also ends up hiding the real issues under tons of layers of exceptions.

Categories

Resources