I'm working in an ASPNET MVC 4 project using SignalR and Windsor Castle.
I've mvc controller and WebApi controllers working and instantiating ok with Castle, but my Unit of Work class is not instantiated when it is used inside the Hub of SignalR.
Is there something I'm missing?
This is my code:
IocConfig class
public static class IocConfig
{
public static IWindsorContainer Container { get; private set; }
public static void RegisterIoc(HttpConfiguration config)
{
// Set the dependency resolver for Web API.
var webApicontainer = new WindsorContainer().Install(new WebWindsorInstaller());
GlobalConfiguration.Configuration.DependencyResolver = new WebApiWindsorDependencyResolver(webApicontainer);
// Set the dependency resolver for Mvc Controllers
Container = new WindsorContainer().Install(new ControllersInstaller());
DependencyResolver.SetResolver(new MvcWindsorDependencyResolver(Container));
ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(Container));
var controllerFactory = new WindsorControllerFactory(Container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}
}
}
WebWindsorInstaller class
internal class WebWindsorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component
.For<RepositoryFactories>()
.ImplementedBy<RepositoryFactories>()
.LifestyleSingleton());
container.Register(Component
.For<IRepositoryProvider>()
.ImplementedBy<RepositoryProvider>()
.LifestylePerWebRequest());
container.Register(Component
.For<IGdpUow>()
.ImplementedBy<GdpUow>()
.LifestylePerWebRequest());
container.Register(Classes
.FromAssemblyContaining<Api.MessagesController>()
.BasedOn<IHttpController>()
.If(t => t.Name.EndsWith("Controller"))
.LifestylePerWebRequest());
}
ControllerInstaller class
public class ControllersInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(FindControllers().Configure(c => c.LifestyleTransient()));
container.Register(Component
.For<RepositoryFactories>()
.ImplementedBy<RepositoryFactories>()
.LifestyleSingleton());
container.Register(Component
.For<IRepositoryProvider>()
.ImplementedBy<RepositoryProvider>()
.LifestylePerWebRequest());
container.Register(Component
.For<IGdpUow>()
.ImplementedBy<GdpUow>()
.LifestylePerWebRequest());
}
private static BasedOnDescriptor FindControllers()
{
return AllTypes.FromThisAssembly()
.BasedOn<IController>()
.If(t => t.Name.EndsWith("Controller"));
}
Hub
using Microsoft.AspNet.SignalR.Hubs;
[HubName("iServerHub")]
public class IServerHub : Hub
{
protected IGdpUow Uow;
public void Send(string name, string message)
{
var messagesList = Uow.UdsMessages.GetAll();
Clients.All.broadcastMessage(messagesList);
}
}
In the Send method Uow is null.
Controllers are inherited from this base class
public abstract class CustomControllerBase : Controller
{
protected IGdpUow Uow { get; set; }
}
And when I use Uow in the controller it is instantiated. I don't understand the difference with using it inside the hub.
NOTE
I'm following the instruction found here on how to set up Windsor Castle to make it work with SignalR, but I can't test it since I'm getting the following error (seems that the proxy cannot be created):
Cannot read property client of undefined
meaning that the proxy to the hub was not created
This is how I have it in the server
public class IServerHub : Hub
and like this in the client
var clientServerHub = $.connection.iServerHub;
I can see the dynamically created file here:
/GdpSoftware.Server.Web/signalr/hubs
So, Why the proxy is not created?
This is my installer file
public class HubsInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component
.For<RepositoryFactories>()
.ImplementedBy<RepositoryFactories>()
.LifestyleSingleton());
container.Register(Component
.For<IRepositoryProvider>()
.ImplementedBy<RepositoryProvider>()
.LifestylePerWebRequest());
container.Register(Component
.For<IGdpUow>()
.ImplementedBy<GdpUow>()
.LifestylePerWebRequest());
container.Register(Classes.FromThisAssembly()
.BasedOn<Hub>()
.LifestyleTransient());
}
}
Thanks in advance! Guillermo.
Where do you expect it to be resolved? I don't see that code.
I don't know ASP.NET MVC very good but last time I used it controllers were receiving dependencies through constructor. How your controllers' ctors look? Do they take IGdpUow as parameter? If not how it is injected? I think Windsor can only inject when you have public setter for property - Castle Windsor property injection.
So I guess your controllers have ctors and your IServerHub doesn't (BTW in .NET this name is a bit misleading for a class)
Related
I'm using ASP.NET Core Web API. I am having a hard time wrapping my head around instantiating a non-controller class that uses DI. There are a multitude of SO articles related to this, but none that have answered my question (as far as I can understand). These are the most popular and relevant:
Net Core Dependency Injection for Non-Controller
Dependency Injection without a Controller
ASP.NET 5 Non-Controller DI injection
My use case (a contrived example):
I have a class SpeechWriter that has a dependency on IRandomTextService:
public class SpeechWriter
{
private readonly IRandomTextService _textService;
// Constructor with Text Service DI
public SpeechWriter(IRandomTextService textService)
{
_textService = textService;
}
public string WriteSpeech()
{
var speech = _textService.GetText(new Random().Next(5,50));
return speech;
}
}
IRandomTextService interface:
public interface IRandomTextService
{
public string GetText(int wordCount);
}
and the implementation:
public class RandomTextService : IRandomTextService
{
public string GetText(int wordCount)
{
return Lorem.Words(wordCount);
}
}
IRandomTextService is registered as a service in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<IRandomTextService, RandomTextService>();
}
In my controller action, if I want to instantiate a SpeechWriter like this:
public IActionResult Index()
{
var speech = new SpeechWriter();
return Ok(speech.WriteSpeech());
}
I can't do it because an argument (the injected service) is expected.
The only way I can seem to get DI to inject RandomTextService in SpeechWriter is if SpeechWriter itself is a service and injected in the controller:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<IRandomTextService, RandomTextService>();
services.AddScoped<SpeechWriter>();
}
public class EchoController : ControllerBase
{
private readonly SpeechWriter _speechWriter;
public EchoController(SpeechWriter speechWriter)
{
_speechWriter = speechWriter;
}
public IActionResult Index()
{
return Ok(_speechWriter.WriteSpeech());
}
}
Is there any way to get RandomTextService injected when SpeechWriter is instantiated as in my first example, like this?
var speech = new SpeechWriter();
If not, what is it about DI that I'm missing? My actual application is more complex than this and I would effectively have to create a chain of DI and services all the way back up to the controller. I could use the ServiceProvider "anti-pattern", but I prefer not to do that because I'd be passing ServiceProvider all over the place.
Please help educate me!
Thanks.
I have the following asp.net c# code
{
public class Test
{
ISomeService _someService;
public Test()
{
}
public void DoSomething()
{
_someService.Do();
}
}
I need to provide ISomeService to Test class, and I dont know how to do it. I am not allowed to add additional construction which would make entire problem go away, for example
public Test(ISomeService someService)
{
_someService = someService;
}
I tried using setter injection or method injection but that didnt do the trick.
Implementation of ISomeService in SomeService class also uses constructor injection, such as
public SomeService(IService1 service1, Iservice2 service2)
Not sure what to do here.
HERE IS A COMPLETE CODE
public class Startup
{
private IService _service;
public Startup()
{
}
public Startup(IService service)
{
_service = service;
}
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
var container = new UnityContainer();
container.RegisterType<IService, Service>();
config.DependencyResolver = new UnityDependencyResolver(container);
app.UseWebApi(config);
_service.DoSomething());
}
}
_service is null
I would suggest you use a factory to create your object. That would have an instance of ISomeService injected on the constructor.
Then in a CreateTest() method on your factory set the ISomeService property directly.
public class Factory
{
private readonly ISomeService someService;
public Factory(ISomeService someService)
{
this.someService = someService ?? throw new ArgumentNullException(nameof(someService));
}
public TestClass CreateTestClass()
{
var instance = new TestClass();
instance.SomeService = this.someService;
return instance;
}
}
You should note that most DI providers have built in functionality to allow factory semantics without the need to create your own factories.
What I ended up doing is this
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IService, Service>();
// create service provider
var serviceProvider = serviceCollection.BuildServiceProvider();
_service = ActivatorUtilities.CreateInstance<Service>(serviceProvider);
_service.DoSomething();
Thanks to this answer Dependency Injection with classes other than a Controller class
Is it possible to use dependency injection to inject dependencies into SignalR on ASP.NET Core v2.0?
Assuming the following hub and dependency:
public MyHub : Hub {
private readonly IMyDependency dependency;
public MyHub(IMyDependency dependency) {
this.dependency = dependency;
}
}
public void MyDependency : IDependency
{
public void MyMethod() {
Console.WriteLine("I'm a dependency!");
}
}
I've scoured a bit of the web and there isn't anything obvious out there. I found this tutorial which at first seemed quite promising until I realised it was for Microsoft.AspNetCore.SignalR.Server which didn't ship in the end.
At the moment I have the following setup using Autofac and it's not working:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSignalR();
// Configue Autofac
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<MyModule>();
// Configure SignalR hubs for dependency injection
containerBuilder.RegisterSignalRHubs(typeof(Startup).GetTypeInfo().Assembly);
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
}
public static class AutoFacExtensions
{
public static IRegistrationBuilder<object, ScanningActivatorData, DynamicRegistrationStyle> RegisterSignalRHubs(this ContainerBuilder builder, params Assembly[] assemblies)
{
return builder.RegisterAssemblyTypes(assemblies)
.Where(t => typeof(IHub).IsAssignableFrom(t))
.ExternallyOwned();
}
}
public class MyModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<MyDependency>().As<IMyDependency>();
}
}
It looks like the IHub interface doesn't exist anymore. I tried IHubContext<MyHub> in the hope that this might work with the latest version but sadly not.
When I have dependencies in my hub's constructor, the hub isn't created despite all of the dependencies registered with Autofac.
How can I achieve this with the lastest version 1.0.0.0-alpha2-final?
The example given in the question does work with version 1.0.0.0-alpha2-final of Microsoft.AspNetCore.SignalR with one slight tweak, use Hub rather than the now non-existent IHub.
public static class AutoFacExtensions
{
public static IRegistrationBuilder<object, ScanningActivatorData, DynamicRegistrationStyle> RegisterSignalRHubs(this ContainerBuilder builder, params Assembly[] assemblies)
{
// typeof(Hub), not typeof(IHub)
return builder.RegisterAssemblyTypes(assemblies)
.Where(t => typeof(Hub).IsAssignableFrom(t))
.ExternallyOwned();
}
}
Ensure that all of your dependencies are satisfied by assigning them to a controller. I'm not sure at this point how to troubleshoot broken dependencies when injecting into a SignalR hub with this method.
Until now I have done type registration inside a class within my MVC project but I am now trying to do it with Modules. My project is structured as follows
Project.Data: contains my entityframework classes and DataContext. I have refactored my datacontect to implement an interface (IDbContext) which I register via an autofac module
Project.Business: contains business logic classes into which and IDbContext instance is injected. Classes implement corresponding interfaces which are also registered via an autofac module
Project.Web: asp.net project which uses assembly scanning to register all autofac modules
Sample Code
Project.Data
//IDbContext
public interface IDbContext
{
IDbSet<MyData> MyDatas { get; set; }
....
}
//DataContext
public class DataContext : DbContext, IDbContext
{
public IDbSet<MyData> MyDatas { get; set; }
}
//Module
public class DataInjectionModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<DataContext>().As<IDbContext>();
}
}
Project.Business
public interface IServeDataService
{
IEnumerable<object> GetData();
}
public class ServeDataService : IServeDataService
{
private IDbContext context;
public ServeDataService()
{
this.context = context;
}
public IEnumerable<object> GetData()
{
}
}
public class BusinessInjectionModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ServeDataService>().As<IServeDataService>();
}
}
Project.Web
public partial class Startup
{
public void RegisterContainer()
{
var builder = new ContainerBuilder();
var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();
builder.RegisterAssemblyModules(assemblies.ToArray());
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);
}
}
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
RegisterContainer();
}
}
//controller
public class DataController : ApiController
{
private IServeDataService dataService;
public DataController(IServeDataService dataService)
{
this.dataService = dataService;
}
public async Task<IHttpActionResult> Get()
{
var data = dataService.GetData();
return Ok(data);
}
}
however when I try to call the api service, I get the following error
An error occurred when trying to create a controller of type 'DataController'. Make sure that the controller has a parameterless public constructor.
I have debugged and set break points in the RegisterContainer methid and that gets called, I have also looked at the assemblies listed and my Business and Data assemblies are listed, but my module registrations are never run. What am I doing wrong?
It seems that you didn't configure Web API to use your Autofac container.
To get Autofac integrated with Web API you need to reference the Web API integration NuGet package, register your controllers, and set the dependency resolver. You can optionally enable other features as well.
> Autofac - Web API documentation.
In order to make it work you have to add a reference on the Autofac ASP.NET Web API Integration nuget package and add the following line in your RegisterContainer method :
// Set the dependency resolver to be Autofac.
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
I'm stuck. I want to configure Castle Windsor in a different class library and I configured, build is fine, no error, ok... But I get an exception at run time.
Castle.MicroKernel.ComponentNotFoundException: No component for supporting the service App.Web.UI.Controllers.HomeController was found
When I get the configure file back to the same assembly (App.Web.UI), I don't get any exception at run time, work is fine.
I tried many ways, but I could not. Is there another bind method except FromThisAssembly? Or Solution?
Castle Windsor configuration is here:
namespace App.Infrastructure.Configurations
{
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly IKernel _kernel;
public WindsorControllerFactory(IKernel kernel)
{
_kernel = kernel;
}
public override void ReleaseController(IController controller)
{
_kernel.ReleaseComponent(controller);
}
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 class ControllersInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<IController>().Unless(x => x.Name == "BaseController").LifestyleTransient());
}
}
public class ServiceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For(typeof (IRepository<>)).ImplementedBy(typeof (Repository<>)).LifestyleTransient(),
Component.For<IUserService>().ImplementedBy<UserService>().LifestylePerWebRequest(),
Component.For<IFormsAuthenticationService>().ImplementedBy<FormsAuthenticationService>().LifestylePerWebRequest(),
Component.For<ILoggingService>().ImplementedBy<LoggingService>().LifestyleTransient(),
Component.For<IFeedbackService>().ImplementedBy<FeedbackService>().LifestylePerWebRequest()
);
}
}
}
In your code, the Classes.FromThisAssembly() references the assembly containing your Windsor stuff -- not your web application. Try telling Windsor to register components from a specific assembly. This should work:
Classes.FromAssembly(Assembly.GetEntryAssembly())
The entry assembly should be the AppDomain running your MVC code.