3

I am learning dependency injection and using autofac for the first time. I built the container as mentioned in several autofac examples (see below) and called from my application_start

public class ContainerConfig
{
    public static void RegisterContainer() 
    {
        //Create a new ContainerBuilder
        var builder = new ContainerBuilder();

        // Register all the controllers using the assembly object
        builder.RegisterControllers(Assembly.GetExecutingAssembly());

        //Registering default convention -- IExample and Example
        builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
            .Where(t => t.Name.Single(i => i.Name == "I" + t.Name))
            .AsImplementedInterfaces();

        //Build the container
        var container = builder.Build();

        //Set the default resolver to use Autofac
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 
    }
}

I created UserService in my Core project with IUserService. This has methods to make dbcall to get user information from tables. In my UI project, i have a class called UserProvider to which i am tying to inject UserService.

public class UserProvider
{
    private readonly IUserService _userService;

    public UserProvider(IUserService userService) 
    {
        _userService = userService;
    }

    public void LoadCurrentUser()
    {
        Users FoundUser = _userService.ImportOrGetUser();

        if (FoundUser != null)
        {
            //add it to session
            CurrentUser = FoundUser;
        }
    }
}

This UserProvider, i am using in my session_start

void Session_OnStart()
{
     UserProvider OUsrPrv = new UserProvider(new UserService());
     OUsrPrv.LoadCurrentUser();
}

In the above code, if i am passing 'new UserService()', my understanding is i am injecting UserService manually. I dont see how autofac is helping here. All the examples in google are talking about Dependency injection in MVCController or WebApiController, not in a individual class (UserProvider) like i am doing.

Can somebody please throw some light? Am I doing it all wrong?

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Jap Evans
  • 1,097
  • 8
  • 22
  • 42
  • What exactly does `LoadCurrentUser` do? How does your application use the data that is loaded by it? Note that you usually don't have a logged in user at the point where a session starts, so this code is not going to work anyway. – NightOwl888 Sep 29 '15 at 20:47
  • I am using windows authentication in my application. There is no login page or anything. `LoadCurrentUser` takes `HttpContext.Current.User.Identity.Name` and calls DB to get his details like phonenumber, country etc. – Jap Evans Sep 29 '15 at 20:54
  • Where does it put those details? Session state? – NightOwl888 Sep 29 '15 at 20:55
  • yes. `HttpContext.Current.Session`. – Jap Evans Sep 29 '15 at 20:56

2 Answers2

3

In order to properly use Dependency Injection, you should never create instance by yourself, the underlying framework should provide instances for you.

But ASP.net invokes the Session_OnStart without any Dependency Injection. In this case you can use the DependencyResolver.Current static property to resolve the requested service.

void Session_OnStart()
{
    UserProvider userProvider = DependencyResolver.Current.GetService<UserProvider>();
    userProvider.LoadCurrentUser(); 
}
Cyril Durand
  • 15,834
  • 5
  • 54
  • 62
  • Thanks for your reply Cyril. "UserProvider userProvider = DependencyResolver.Current.GetService();" -- gives userProvider as null... – Jap Evans Sep 29 '15 at 20:22
  • 1
    @Jeevan It seems that UserProvider is not registered. You have to register each type you want to resolve in the container. You can register `UserProvider` as is (ie: `builder.RegisterType().AsSelf()`) or create a `IUserProvider` interface and let `UserProvider` implements it. – Cyril Durand Sep 29 '15 at 21:30
  • Thanks a ton Cyril. That works. I will use this for couple of days. But I am going to try @NightOwl888 's answer in the end. – Jap Evans Sep 30 '15 at 20:22
2

The event model in the System.Web.HttpApplication is part of ASP.NET, not MVC. It was not designed for use with dependency injection.

The answer that Cyril suggested is using a service locator to get a reference to the service. This is far from ideal, since you are taking on a dependency to the service locator in your code.

The MVC-centric way of implementing cross cutting concerns (such as loading user data into session state) is to use globally registered filters. You can either implement IAuthorizationFilter or IActionFilter to get the desired effect. In this case it makes sense to use IActionFilter since you want to wait until you are sure there is an authorized user before it is called.

NOTE: While this answers your specific question, it is best not to use session state for this scenario in MVC. An alternative is to use ASP.NET Identity with Claims to store user profile data instead of using Session.

using System;
using System.Web.Mvc;
using System.Security.Principal;

public class GetUserActionFilter : IActionFilter
{
    private readonly IUserRepository userRepository;

