-1

Current project:

When I switch the DI from AutoFac to Unity, I am unable to get the Services built into the boilerplate (robots.txt, sitemap.xml) back up and running. In particular, I am unable to translate the Autofac entries for these services to the appropriate Unity entries.

My HomeController default constructor is unchanged from the default, at least for robots.txt, which I am doing the litmus test on:

private readonly IRobotsService _robotsService;
public HomeController(IRobotsService robotsService) {
    _robotsService = robotsService;
}

The robots.txt method in my HomeController is similarly default for the boilerplate:

[NoTrailingSlash]
[OutputCache(CacheProfile = CacheProfileName.RobotsText)]
[Route("robots.txt", Name = HomeControllerRoute.GetRobotsText)]
public ContentResult RobotsText() {
    Trace.WriteLine($"robots.txt requested. User Agent:<{Request.Headers.Get("User-Agent")}>.");
    var content = _robotsService.GetRobotsText();
    return Content(content, ContentType.Text, Encoding.UTF8);
}

The IRobotsService and RobotsService files are also default for the boilerplate - they are completely unmodified (aside from removing comments for brevity):

namespace Project.Website.Services {
    public interface IRobotsService {
        string GetRobotsText();
    }
}

namespace Project.Website.Services {
    using Boilerplate.Web.Mvc;
    using Constants;
    using System.Text;
    using System.Web.Mvc;

    public sealed class RobotsService : IRobotsService {
        private readonly UrlHelper _urlHelper;
        public RobotsService(UrlHelper urlHelper) => _urlHelper = urlHelper;

        public string GetRobotsText() {
            var stringBuilder = new StringBuilder();
            stringBuilder.AppendLine("user-agent: *");
            stringBuilder.AppendLine("disallow: /error/");
            stringBuilder.Append("sitemap: ");
            // Commented out so it wouldn't trigger the sitemap, which is not active:
            //stringBuilder.AppendLine(_urlHelper.AbsoluteRouteUrl(HomeControllerRoute.GetSitemapXml).TrimEnd('/'));
            return stringBuilder.ToString();
        }
    }
}

The original Startup.Container.cs for Autofac is quite extensive, but the robots.txt service is injected by:

builder.RegisterType<RobotsService>().As<IRobotsService>().InstancePerRequest();

When my UnityConfig.cs file has the following:

container.RegisterType<RobotsService>(new TransientLifetimeManager());

I get

The current type, JCI_Vernon.Website.Services.IRobotsService, is an interface and cannot be constructed. Are you missing a type mapping?

Which pretty well tells me I have to include IRobotsService, but when my UnityConfig file has the following:

container.RegisterType<IRobotsService, RobotsService>(new TransientLifetimeManager());

I get

The current type, System.Web.HttpContextBase, is an abstract class and cannot be constructed. Are you missing a type mapping?

I am unsure as to where I am going wrong, as all other Unity DI in my project is configured by using one of these two variants.

Any assistance would be greatly appreciated.


Edit: Including the Unity files from my primary project (the visible website).

UnityMvcActivator.cs:

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(JCI_Vernon.Website.UnityMvcActivator), nameof(JCI_Vernon.Website.UnityMvcActivator.Start))]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(JCI_Vernon.Website.UnityMvcActivator), nameof(JCI_Vernon.Website.UnityMvcActivator.Shutdown))]

namespace JCI_Vernon.Website {
  using System.Linq;
  using System.Web.Mvc;
  using Unity.AspNet.Mvc;

  /// <summary>
  /// Provides the bootstrapping for integrating Unity with ASP.NET MVC.
  /// </summary>
  public static class UnityMvcActivator {
    /// <summary>
    /// Integrates Unity when the application starts.
    /// </summary>
    public static void Start() {
      FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
      FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(UnityConfig.Container));

      DependencyResolver.SetResolver(new UnityDependencyResolver(UnityConfig.Container));

      // TODO: Uncomment if you want to use PerRequestLifetimeManager
      // Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
    }

