Autofac property injection in base class - c#

I am working on a Windows Phone 8.1 application and I have a base class with public property.
public class ViewModelBase
{
public ISomeClass MyProp {get;set;}
}
My derived class looks like this
public class MainViewModel : ViewModelBase
{
private readonly INavigation _navigation;
public MainViewModel(INavigation navigation)
{
_navigation = navigation;
}
}
In my App.cs I have
var builder = new ContainerBuilder();
builder.RegisterType<Navigation>().As<INavigation>();
builder.RegisterType<SomeClass>().As<ISomeClass>();
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
When MainViewModel is created my INavigation is resolved but MyProp is null.
I have tried
builder.Register(c => new ViewModelBase { MyProp = c.Resolve<ISomeClass>() });
builder.Register(c => new ViewModelBase()).OnActivated(e => e.Instance.MyProp = e.Context.Resolve<ISomeClass>());
builder.RegisterType<ViewModelBase>().PropertiesAutowired();
but none of it works!
Solution posted here
http://bling.github.io/blog/2009/09/07/member-injection-module-for-autofac/
works but I don't like it :)
I don't want to use constructor injection in this case.
Thank you.

This will load up all classes that inherit ViewModelBase and inject only the specific properties that you want. A lot of the time, you don't want the other properties on the child class to be injected.
builder.RegisterAssemblyTypes( GetType().Assembly )
.AssignableTo<ViewModelBase>()
.OnActivated( args =>
{
var viewModel = args.Instance as ViewModelBase;
if( viewModel != null )
{
viewModel.MyProp = args.Context.Resolve<ISomeClass>();
}
} );

You must make sure that your viewmodel class, MainViewModel, is registered with property injection. Currently, all you have registered with property injection is ViewModelBase, but think about what you are resolving. You will never resolve ViewModelBase, you're resolving MainViewModels. So that is what needs to be registered in the container.
Try:
builder.RegisterType<MainViewModel>().PropertiesAutowired();

Related

Property injection with Autofac and ASP.NET Core controller

My controller classes have a single dependency, e.g.:
public class UserController : ControllerBase {
public UserController(IMediator mediator) => _mediator = mediator;
private readonly IMediator _mediator;
}
But that must be duplicated in all my controllers. I'd prefer to do property injection, so I could clean it up like this:
public abstract class MyControllerBase : ControllerBase {
public IMediator Mediator { get; init; } // use init so container resolves this and cannot be changed later
}
public class UserController : MyControllerBase {
}
To get that working, I followed the docs.
I changed my Startup.ConfigureServices():
services.AddControllers().AddControllersAsServices();
And I registered controllers in Startup.ConfigureContainer(ContainerBuilder builder):
builder
.RegisterAssemblyTypes(typeof(Startup).Assembly)
.AssignableTo<MyControllerBase>()
.InstancePerLifetimeScope()
.PropertiesAutowired();
That works.
However the docs say that I can inject a specific property:
If you have one specific property and value to wire up, you can use the WithProperty() modifier: builder.RegisterType<A>().WithProperty("PropertyName", propertyValue);
So I have to change my code:
var types =
typeof(Startup).Assembly
.GetExportedTypes()
.Where(type => typeof(MyControllerBase).IsAssignableFrom(type))
.ToArray();
foreach (var type in types)
builder.RegisterType(type).WithProperty(nameof(MyControllerBase.Mediator), propertyValue);
How can I dynamically resolve propertyValue from the container. AND, will the container inject any other properties in controller classes, or would it inject that one only?
What you are trying to do wasn't meant for that scenario.
As you have already encountered, it for if the specific value is known at the time of setup not about dynamic resolution.
I suggest leaving it the way it was working before where the container will resolve the dependency and set the property.
//...
public void ConfigureContainer(ContainerBuilder builder) {
var types =
typeof(Startup).Assembly
.GetExportedTypes()
.Where(type => typeof(MyControllerBase).IsAssignableFrom(type))
.ToArray();
builder.RegisterTypes(types).PropertiesAutowired();
}
The approach you want to take is really unnecessary given your scenario.

How is this viewModel being created?

