Executing my Class Function via Dependency Injection - c#

I have been working on converting my tightly coupled method into one that can be unit tested and have sought some advice on here. I now have my method passing its unit test thanks to some advice - however I now find that I cant call the method from my application. I used to access my GetAllProductsFromCSV() method from the controller with the following:
public ActionResult Index()
{
var products = new ProductsCSV();
List<ProductItem> allProducts = products.GetAllProductsFromCSV();
foreach (var product in allProducts)
{
if (string.IsNullOrEmpty(product.ImagePath))
{
product.ImagePath = "blank.jpg";
}
}
return View(allProducts);
}
The Method was as follows:
public class ProductsCSV
{
public List<ProductItem> GetAllProductsFromCSV()
{
var productFilePath = HttpContext.Current.Server.MapPath(#"~/CSV/products.csv");
String[] csvData = File.ReadAllLines(productFilePath);
List<ProductItem> result = new List<ProductItem>();
foreach (string csvrow in csvData)
{
var fields = csvrow.Split(',');
ProductItem prod = new ProductItem()
{
ID = Convert.ToInt32(fields[0]),
Description = fields[1],
Item = fields[2][0],
Price = Convert.ToDecimal(fields[3]),
ImagePath = fields[4],
Barcode = fields[5]
};
result.Add(prod);
}
return result;
}
}
I have now made the following changes in the ProductCSV class:
public class ProductsCSV
{
private readonly IProductsCsvReader reader;
public ProductsCSV(IProductsCsvReader reader = null)
{
this.reader = reader;
}
public List<ProductItem> GetAllProductsFromCSV()
{
var productFilePath = #"~/CSV/products.csv";
var csvData = reader.ReadAllLines(productFilePath);
var result = parseProducts(csvData);
return result;
}
private List<ProductItem> parseProducts(String[] csvData)
{
List<ProductItem> result = new List<ProductItem>();
foreach (string csvrow in csvData)
{
var fields = csvrow.Split(',');
ProductItem prod = new ProductItem()
{
ID = Convert.ToInt32(fields[0]),
Description = fields[1],
Item = fields[2][0],
Price = Convert.ToDecimal(fields[3]),
ImagePath = fields[4],
Barcode = fields[5]
};
result.Add(prod);
}
return result;
}
Along with the following class & Interface:
public class DefaultProductsCsvReader : IProductsCsvReader
{
public string[] ReadAllLines(string virtualPath)
{
var productFilePath = HostingEnvironment.MapPath(virtualPath);
String[] csvData = File.ReadAllLines(productFilePath);
return csvData;
}
}
public interface IProductsCsvReader
{
string[] ReadAllLines(string virtualPath);
}
As I said, the Unit-Test on method GetAllProductsFromCSV completes successfully now however when I try accessing the method from my controller I get a NullReferenceException on the reader.ReadAllLines call within GetAllProductsFromCSV. It makes sense to me that when I attempt to create an instance of ProductsCSV from within the controller - I am not passing in any parameters... The constructor for the class however requests a IProductsCsvReader. What I cant figure out is how do I actually make the call to the method now? I hope that is clear??

First lets update ProductsCSV to have a backing interface
public interface IProductsCSV {
List<ProductItem> GetAllProductsFromCSV();
}
public class ProductsCSV : IProductsCSV {
//...other code removed for brevity
}
The controller wound now depend on the abstraction introduced above, decoupling it from the concrete implementation from the original controller. Though a simplified example, this allows the controller to be easier to maintain and unit-test in isolation.
public class ProductsController : Controller {
private readonly IProductsCSV products;
public ProductsController(IProductsCSV products) {
this.products = products;
}
public ActionResult Index() {
List<ProductItem> allProducts = products.GetAllProductsFromCSV();
foreach (var product in allProducts) {
if (string.IsNullOrEmpty(product.ImagePath)) {
product.ImagePath = "blank.jpg";
}
}
return View(allProducts);
}
}
Note how the action matched exactly what you had before except for how the products is created.
Finally now that the controller has been refactored for dependency inversion, the framework needs to be configured to be able to inject the dependencies into the the controller when requested.
You can eventually use your library of choice but for this example I am using what they used in documentation
ASP.NET MVC 4 Dependency Injection
Never mind the version. The implementation is transferable.
In the documentation above they used Unity for their IoC container. There are many container libraries available so search for the one you prefer and use that.
public static class BootStrapper {
private static IUnityContainer BuildUnityContainer() {
var container = new UnityContainer();
//Register types with Unity
container.RegisterType<IProductsCSV , ProductsCSV>();
container.RegisterType<IProductsCsvReader, DefaultProductsCsvReader>();
return container;
}
public static void Initialise() {
//create container
var container = BuildUnityContainer();
//grab the current resolver
IDependencyResolver resolver = DependencyResolver.Current;
//create the new resolver that will be used to replace the current one
IDependencyResolver newResolver = new UnityDependencyResolver(container, resolver);
//assign the new resolver.
DependencyResolver.SetResolver(newResolver);
}
}
public class UnityDependencyResolver : IDependencyResolver {
private IUnityContainer container;
private IDependencyResolver resolver;
public UnityDependencyResolver(IUnityContainer container, IDependencyResolver resolver) {
this.container = container;
this.resolver = resolver;
}
public object GetService(Type serviceType) {
try {
return this.container.Resolve(serviceType);
} catch {
return this.resolver.GetService(serviceType);
}
}
public IEnumerable<object> GetServices(Type serviceType) {
try {
return this.container.ResolveAll(serviceType);
} catch {
return this.resolver.GetServices(serviceType);
}
}
}
You would call the above bootstrapper in your start up code.
For example
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Bootstrapper.Initialise(); //<-- configure DI
AppConfig.Configure();
}
So now when ever the framework has to create the ProductsController it will know how to initialize and inject the controller's dependencies.

Related

MVC MEF Error: Make sure that the controller has a parameterless public constructor

For MVC app using MEF, sometimes I get the erorr ""
I have a .NET solution with
One MVC Web application project and,
Many class libraries projects which take care of authentication, getting configurations, making external API calls etc.
I have configured the MEF as shown below through the code and deployed this on Web Server using IIS. Observed the below error couple of times, after which I tried to load the page multiple times but it still throws the same error.
Once I refreshed the application pool, then only the error disappears. I had been trying hard to debug and understand the error, but with no success. Am I misconfiguring MEF anywhere?
Global.asax:
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
var pluginFolders = LoadMefComponents();
Bootstrapper.Compose(pluginFolders);
IControllerFactory mefControllerFactory = new MefControllerFactory(Bootstrapper.Container);
ControllerBuilder.Current.SetControllerFactory(mefControllerFactory);
}
protected List<string> LoadMefComponents()
{
var pluginFolders = new List<string>();
string ModulesPath = CommonUtility.GetApplicationDirectory();
var plugins = Directory.GetDirectories(ModulesPath).ToList();
plugins.ForEach(path =>
{
var directoryInfo = new DirectoryInfo(path);
pluginFolders.Add(directoryInfo.Name);
});
return pluginFolders;
}
}
MEFControllerFactory.cs: This file resides in App_Start
public class MefControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _container;
private readonly Dictionary<IController, Lazy<object, object>> exports;
private readonly object syncRoot;
public MefControllerFactory(CompositionContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this._container = container;
this.exports = new Dictionary<IController, Lazy<object, object>>();
this.syncRoot = new object();
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
Lazy<object, object> export = _container.GetExports(controllerType, null, null).FirstOrDefault();
var controller = null == export ? base.GetControllerInstance(requestContext, controllerType)
: (IController)export.Value;
lock (this.syncRoot)
{
this.exports.Add(controller, export);
}
return controller;
}
public override void ReleaseController(IController controller)
{
lock (this.syncRoot)
{
var export = this.exports[controller];
this.exports.Remove(controller);
// this._container.ReleaseExport(export);
}
((IDisposable)controller).Dispose();
}
}
Bootstrapper.cs: This file resides in App_Start
public class Bootstrapper
{
private static CompositionContainer compositionContainer;
private static bool IsLoaded = false;
public static CompositionContainer Container
{
get { return compositionContainer; }
set { compositionContainer = value; }
}
public static void Compose(List<string> pluginFolders)
{
if (IsLoaded) return;
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryCatalog(CommonUtility.GetApplicationDirectory()));
compositionContainer = new CompositionContainer(catalog);
compositionContainer.ComposeParts();
IsLoaded = true;
}
public static T GetInstance<T>(string contractName = null)
{
var type = default(T);
if (compositionContainer == null) return type;
if (!string.IsNullOrWhiteSpace(contractName))
type = compositionContainer.GetExportedValue<T>(contractName);
else
type = compositionContainer.GetExportedValue<T>();
return type;
}
}
CommonUtility.cs: This file resides in App_Start
public class CommonUtility
{
public static string GetApplicationDirectory()
{
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
HomeController.cs:
[CommonExceptionFilter]
public class HomeController : Controller
{
private IConfigurationManager _configurationManager;
[ImportingConstructor]
public HomeController()
{
_configurationManager = Bootstrapper.GetInstance<IConfigurationManager>();
}
public async Task<ActionResult> Index()
{
//Business Logic
return View()
}
}
IConfigurationManager.cs
[InheritedExport]
public interface IConfigurationManager
{
string GetConfigurationValue(string keyName)
}
ConfigurationManager.cs
[PartCreationPolicy(CreationPolicy.Shared)]
public class ConfigurationManager: IConfigurationManager
{
[ImportingConstructor]
public ConfigurationManager()
{
}
public string GetConfigurationValue(string keyName)
{
return "";
}
}
IHttpHandlers Used In The MVC Project:
public class CommonServiceHandler : HttpTaskAsyncHandler, IRequiresSessionState
{
private ICommonServiceHandlerManager _commonServiceHandlerManager;
public CommonServiceHandler()
{
_commonServiceHandlerManager = Bootstrapper.GetInstance<ICommonServiceHandlerManager>();
}
public override bool IsReusable
{
get
{
return false;
}
}
}
This error indicates that at least one controller exists that its constructor parameters are not resolved.
Every controller needs a constructor to be resolved at run-time. By default every c# class has a default (parameter-less) constructor that can be called when an instance of the class is needed to be created.
However, after defining an explicit constructor, you lose the default constructor, therefore, you need to make sure that all your controllers have either a parameter-less constructor or if instead they have a parametric constructor the parameters need to be registered through dependency injection.

How to create a Dependency Injection using Microsoft Asp.net WebApi

I have a little experience working with Dependency Injection using Xamarin.Forms, but not in WebApi, What I want is send data through my Interface and execute inside of my Class which is implementing that Interface, there's what I have :
public interface IRepository
{
IHttpActionResult SendContext(user user);
IHttpActionResult GetContextData(int id);
}
public class ContextGoneBase : ApiController,IRepository
{
public IHttpActionResult GetContextData(int id)
{
try
{
using (var context = new GoneContext())
{
var result = context.user.Where(a => a.id_user == id).Select(w =>
new { w.user_name, w.cellphone_number, w.user_kind, w.CEP, w.area.area_name, w.district, w.city.city_name, w.city.state.state_name });
var list = result.ToList();
if (list != null)
{
return Ok(list);
}
else
{
return BadRequest();
}
}
}
catch (Exception)
{
return BadRequest();
}
}
And Inside of my controller I was trying to do something like that:
[Route("86538505")]
public IHttpActionResult GetData(int id, IRepository repo)
{
this._repo = repo;
var result = _repo.GetContextData(id);
return result;
}
But, it fails! Thanks!
You should instead pass the IRepository as a parameter to the constructor
Set the field _repo of type IRepository to the value of the parameter passed.
public ContextGoneBase (IRepository repository){ //Constructor
_repo = repository;
}
.And then use an IOC container like Unity to instantiate the controller with the right parameters.For example after you install Unity using nuget you will have a UnityConfig class file and there you can register your repository type.For example if your repository is of type Repository then
public static class UnityConfig
{
public static void RegisterComponents()
{
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
var container = new UnityContainer();
container.RegisterType<IRepository,Repository>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
Now in Global.asax call this method:
protected void Application_Start()
{
UnityConfig.RegisterComponents();
}

Can't get Moq Setup to not return null

I'm trying to Setup a Mock call to a the IModelFactory interface that I have.
here is the IModelFactory interface
public interface IModelFactory
{
MaterialAcceptedModel Create(MaterialAccepted model);
MaterialAccepted Parse(MaterialAcceptedModel model);
}
This is the ModelFactory class which implements the IModelFactor interface (I've only add here the method I'm trying to test, no need to add the implementation of the Create Method)
public class ModelFactory : IModelFactory
{
private UrlHelper _urlHelper;
private IRTWRepository _repo;
//private IKeysGeneratorService _keysGen;
private IGeocodeService _geocoder;
public ModelFactory(HttpRequestMessage request, IRTWRepository repo,IGeocodeService geocoder)
{
_urlHelper = new UrlHelper(request);
_repo = repo;
_geocoder = geocoder;
}
#region Parses
public MaterialAccepted Parse(MaterialAcceptedModel model)
{
try
{
if (!string.IsNullOrWhiteSpace(model.category))
{
var category = _repo.CategoryRepository.Get(model.category);
if (category == null) return null;
var entry = new MaterialAccepted()
{
business = model.business,
businessService = model.businessService,
residential = model.residential,
residentialService = model.residentialService,
note = model.note,
Category = category
};
return entry;
}
return null;
}
catch
{
return null;
}
}
#endregion
}
I'm using a BaseAPiController that contains the repo and configuration Interfaces
public class BaseApiController : ApiController
{
IRTWRepository _repository;
IModelFactory _modelFactory;
IConfiguration _configuration;
IGeocodeService _geocoder;
public BaseApiController(IRTWRepository repository,IConfiguration configuration)
{
_repository = repository;
_configuration = configuration;
}
protected IRTWRepository TheRepository
{
get
{
return _repository;
}
}
protected IConfiguration TheConfiguration
{
get
{
return _configuration;
}
}
protected IModelFactory TheModelFactory
{
get
{
_geocoder = new GeocodeService(_configuration.GetValue("geocodeGoogleApiKey"));
if (_modelFactory == null)
{
_modelFactory = new ModelFactory(this.Request, _repository,_geocoder);
}
return _modelFactory;
}
}
Here is the action method in the controller I'm trying to test
[HttpPost]
[Route("api/recyclecenters/{rcid}/materials/")]
public IHttpActionResult Post(int rcid, [FromBody]MaterialAcceptedModel model)
{
try
{
if (model != null)
{
var recycleCenter = TheRepository.RecycleCenterRepository.Get(rcid);
if (recycleCenter == null)
return NotFound();
if (!ModelState.IsValid)
return BadRequest(ModelState);
var entity = TheModelFactory.Parse(model);
if (entity == null) return BadRequest("Could not read material accepted in body");
if (TheRepository.MaterialAcceptedRepository.Get(recycleCenter.RecycleCenterId, entity.Category.name) != null)
return Conflict();
recycleCenter.Materials.Add(entity);
if (TheRepository.SaveAll())
{
string locationHeader = Url.Link("Materials", new { rcid = rcid, name = model.category.ToLower() });
return Created<MaterialAcceptedModel>(locationHeader, TheModelFactory.Create(entity));
}
return BadRequest("Could not save to the database");
}
return BadRequest();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
This is the line that returns null even though I mocked it up on my test method
var entity = TheModelFactory.Parse(model);
and this one is my TestClass
namespace API.Tests.Web
{
[TestClass]
public class MaterialsControllerTest
{
private Mock<IRTWRepository> repository;
private Mock<IModelFactory> factory;
private Mock<IConfiguration> configuration;
private Mock<IRTWAPIIdentityService> identityService;
private MaterialsController controller;
RecycleCenter recycleCenter;
private MaterialAccepted CreateMaterial()
{
return new MaterialAccepted()
{
business = true,
businessService = EnumRecycleCenterService.Dropoff,
residential = false,
residentialService = EnumRecycleCenterService.Pickup,
note = "this a note",
Category = new Category()
{
name = "Books"
}
};
}
[TestInitialize]
public void Initialize()
{
repository = new Mock<IRTWRepository>();
factory = new Mock<IModelFactory>();
configuration = new Mock<IConfiguration>();
identityService = new Mock<IRTWAPIIdentityService>();
controller = new MaterialsController(repository.Object,configuration.Object);
controller.Request = new HttpRequestMessage();
recycleCenter = new RecycleCenter(){RecycleCenterId = 1};
}
[TestMethod]
public void Post_ShouldReturnConflictIfTheRecycleCenterAlreadyTakesMaterial()
{
//arrange
repository.Setup(r => r.RecycleCenterRepository.Get(It.IsAny<int>())).Returns(() => recycleCenter);
factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
configuration.Setup(c => c.GetValue(It.IsAny<string>())).Returns(() => "APIKEY");
repository.Setup(r => r.MaterialAcceptedRepository.Get(It.IsAny<int>(), It.IsAny<string>())).Returns(() => null);
//act
var actionResult = controller.Post(It.IsAny<int>(),new MaterialAcceptedModel());
//assert
Assert.IsInstanceOfType(actionResult, typeof(ConflictResult));
}
}
}
This is the line that's not working because it always return null instead of a new instance of MaterialAccepted
factory.Setup(f => f.Parse(new MaterialAcceptedModel())).Returns(() => new MaterialAccepted());
I tried f.Parse(It.IsAny()) but still doesn't work.
To clarify
the above line of code is returning null because is not mocking the f.Parse() call, instead is executing it and returning null because the if condition I have on that method
Anyone could explain why the Setup is not working?
Setting up your Mock using It.IsAny will work:
factory.Setup(f => f.Parse(It.IsAny<MaterialAcceptedModel>()))
.Returns(() => new MaterialAccepted());
However, as has been said by #Macilquham, I can't see where your are passing the Mock to your controller in the supplied code so that it is used by the production code.
If you don't call the method on your Mock object, which you don't, you're currently calling the method on the instance of the real object created by your base class, then it doesn't matter how you set up your mock it's not going to work. If you are able to change your base class, then doing something like this would allow you to work around your problem:
// Add defaulted parameter to base class to allow modelFactory creation
// to be overridden/injected (this will prevent the current default behaviour
// when fetching the factory, if a non-null is passed in)
public BaseApiController(IRTWRepository repository,IConfiguration configuration,
IModelFactory modelFactory = null)
{
_modelFactory = modelFactory;
}
Modify your sut constructor to allow you to supply a modelFactory (again, default it to null), then amend your test as appropriate:
controller = new MaterialsController(repository.Object,configuration.Object,
factory.Object);
You don't seem to be injecting in the IModelFactory into the controller. You need to make sure that your production code is using the Mock you are setting up in the test.
Mock cannot return null directly.
The trick is just to create a null object.
Assuming the object returned is of type class Material:
Material nullMaterial = null;
...
repository.Setup(r => r.MaterialAcceptedRepository
.Get(It.IsAny<int>(), It.IsAny<string>()))
.Returns(nullMaterial);
This should solve your problem

How do I use Autofac instead singleton in c#?

I have used singleton pattern a using static property, constructor
public class MyClass
{
private readonly MemoryCacheManager _cacheManager;
private static readonly Lazy<MyClass> _Lazy = new Lazy<MyClass>(() => new MyClass());
public static MyClass Language { get { return _Lazy.Value; } }
private MyClass()
{
_cacheManager = new MemoryCacheManager();
LoadCacheData();
}
// Rest of class
}
I have tried like following using Autofac in global.asax:
protected void Application_Start()
{
var builder = new ContainerBuilder();
builder.RegisterType<MemoryCacheManager>().SingleInstance();
}
And inside MyClass constructor:
private MyClass(MemoryCacheManager cache)
{
_cacheManager = cache;
LoadCacheData();
}
public string Translate(string language)
{
var translation = _cacheManager.GetValueFromCache();
}
And I want to call this method in Index.cshtml
Previously I did it directly like this:
<h4>#MyClass.Language.Translate("Welcome", Model.Language)</h4>
As I had Language as follow in MyClass:
public static Localizer Language { get { return _Lazy.Value; } }
But now I do not have this property.
How can I call Translate method in Index.cshtml as I do not have static property like previous.
You just need to register MyClass as a SingleInstance with your container:
var builder = new ContainerBuilder();
builder.RegisterType<MyClass>().As<IMyClass>().SingleInstance();
Then inject where ever you need:
public AnotherClass(IMyClass myClass)
{
_myClass = myClass;
}
Although it's probably the cache you want a single instance of. In that case:
builder.RegisterType<MemoryCacheManager>().SingleInstance();
And then:
public MyClass(MemoryCacheManager cache)
{
_cacheManager = cache;
LoadCacheData();
}
EDIT:
The first thing you need to do is set the DependencyResolver in your Application_Start class (you'll need to get the Autofac MVC Integration NuGet package for this):
protected void Application_Start()
{
this.RegisterAutoFac();
// Rest of method
}
private void RegisterAutoFac()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterType<MyClass>().As<IMyClass>();
builder.RegisterType<MyCache>().As<IMyCache>().SingleInstance();
var container = builder.Build();
// Setup the dependency resolver
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
Then you need to inject MyClass into the constructor of your controller:
public class TestController : Controller
{
private readonly IMyClass _myClass;
public TestController(IMyClass myClass)
{
_myClass = myClass;
}
// Rest of the controller
}
Then when your creating model for you get the data you need from MyClass:
public ActionResult Index()
{
var model = new ExampleModel
{
WelcomeMessage = _myClass.Translate("Welcome")
};
return View(model);
}
And in your view:
<h4>#Model.WelcomeMessage</h4>

Issues with IMutableDependencyResolver and Structuremap in ReactiveUI

First off, let me say that I don't think that is is an issue with ReactiveUI per se, which is why I've not created an issue on its github repo, and second, I realise that I'm using a beta version of ReactiveUI.
I want to use Structuremap because I'm going to have a plugin scenario in my WPF app, and the DI container in Splat isn't cut out for that sort of thing.
Observe these unit tests:
[Fact]
public void ShouldBeAbleToOverrideDefaultDependencyResolver()
{
Locator.Current = new ApplicationDependencyResolver(StructureMapBootstrapper.Instance.Container);
Locator.CurrentMutable.InitializeSplat();
Locator.CurrentMutable.InitializeReactiveUI();
var view = Locator.Current.GetService<SplashScreenView>();
view.Should().NotBeNull().And.BeOfType<SplashScreenView>();
}
[Fact]
public void ShouldBeAbleToLocateTheViewForAViewModel()
{
Locator.Current = new ApplicationDependencyResolver(StructureMapBootstrapper.Instance.Container);
Locator.CurrentMutable.InitializeSplat();
Locator.CurrentMutable.InitializeReactiveUI();
var viewLocator = Locator.Current.GetService<IViewLocator>();
var view = viewLocator.ResolveView(typeof (SplashScreenViewModel));
view.Should().NotBeNull().And.BeOfType<SplashScreenView>();
}
The first test passes. The second test does not, and provides this stacktrace:
StructureMap.StructureMapConfigurationExceptionNo default Instance is registered and cannot be automatically determined for type 'IViewFor<RuntimeType>'
There is no configuration specified for IViewFor<RuntimeType>
1.) Container.GetInstance(IViewFor<RuntimeType>)
at StructureMap.SessionCache.GetDefault(Type pluginType, IPipelineGraph pipelineGraph) in c:\BuildAgent\work\996e173a8ceccdca\src\StructureMap\SessionCache.cs: line 63
at StructureMap.Container.GetInstance(Type pluginType) in c:\BuildAgent\work\996e173a8ceccdca\src\StructureMap\Container.cs: line 325
at Redacted.ApplicationDependencyResolver.GetService(Type serviceType, String contract) in ApplicationDependencyResolver.cs: line 27
at ReactiveUI.DefaultViewLocator.attemptToResolveView(Type type, String contract)
at ReactiveUI.DefaultViewLocator.ResolveView(T viewModel, String contract)
at Redacted.BootstrapAndDependencyResolutionTests.ShouldBeAbleToLocateTheViewForAViewModel() in BootstrapAndDependencyResolutionTests.cs: line 39
I obviously do not, and can not, have any views which implement IViewFor<RuntimeType>. Anyone got any ideas as to why this is happening, and what I can do to get around this? I can't exclude it using the normal Structuremap configuration.
For full clarity here are the implementations of the resolver and the structuremap bootstrapper:
public class ApplicationDependencyResolver : IMutableDependencyResolver
{
private readonly IContainer _container;
public ApplicationDependencyResolver(IContainer container)
{
_container = container;
}
public void Dispose()
{
_container.Dispose();
}
public object GetService(Type serviceType, string contract = null)
{
return string.IsNullOrEmpty(contract)
? _container.GetInstance(serviceType)
: _container.GetInstance(serviceType, contract);
}
public IEnumerable<object> GetServices(Type serviceType, string contract = null)
{
return _container.GetAllInstances(serviceType).Cast<object>();
}
public void Register(Func<object> factory, Type serviceType, string contract = null)
{
var o = factory();
_container.Configure(configure => configure.For(serviceType).Use(o));
}
}
public sealed class StructureMapBootstrapper
{
private static readonly StructureMapBootstrapper InternalInstance = new StructureMapBootstrapper();
static StructureMapBootstrapper() { }
private StructureMapBootstrapper()
{
Configure();
}
public static StructureMapBootstrapper Instance { get { return InternalInstance; } }
public IContainer Container { get; private set; }
private void Configure()
{
Container = new Container(configure =>
{
configure.Scan(with =>
{
with.TheCallingAssembly();
with.LookForRegistries();
with.WithDefaultConventions();
});
});
}
}
After some quality time with the ReactiveUI unit tests, it turns out that the unit test which was failing was actually not implemented correctly, and should look like this:
[Fact]
public void ShouldBeAbleToLocateTheViewForAViewModel()
{
var container = StructureMapBootstrapper.Instance.Container;
var ihas = container.WhatDoIHave();
Locator.Current = new ApplicationDependencyResolver(container);
Locator.CurrentMutable.InitializeSplat();
Locator.CurrentMutable.InitializeReactiveUI();
var vm = new SplashScreenViewModel();
var viewLocator = Locator.Current.GetService<IViewLocator>();
var view = viewLocator.ResolveView(vm);
view.Should().NotBeNull().And.BeOfType<SplashScreenView>();
}
Specifically, it was the fact I was passing typeof(SplashScreenViewMode), and not an instance, that was causing the test to fail.
Edit: I also had to add with.AddAllTypesOf(typeof (IViewFor<>)); to the Structuremap configuration.

Categories