    /// <summary>
    /// Disposes the Unity container when the application is shut down.
    /// </summary>
    public static void Shutdown() {
      UnityConfig.Container.Dispose();
    }
  }
}

UnityConfig.cs:

namespace JCI_Vernon.Website {
  using Data;
  using Domain;
  using Identity;
  using Microsoft.AspNet.Identity;
  using Services;
  using Store;
  using System;
  using System.Web;
  using System.Web.Mvc;
  using Unity;
  using Unity.Injection;
  using Unity.Lifetime;
  using Unity.Mvc5;

  public static class UnityConfig {
    public static IUnityContainer Container { get; internal set; }

    public static void RegisterComponents() {
      var container = new UnityContainer();

      container.RegisterType<IUnitOfWork, UnitOfWork>(new HierarchicalLifetimeManager(), new InjectionConstructor("DefaultConnection"));
      container.RegisterType<IUserStore<IdentityUser, Guid>, UserStore>(new TransientLifetimeManager());
      container.RegisterType<RoleStore>(new TransientLifetimeManager());

      container.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current), new TransientLifetimeManager());

      container.RegisterType<IRobotsService, RobotsService>(new Unity.AspNet.Mvc.PerRequestLifetimeManager());
      //container.RegisterType<ISitemapService, SitemapService>(new InjectionConstructor());
      //container.RegisterType<ISitemapPingerService, SitemapPingerService>(new InjectionConstructor());

      DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }
  }
}

In my UnityMvcActivator.cs, I have had that one PerRequestLifetimeManager line both commented and uncommented with every change, no difference observed. Any attempt to use PerRequestLifetimeManager within UnityConfig.cs without Unity.Mvc (as using Unity.AspNet.Mvc;) failed.

Changing UnityConfig.cs to include Unity.AspNet.Mvc caused mass borkage: while I was able to get PerRequestLifetimeManager to be accepted without obvious Intellisense error, UnityMvcActivator.cs suddenly couldn’t resolve its UnityConfig.Container entries without a very odd entry at the top of UnityConfig.cs:

public static IUnityContainer Container { get; internal set; }

And the SetResolver in UnityConfig.cs needed to explicitly state new Unity.Mvc5.UnityDependencyResolver(container) in order to not trigger Intellisense confusion.

Plus, when run, the following error occurred:

Could not load file or assembly 'Unity.Abstractions, Version=3.1.3.0, Culture=neutral, PublicKeyToken=6d32ff45e0ccc69f' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

Why it is trying to target a v3.x of Unity despite the entire solution having been created under v5.x is causing my grey matter no end of meltdown. And yes, I did a full clean and rebuild of the entire solution, plus individual projects.


Edit 2:

May have come across an interesting wrinkle. On a lark, I decided to do a full reinstallation of all NuGet packages, a refresh of sorts. Naturally, when you do an upgrade or reinstall of Unity, it tries to overwrite your unity files, which is why you always need to have your UnityConfig.cs backed up otherwise your registrations will vanish. Happens to me with every. Single. F##cking. Project. So annoying.

So anyhow, I did a full refresh, and my UnityConfig.cs suddenly underwent a major change. Before it was as above, including all upgrades within v5, but the refresh provided me with the following (comments removed for brevity):

namespace JCI_Vernon.Website {
  using System;
  using Unity;

  public static class UnityConfig {
    #region Unity Container
    private static Lazy<IUnityContainer> container =
      new Lazy<IUnityContainer>(() => {
        var container = new UnityContainer();
        RegisterTypes(container);
        return container;
      });

    public static IUnityContainer Container => container.Value;
    #endregion

    public static void RegisterTypes(IUnityContainer container) {
      // TODO: Register your type's mappings here.
      // container.RegisterType<IProductRepository, ProductRepository>();
    }
  }
}

Ya, weird. Major change with no clue why. The old version works just fine, it just blows its cookies all over the specific type mapping this post is about.

