web-dev-qa-db-fra.com

MVC 5 Comment définir Owin LoginPath avec des itinéraires localisés

J'ai un site Web MVC 5 avec des itinéraires localisés définis comme

routes.MapRoute(
            name: "Default",
            url: "{culture}/{controller}/{action}/{id}",
            defaults: new { culture = CultureHelper.GetDefaultCulture(), controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

Où la culture par défaut se traduit par "en-US".


Le problème survient lorsque, au démarrage, je dois définir l'URL de connexion à l'aide de la propriété LoginPath, qui est définie une fois et elle utilisera toujours la valeur fournie, par exemple la culture par défaut si "/ en-Us/Account/Login" est la valeur spécifiée. J'ai ensuite essayé d'utiliser la classe UrlHelper dans l'espoir de faire l'expérience de la magie mais le résultat est évidemment le même:

var httpContext = HttpContext.Current;
        if (httpContext == null) {
          var request = new HttpRequest("/", "http://example.com", "");
          var response = new HttpResponse(new StringWriter());
          httpContext = new HttpContext(request, response);
        }

        var httpContextBase = new HttpContextWrapper(httpContext);
        var routeData = new RouteData();
        var requestContext = new RequestContext(httpContextBase, routeData);
        UrlHelper helper = new UrlHelper(requestContext);

        var loginPath = helper.Action("Login", "Account");

        // Enable the application to use a cookie to store information for the signed in user
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,                
            LoginPath = new PathString(loginPath)
        });

Ma question est: existe-t-il un moyen de pirater ce mécanisme pour récupérer dynamiquement la culture actuelle ou suis-je obligé de définir la culture actuelle dans un cookie et, lorsque je suis redirigé vers la page de connexion, utilisez la valeur du cookie pour définir le courant la culture avant de rendre la page?

Merci

47
s0nica

J'ai exactement eu le même problème et trouvé un moyen de surmonter cette limitation.

Dans les options CookieAuthenticationOptions, il existe une propriété "Provider" qui est initialisée avec le CookieAuthenticationProvider. Cela implémente une méthode appelée ApplyRedirect et un délégué OnApplyRedirect. Ma première idée a été d'écraser ce ApplyRedirect et d'implémenter la logique requise pour gérer les itinéraires localisés. Mais malheureusement, cela ne peut pas être annulé. Passer ma logique à OnApplyRedirect fait écraser le comportement par défaut. Vous pouvez théoriquement saisir le source de ce comportement , le copier dans votre projet et le modifier selon vos besoins, mais ce n'est évidemment pas une bonne pratique. Tout d'abord, j'ai décidé de créer un wrapper autour de CookieAuthenticationProvider avec deux points d'extension en utilisant des délégués et en préservant le comportement par défaut, à l'exception de l'URL utilisée - ou plus facilement, contourner le wrapper (thx à lafi).

Ensuite, dans la configuration d'authentification, j'ai ajouté ma logique personnalisée au fournisseur:

public void ConfigureAuth(IAppBuilder app)
{
    UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);

    CookieAuthenticationProvider provider = new CookieAuthenticationProvider();

    var originalHandler = provider.OnApplyRedirect;

    //Our logic to dynamically modify the path (maybe needs some fine tuning)
    provider.OnApplyRedirect = context =>
    {
        var mvcContext = new HttpContextWrapper(HttpContext.Current);
        var routeData = RouteTable.Routes.GetRouteData(mvcContext);

        //Get the current language  
        RouteValueDictionary routeValues = new RouteValueDictionary();
        routeValues.Add("lang", routeData.Values["lang"]);

        //Reuse the RetrunUrl
        Uri uri = new Uri(context.RedirectUri);
        string returnUrl = HttpUtility.ParseQueryString(uri.Query)[context.Options.ReturnUrlParameter];
        routeValues.Add(context.Options.ReturnUrlParameter, returnUrl);

        //Overwrite the redirection uri
        context.RedirectUri = url.Action("login", "account", routeValues);
        originalHandler.Invoke(context);
    };

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString(url.Action("login", "account")),
        //Set the Provider
        Provider = provider
    });
}

Voir aussi ce code:

J'espère que cela correspond à vos besoins.

MISE À JOUR: Pour moins de confusion, j'ai mis à jour ma réponse pour utiliser l'amélioration de @Lafis, sans utiliser de classe wrapper pour appliquer le comportement étendu. Merci également de donner crédit à @Lafis lors du vote.

