How do I NOT use DependencyResolver.Current.GetService(...) in this situation

15,209

Solution 1

You can't prevent having to call either the DI Container or an abstraction over it in your Application_PostAuthenticateRequest, but that shouldn't be a problem, since this Application_PostAuthenticateRequest can be considered to be part of your Composition Root. Or in other words: you have to resolve it somewhere.

The problem in your case, however, is that this method contains an awful lot of code, and the real problem is that you are missing an abstraction. To resolve this, extract all logic of this method into a new class and hide it behind an abstraction. What will be left is the following code:

protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
   var provider = (IPostAuthenticateRequestProvider)
       DependencyResolver.Current.GetService(typeof(IPostAuthenticateRequestProvider));

   provider.ApplyPrincipleToCurrentRequest();
}

The code can be built up by your DI Container, and will have the following signature:

public class MvcPostAuthenticateRequestProvider : IPostAuthenticateRequestProvider
{
    private readonly IApplicationConfiguration configuration;

    public MvcPostAuthenticateRequestProvider(IApplicationConfiguration configuration)
    {
        this.configuration = configuration;
    }

    public void ApplyPrincipleToCurrentRequest()
    {
        // ...
    }
}

Solution 2

Following Steven's suggestion, the final code was:

A new interface "IPostAuthenticateRequestProvider"

/// <summary>
/// Defines the expected members of a PostAuthenticateRequestProvider
/// </summary>
internal interface IPostAuthenticateRequestProvider
{
    /// <summary>
    /// Applies a correctly setup principle to the Http request
    /// </summary>
    /// <param name="httpContext"></param>
    void ApplyPrincipleToHttpRequest(HttpContext httpContext);
}

A concrete class that implements "IPostAuthenticateRequestProvider"

/// <summary>
/// Provides PostAuthenticateRequest functionality
/// </summary>
public class MvcPostAuthenticateRequestProvider : IPostAuthenticateRequestProvider
{
    #region Declarations

    private readonly IApplicationConfiguration _configuration;

    #endregion

    #region Constructors

    public MvcPostAuthenticateRequestProvider(IApplicationConfiguration configuration)
    {
        _configuration = configuration;
    }

    #endregion

    #region IPostAuthenticateRequestProvider Members

    /// <summary>
    /// Applies a correctly setup principle to the Http request
    /// </summary>
    /// <param name="httpContext"></param>
    public void ApplyPrincipleToHttpRequest(HttpContext httpContext)
    {
        // declare a collection to hold roles for the current user
        String[] roles;

        // Get the current identity
        var identity = HttpContext.Current.User.Identity;

        // Check if the request is authenticated...
        if (httpContext.Request.IsAuthenticated)
        {
            // ...it is so load the roles collection for the user
            roles = Roles.GetRolesForUser(identity.Name);
        }
        else
        { 
            // ...it isn't so load the collection with the unknown role
            roles = new[] { _configuration.UnknownUserRoleName };
        }

        // Create a new WebIdenty from the current identity 
        // and using the roles collection just populated
        var webIdentity = new WebIdentity(identity, roles);

        // Create a principal using the web identity and load it
        // with the app configuration
        var principal = new WebsitePrincipal(webIdentity)
        {
            ApplicationConfiguration = _configuration
        };

        // Set the user for the specified Http context
        httpContext.User = principal;
    }

    #endregion
}

And in global.asax...

public class MvcApplication : NinjectHttpApplication
{
    /// <summary>
    /// Handles the PostAuthenticateRequest event of the Application control.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
    protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
    {
        // Get a PostAuthenticateRequestProvider and use this to apply a 
        // correctly configured principal to the current http request
        var provider = (IPostAuthenticateRequestProvider)
            DependencyResolver.Current.GetService(typeof(IPostAuthenticateRequestProvider));
        provider.ApplyPrincipleToHttpRequest(HttpContext.Current);
    }
.
.
}
Share:
15,209
Dib
Author by

Dib

I work as a .Net application developer for DCH FT in the UK NHS. My preferred language is C#. I also work with HTML, JS, Vue, and React, SQL.

Updated on June 03, 2022

Comments

  • Dib
    Dib almost 2 years

    Following the advice I have been given in this thread [Ninject UOW pattern, new ConnectionString after user is authenticated I now understand that I should not use the following line...

        var applicationConfiguration =
                (IApplicationConfiguration)
                    DependencyResolver.Current.GetService(typeof(IApplicationConfiguration));
    

    ...as a Service Locator is an anti-pattern.

    But in the case of the following procedure how can I instantiate my concrete object that implements "IApplicationConfiguration" so that I can use that object to get the unknown user role name, or use it to assign to the "ApplicationConfiguration" property of my principle?

    Global.asax

    public class MvcApplication : NinjectHttpApplication
    {
        /// <summary>
        /// Handles the PostAuthenticateRequest event of the Application control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
        {
            String[] roles;
            var applicationConfiguration =
                (IApplicationConfiguration)
                    DependencyResolver.Current.GetService(typeof(IApplicationConfiguration));
            var identity = HttpContext.Current.User.Identity;
            if (Request.IsAuthenticated)
            {
                roles = Roles.GetRolesForUser(identity.Name);
            }
            else
            {
                roles = new[] { applicationConfiguration.UnknownUserRoleName };
            }
            var webIdentity = new WebIdentity(identity, roles);
            var principal = new WebsitePrincipal(webIdentity)
            {
                ApplicationConfiguration = applicationConfiguration
            };
    
            HttpContext.Current.User = principal;
        }
        .
        .
        .
    }
    

    Resolution Mapping Code

        public class ApplicationConfigurationContractMapping : NinjectModule
    {
        public override void Load()
        {
            Bind<IApplicationConfiguration>()
                .To<ApplicationConfiguration>();
        }
    }
    

    ApplicationConfiguration

    public class ApplicationConfiguration : IApplicationConfiguration
    {
        .
        .
        .
        .
    }
    

    I am using Ninject as my Dependency Injection framework. Any suggestions appreciated.

    EDIT: Full code can be seen here: https://github.com/dibley1973/Dibware.Template.Presentation.Web

  • Dib
    Dib almost 10 years
    Oooh that looks pretty sweet, thank you. I will give that a go and feedback.
  • Dib
    Dib almost 10 years
    That works a treat and looks nice and neat. Thank you