Plus, I have to idea what to change the Global.cs entry to in order to load my type mappings, as just using the obvious (changing UnityConfig.RegisterComponents(), which cannot be found, to UnityConfig.RegisterTypes()) does not make any sense -- how do I pass in the container?

René Kåbis
  • 842
  • 2
  • 9
  • 28
  • `when you do an upgrade or reinstall of Unity, it tries to overwrite your unity files` - This is one reason why I never use the "adapter package" with any DI container. It is much simpler to keep things straight by wiring up the implementation yourself. My suggestion is to [manually go through the project](https://stackoverflow.com/a/35991676) and take out any of the "old" Unity stuff and upgrade to the corresponding "new" Unity stuff. Then work through putting in a "new" configuration. – NightOwl888 Jan 30 '18 at 21:51
  • FYI - I found the "new" [PerRequestLifetimeManager here](https://github.com/unitycontainer/aspnet-mvc/tree/master/src). – NightOwl888 Jan 30 '18 at 21:52
  • @NightOwl888 - yes, the `Unity.Mvc` that I mentioned above, and in the comments to the answer below. Still an issue. – René Kåbis Jan 30 '18 at 22:47

1 Answers1

0

There are a couple of issues here. First of all, this line:

container.RegisterType<RobotsService>(new TransientLifetimeManager());

is not the equivalent of:

builder.RegisterType<RobotsService>().As<IRobotsService>().InstancePerRequest();

It should instead be:

container.RegisterType<IRobotsService, RobotsService>(new TransientLifetimeManager());

Keep in mind Autofac type mappings use the concrete type first, and then the interface type. This is backward from most other DI containers.

The last error message indicates you need to register HttpContextBase with Unity. You do that by wrapping HttpContext.Current with HttpContextWrapper.

container.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current), new TransientLifetimeManager());
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Uhhhh… *nope*. “The current type, System.Web.HttpContextBase, is an abstract class and cannot be constructed. Are you missing a type mapping?” And yes, I copypasta’d your line to my UnityConfig.cs. – René Kåbis Jan 26 '18 at 16:16
  • Clearly, you have other problems that you haven't posted in your question. If you add this to a container and it doesn't work, then you must have multiple container instances configured in your project (which you shouldn't normally do). – NightOwl888 Jan 27 '18 at 12:53
  • What do you mean by "multiple container instances"? All I am doing is running a standard repository pattern, with multiple projects within the solution (standard DDD). The boilerplate is contained entirely within one solution. – René Kåbis Jan 29 '18 at 06:11
  • I mean you are newing up more than one `UnityContainer` in your project same as [this example](https://stackoverflow.com/q/48425908). – NightOwl888 Jan 29 '18 at 06:39
  • Nope, not the case. Went through everything with a fine-toothed comb, nothing of that sort anywhere. It’s not a big project. – René Kåbis Jan 30 '18 at 18:20
  • I spotted another issue and updated my answer. Hopefully, that is the culprit. – NightOwl888 Jan 30 '18 at 18:39
  • “The type or namespace name 'PerRequestLifetimeManager' could not be found (are you missing a using directive or an assembly reference?)” - Using Unity 5.5.6. Which, AFAIR, does away with the Microsoft.Practices.Unity namespace within which PerRequestLifetimeManager exists. When I try to use Unity.MVC (where PerRequestLifetimeManager does exist), which means an include Unity.AspNet.MVC, everything else breaks. Massively. Will add more info to my base post very, very shortly. – René Kåbis Jan 30 '18 at 20:53
  • Changed back to `TransientLifetimeManager`, although that isn't the best option. I can't find any documentation that indicates what the "new" lifetimes are. Frankly, Unity has had such poor support recently that I am surprised anyone would make the switch **to** Unity. – NightOwl888 Jan 30 '18 at 21:02
  • More details in my original post, did a nuget refresh, something very odd happened to my UnityConfig.cs, no clue how to proceed. – René Kåbis Jan 30 '18 at 21:39