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

2

14

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

Monoploid answered 19/5, 2014 at 5:19 Comment(0)
L
15

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 reason for this 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()
    {
        // ...
    }
}
Landloper answered 19/5, 2014 at 11:45 Comment(2)
Oooh that looks pretty sweet, thank you. I will give that a go and feedback.Monoploid
That works a treat and looks nice and neat. Thank youMonoploid
M
4

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);
    }
.
.
}
Monoploid answered 19/5, 2014 at 12:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.