40
martinoss

Pour améliorer la réponse @martinoss, vous pouvez atteindre le même résultat sans implémenter un wrapper. Copiez simplement le gestionnaire d'origine, attribuez-en un nouveau qui implémente votre logique de redirection pour modifier context.RedirectionUri, et à la fin, appelez le gestionnaire d'origine.

CookieAuthenticationProvider provider = new CookieAuthenticationProvider();

var originalHandler = provider.OnApplyRedirect;
provider.OnApplyRedirect = context =>
{
    //insert your logic here to generate the redirection URI
    string NewURI = "....";
    //Overwrite the redirection uri
    context.RedirectUri = NewURI;
    originalHandler.Invoke(context);
};

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
   AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
   LoginPath = new PathString(url.Action("Login", "Account")),
   Provider = provider
});
33
Lafi

Que dis-tu de ça:

var cao = new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider { OnApplyRedirect = ApplyRedirect }
        };
app.UseCookieAuthentication(cao);

et

  private static void ApplyRedirect(CookieApplyRedirectContext context)
    {

        UrlHelper _url = new UrlHelper(HttpContext.Current.Request.RequestContext);
        String actionUri = _url.Action("Login", "Account", new { });
        context.Response.Redirect(actionUri);
    }
13
Sentinel

Sans prendre trop de responsabilité sur le format d'URL, etc., vous pouvez faire quelque chose comme ce qui suit

public static void Configuration(IAppBuilder app)
{
    UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString(url.Action("LogOn", "Account", new { area = "Account" })),
        Provider = new CookieAuthenticationProvider
        {
            OnApplyRedirect = context => context.Response.Redirect(context.RedirectUri.Replace(CultureHelper.GetDefaultCulture(), Thread.CurrentUiCulture.Name))
        }
    });
}
4
Jani Hyytiäinen

J'ai amélioré la réponse Sentinel, pour garder l'URL de retour:

private static void ApplyRedirect(CookieApplyRedirectContext context)
        {
            //use this way to keep return url
            var loginUrl = context.RedirectUri.Insert(
                context.RedirectUri.IndexOf("/Account/Login"),
                "/" + CultureHelper.GetCurrentCulture());

            context.Response.Redirect(loginUrl);
        }
2
utilsit

Je pense que j'ai un an de retard dans cette réponse mais l'objectif principal ici est de partager les connaissances ... :)

Je viens de trouver le même problème dans une application que je développe actuellement. J'ai examiné la quantité de code dont nous avons besoin (dans les messages précédents) pour résoudre ce problème et je m'inquiète (la plupart du code était complexe et touchait l'intérieur de la bête). J'ai donc essayé de trouver une solution plus simple et j'ai ajouté la route suivante à ma collection de routes:

routes.MapRoute(
            name: "loginRoute",
            url: "account/login",
            defaults:new { culture = "", controller = "account", action = "login", id = UrlParameter.Optional });

Cela permettra à mon action de connexion dans le contrôleur de compte d'être invoquée et mon mécanisme standard (un remplacement de la méthode BeginExecuteCore du contrôleur) est capable de joindre la culture d'interface utilisateur actuelle à l'URL.

J'espère que ça aide quelqu'un.

Ajouté: Mon mécanisme standard:

protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
    {
        var cultureName = RouteData.Values["culture"] as string;

        var cultureCookie = Request.Cookies["_culture"];
        if (cultureCookie != null && string.IsNullOrEmpty(cultureName))
        {
            cultureName = cultureCookie.Value;
        }

        if (cultureName == null)
            cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? Request.UserLanguages[0] : null; 

        cultureName = CultureHelper.GetImplementedCulture(cultureName); 

        if (RouteData.Values["culture"] as string != cultureName)
        {
            RouteData.Values["culture"] = cultureName.ToLowerInvariant(); // lower case too

            var cookie = Request.Cookies["_culture"];
            if (cookie != null)
                cookie.Value = cultureName;   // update cookie value
            else
            {
                cookie = new HttpCookie("_culture") { Value = cultureName, Expires = DateTime.Now.AddYears(1) };
            }
            Response.Cookies.Add(cookie);

            // Redirect user
            Response.RedirectToRoute(RouteData.Values);
        }

        Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
        Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

        return base.BeginExecuteCore(callback, state);
    }
1
GRGodoi