I have a simple application here but I am unsure of how my ViewModel is being created. I am assuming it's from the unity container but I am unsure and curious. The module initializes and registers the view with the region. The view's code behind has the ViewModel initialized in it's constructor and the ViewModel calls some services that were previously registered.
My question is how is the ViewModel created in the View's code behind when I've never registered the ViewModel type with the unity container? Is there some magic happening in the RegisterViewWithRegion method?
AlarmsModule.cs: This simply registers the view with the region
[Module(ModuleName = "AlarmsModule")]
public class AlarmsModule : IModule
{
[Dependency]
public IRegionManager regionManager { get; set; }
public void Initialize()
{
regionManager.RegisterViewWithRegion("AlarmsRegion", typeof(AlarmPanel.View));
}
}
View.xaml.cs:
public partial class View : UserControl
{
public View(ViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
}
ViewModel.cs
public class ViewModel: DependencyObject
{
IEventAggregator _eventAggregator;
public ObservableCollection<IAlarmContainer> AlarmList { get; set; }
IAlarmService _alarmService;
public ViewModel(IAlarmService alarmService)
{
//Adding an alarm from the alarm service, which is injected into this viewModel
AlarmList = alarmService.AlarmList;
}
}
The view model is created by the unity container in the DoGetInstance method of the UnityServiceLocatorAdapter class in the Prism.Unity assembly which is in turn called by the RegisterViewWithRegion method through some other methods of the RegionViewRegistry class.
Unity is able to resolve the view model type automatically provided that it has a default parameterless constructor.
You could verify this yourself using the following code:
var view = unityContainer.Resolve(typeof(View), null); //will automatically resolve the view model type and inject the view with an instance of it

Set the type of registered Service during runtime within an action filter/message handler

public class ActionFilterVersionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Any(x => x.Key == "SetInternalVersion"))
{
// determine somehow that the **InternalSystem implementation** should be resolved when the controller class is instantiated with the **ISystem constructor** parameter
}
else
{
// determine somehow that the **ExternalSystem implementation** should be resolved when the controller class is instantiated with the **ISystem constructor** parameter
}
base.OnActionExecuting(actionContext);
}
}
I have ExternalSystem/InternalSystem with the ISystem interface.
How can I tell autofac to inject the ExternalSystem or InternalSystem into the instantiated controller as ISystem instance depending on the string value I pass in the ActionFilter or maybe message handler.
I know I can do stuff like:
builder.RegisterType<InternalSystem>().As<ISystem>().Keyed<ISystem>("Internal");
where I can use a func<string,ISystem> factory to resolve the class during runtime but this is not what I want to do.
Actually I need to register the ISystem within the the action filter, but then I would need somehow to pass the container into the filter, but that is not what I want...and prolly its also not possible.
// Action: returns external or internal value
public string Get()
{
return resolvedISystem.Get();
}
Of course I could resolve the ISystem depending on the func factory within each single action or put behavior into a base controller where I check for the header, but I really would prefer the action filter as it can be just globally registerd ONE time, but for each new controller I have to subclass the base controller.
Base controller sample with pseudo code , because the base.Request is null which needs another workaround/fix...
public class BaseController : ApiController
{
public BaseController(Func<string, ISystem> dataServiceFactory)
{
string system = base.Request.Headers.Any(x => x.Key == "SetInternalVersion") ? "internal" : "external";
System = dataServiceFactory(system);
}
public ISystem System { get; set; }
}
UPDATING the container is also marked as OBSOLETE by the Autofac author.
Thus I do not want to add registrations in my filter/handler and update/build the container again.
I think you should not use ActionFilter at all. You have a controller dependency which should be resolved properly based on the information coming from request. Here is a possible solution. You can use a static HttpContext.Current property in order to extract request header.
System classes:
public interface ISystem { }
public class ExternalSystem : ISystem { }
public class InternalSystem : ISystem { }
SystemKeyProvider:
public enum SystemKey
{
External,
Internal
}
public interface ISystemKeyProvider
{
SystemKey GetSystemKey();
}
public class SystemKeyProvider : ISystemKeyProvider
{
private const string HeaderKey = "SetInternalVersion";
private readonly HttpRequest _request;
public SystemKeyProvider(HttpRequest request)
{
_request = request;
}
public SystemKey GetSystemKey()
{
return (_request.Headers[HeaderKey] != null) ?
SystemKey.Internal :
SystemKey.External;
}
}
Controller constructor: ValuesController(ISystem system)
Autofac container registration:
var builder = new ContainerBuilder();
builder.Register(c => HttpContext.Current.Request).As<HttpRequest>().InstancePerRequest();
builder.RegisterType<SystemKeyProvider>().AsImplementedInterfaces();
// service registration
builder.RegisterType<ExternalSystem>().Keyed<ISystem>(SystemKey.External);
builder.RegisterType<InternalSystem>().Keyed<ISystem>(SystemKey.Internal);
builder.Register(c =>
c.ResolveKeyed<ISystem>(c.Resolve<ISystemKeyProvider>().GetSystemKey()))
.As<ISystem>();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
GlobalConfiguration.Configuration.DependencyResolver =
new AutofacWebApiDependencyResolver(builder.Build());
In this solution I created a SystemKeyProvider wrapper class which is responsible for providing appropriate key in order to resolve ISystem.
Demo:
When no SetInternalSystem header is present.
Then the dependency is resolved as ExternalSystem.
When SetInternalSystem header is present.
Then the dependency is resolved as InternalSystem.