    public GetUserActionFilter(IUserRepository userRepository)
    {
        if (userRepository == null)
            throw new ArgumentNullException("userRepository");

        this.userRepository = userRepository;
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // Do nothing - this occurs after the action method has run
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        IPrincipal user = filterContext.HttpContext.User;

        if (user == null)
        {
            return;
        }

        IIdentity identity = user.Identity;

        if (identity == null)
        {
            return;
        }

        // Make sure we have a valid identity and it is logged in.
        if (identity.IsAuthenticated)
        {
            string key = "__CurrentUserData";
            var userData = filterContext.HttpContext.Session[key];
            if (userData == null)
            {
                // User data doesn't exist in session, so load it
                userData = userRepository.GetUserData(identity.Name);

                // Add it to session state
                filterContext.HttpContext.Session[key] = userData;
            }
        }
    }
}

Now, to add your filter globally, you need to:

  1. Register the filter and its dependencies with Autofac.
  2. Pass the container to the static RegisterGlobalFilters method.

Register the Filter

Using a named instance to differentiate it from other potential IActionFilter instances.

builder.RegisterType<GetUserActionFilter>()
       .Named<IActionFilter>("getUserActionFilter");

Pass the Container

FilterConfig.cs

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters, IContainer container)
    {
        filters.Add(container.ResolveNamed<IActionFilter>("getUserActionFilter"));
        filters.Add(new HandleErrorAttribute());
    }
}

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    // This method serves as the composition root
    // for the project.
    protected void Application_Start()
    {
        // Register Autofac DI
        IContainer container = ContainerConfig.RegisterContainer();

        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters, container);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();
    }
}

ContainerConfig.cs

public class ContainerConfig
{
    public static IContainer RegisterContainer() 
    {
        //Create a new ContainerBuilder
        var builder = new ContainerBuilder();

        // Register all the controllers using the assembly object
        builder.RegisterControllers(Assembly.GetExecutingAssembly());

        //Registering default convention -- IExample and Example
        builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
              .Where(t => t.Name.Single(i => i.Name == "I" + t.Name))
              .AsImplementedInterfaces();

        // Register our filter
        builder.RegisterType<GetUserActionFilter>()
           .Named<IActionFilter>("getUserActionFilter");

        //Build the container
        var container = builder.Build();

        //Set the default resolver to use Autofac
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 

        // Return the container to our composition root.
        return container;
    }
}

Note that I just used a repository service here, since HttpContext is available already through the action filter directly and additional logic is needed here because we don't know for sure if it exists in session state or not or whether there is even a user to lookup, so our filter does those checks in addition to loading session state.

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • 1
    @Jeevan - Note that you can completely eliminate session state by using [ASP.NET Identity with Claims](http://stackoverflow.com/questions/31974228/can-you-extend-httpcontext-current-user-identity-properties/31976327#31976327) to store the user profile info. – NightOwl888 Oct 01 '15 at 07:10
  • I am getting error (didnt get before, getting after IISRESET) I filterconfig.cs, when i add getUserActionFilter. Exception as below: `An exception of type 'Autofac.Core.DependencyResolutionException' occurred in Autofac.dll but was not handled in user code Additional information: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'GetUserActionFilter' can be invoked with the available services and parameters: Cannot resolve parameter 'IUserRepository userRepository' of constructor 'Void .ctor(IUserRepository )'.` – Jap Evans Oct 26 '15 at 17:04
  • When I remove the constructor in the GetUserActionFilter that injects IUserRepository), the exception is gone. can you please help? – Jap Evans Oct 26 '15 at 17:06
  • It sounds like you haven't registered a type for the `IUserRepository` interface with Autofac. Do you have a service named `UserRepository` that implements `IUserRepository`? If not, you will need to register it manually. – NightOwl888 Oct 26 '15 at 18:42
  • Yes, i do the below `builder.RegisterControllers(Assembly.GetExecutingAssembly()); builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies()).Where(t => t.Name.EndsWith("Repository")) .AsImplementedInterfaces(); builder.RegisterType().Named("getUserActionFilter");` And yes, i do have IUserRepository and UserRepository. The UserRepository.cs is another class project. Dont know if that makes any difference. Could it be any timing issue? – Jap Evans Oct 26 '15 at 19:30
  • NightOwl888. I finally found the problem. IUserRepsitory resides in another (Repo) csproj. So when i say Register assemblyTypes(AppDomain.CurrentDomain.GetAssemblies), sometimes the Repo dll is loaded and sometimes it is not loaded. On searching on stackoverflow, i found few related links [here](http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app) and [here](http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl). Thanks for your help. Appreciate it. – Jap Evans Oct 27 '15 at 21:17
  • Hi, Stuck in another place now. I would appreciate if you can take a look at http://stackoverflow.com/questions/33504355/actionexecutingcontext-httpcontext-session-is-null – Jap Evans Nov 04 '15 at 15:58