How to use Singleton class injected by .NET MVC Ninject?

I have some problem with InSingletonScope().
My interface:
public interface ISettingsManager
{
ApplicationSettings Application { get; }
}
and my class:
public class SettingsManager : ISettingsManager
{
private readonly IConfigurationService _configurationService;
private readonly Lazy<ApplicationSettings> _applicationSettings;
public ApplicationSettings Application { get { return _applicationSettings.Value; } }
private SettingsManager(IConfigurationService context)
{
_configurationService = context;
_applicationSettings = new Lazy<ApplicationSettings>(() => new ApplicationSettings(context));
}
}
and binding looks like this:
kernel.Bind<ISettingsManager>().To<SettingsManager>().InSingletonScope();
What do you think about this approach?
For example, in HomeControler, when I'm add:
[Inject]
SettingsManager _settingsManager;
the _settingsManager is always null.
How I can use SettingsManager singleton in another project? I always get null.
What do you think about this approach?
InSingletonScope() - Only a single instance of the type will be created, and the same instance will be returned for each subsequent request. The dependency will have same scope as of Kernel i.e. Disposed when the Kernel is Disposed.
Read about object scopes here.
[Inject]
SettingsManager _settingsManager;
What are you trying to do here? Field injection?
I do not think it is supported. Copied following from official page:
Field Injection is a bad practice, and got cut for minimization as
part of the rewrite between v1 and v2. There is no field injection in
Ninject v2.
You can use Property or Constructor injection instead.
Property Injection:
[Inject]
public ISettingsManager SettingsManager { private get; set; }
Constructor Injection:
public class HomeController : Controller
{
readonly ISettingsManager settingsManager;
public HomeController(ISettingsManager settingsManager )
{
if(settingsManager == null)
throw new ArgumentNullException("settingsManager is null");
this.settingsManager = settingsManager;
}
}
You need to change your _settingsManager to have a type of ISettingsManager instead of SettingsManager.
(You're binding ISettingsManager to SettingsManager. This means when Ninject sees a dependency on ISettingsManager it will inject a SettingsManager. If something is directly declared as SettingsManager it won't do anything)

How can I Import my ViewModels without breaking MVVM?

[Export]
public sealed class MainViewModel : NotificationObject
{
[Import]
public ISomeService MyService { get; private set; }
...
}
In order to INJECT this class as the DataContext to my View, I have to mark it as Export so MEF creates an instance of it in the Catalog. The problem is that the main window needs to create other windows and pass in orders, I'm not sure how to go about that without breaking the MVVM approach.
I figure that an ICommand will trigger something on my MainViewModel to generate a new ViewModel, but then after that happens I can't really force a new Window (view) to open up from the ViewModel. Plus, I can't even really create a new ViewModel from my MainViewModel because then MEF won't really work, right?
[Export]
public sealed class MainViewModel : NotificationObject
{
[Import]
public ISomeService MyService { get; private set; }
private ObservableCollection<IOrderViewModel> Orders { get; set; }
public void OpenOrder(int id)
{
//Pseudo-code to ensure that duplicate orders are not opened)
//Else create/open the new order
var order = new OrderViewModel(id);
OpenOrders.Add(order);
}
}
2 problems here:
Since I "newed" the OrderViewModel services are not autoloaded via MEF.
How does this code on my ViewModel layer (appropriate layer) create the necessary view as a NEW WINDOW (child of the main window), and then link this new OrderViewModel as the DataContext?
The way to avoid 'new-ing' the OrderViewModel is to use a factory:
[Export]
public class OrderViewModelFactory
{
[Import]
public ISomeDependency ImportedDependency { get; set; }
public OrderViewModel Create(int id)
{
return new OrderViewModel(id, this.ImportedDependency);
}
}
Then import the factory into your MainViewModel as a dependency and MEF will take care of filling everything in as required.
To get around the problem of instantiating windows, we have created a DialogService that does something like:
[Export]
public class DialogService
{
public bool? ShowDialog(string regionName, object data = null)
{
var window = new Window();
var presenter = new ContentControl();
presenter.SetProperty(RegionManager.RegionName, regionName);
window.Content = presenter;
if (data != null) window.DataContext = data;
return window.ShowDialog();
}
}
One technique I use is what I call the Navigation service. Note this is different from WPF's built in navigation framework. Your viewmodel could have an instance injected directly or you can use the EventAggregator pattern to fire a request to navigate that then gets handled by the navigation service. Having the navigation service injected directly means that it can be injected with other objects like ViewModelFactories. Regardless how you do it, at some point you're going to have to have an object that knows how to create the viewmodel properly resolved by your container.

Categories