将逻辑与控制器分开并使用类库

时间:2017-04-17 13:38:36

标签: c# asp.net-mvc asp.net-web-api

我正在尝试将OWIN应用到我的应用程序中,但我讨厌登录控制器,所以我决定尝试将登录信息移到我的提供程序中。

我的控制器方法如下所示:

[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("externallogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLoginAsync(string provider, string error = null) => await _userProvider.GetExternalLoginAsync(this, User.Identity, provider, error);

在我的 UserProvider 中,我已将代码更改为:

public async Task<IHttpActionResult> GetExternalLoginAsync(ApiController controller, IIdentity identity, string provider, string error = null)
{

    // If we have an error, return it
    if (error != null) throw new Exception(Uri.EscapeDataString(error));

    // If the current user is not authenticated
    if (!identity.IsAuthenticated) return new ChallengeResult(provider, controller);

    // Validate the client and the redirct url
    var redirectUri = await ValidateClientAndRedirectUriAsync(controller.Request);

    // If we have no validated
    if (!string.IsNullOrWhiteSpace(redirectUri)) throw new Exception(redirectUri);

    // Get the current users external login
    var externalLogin = GetExternalLoginDataFromIdentity(identity as ClaimsIdentity);

    // If the current user has no external logins, throw an error
    if (externalLogin == null) throw new ObjectNotFoundException();

    // if the login provider doesn't match the one specificed
    if (externalLogin.LoginProvider != provider)
    {

        // Sign the user out
        AuthenticationManager(controller.Request).SignOut(DefaultAuthenticationTypes.ExternalCookie);

        // Challenge the result
        return new ChallengeResult(provider, controller);
    }

    // Get the user
    var user = await FindAsync(new UserLoginInfo(externalLogin.LoginProvider, externalLogin.ProviderKey));

    // Have they registered
    bool hasRegistered = user != null;

    // Update our url
    redirectUri += $"{redirectUri}#external_access_token={externalLogin.ExternalAccessToken}&provider={externalLogin.LoginProvider}&haslocalaccount={hasRegistered.ToString()}&external_user_name={externalLogin.UserName}";

    // Return our url
    return new RedirectResult(redirectUri);
}

对于这个特定的方法,这不是一个太大的问题,但我希望能够使用控制器的 Ok(),BadRequest()和InternalServerError()方法。另外,我想使用 ModelState.IsValid 。后者可以通过将控制器传递给方法来使用,我可以简单地执行:

controller.ModelState.IsValid

但其他方法是内部方法,所以我必须在我的提供者中继承 ApiController ,我无法做到或找到另一种方法。

我发现this on stackoverflow让我觉得我可以做类似的事情,因为我已经将控制器传递给方法了,所以我创建了一个像这样的静态Extension类:

public static class ResponseExtensions
{
    public static HttpResponseMessage Ok(T content) => new HttpResponseMessage(HttpStatusCode.Accepted, content);
}

在我的方法中,我希望做到这样的事情:

return controller.ResponseMessage(ResponseExtensions.Ok());

但出现同样的问题是 ResponseMessage 也是受保护的内部方法

以前有没有人遇到这个问题,能否就如何解决这个问题给出一些指示?

3 个答案:

答案 0 :(得分:2)

首先,您的控制器中没有“登录”。实际的身份验证和授权已经足够抽象。您在控制器中拥有的所有内容都是调用,这对控制器来说非常有效。进一步抽象的唯一原因是,如果你需要做一些事情,比如在多个不同的控制器中登录。否则,你只是在浪费时间。

其次,您不需要控制器实例来返回各种结果类型。像ControllerOkBadRequest上的方法只是方便的方法。它们各自返回ActionResult个子类,即OkResultBadRequestResult等。如果您的“提供者”需要返回操作结果,只需返回这些类的新实例。

答案 1 :(得分:0)

我认为这样做的方法是使用(argh)方法隐藏。 我创建了一个 BaseApiController

public class BaseApiController : ApiController
{
    public IHttpActionResult Ok<T>(T content) => Ok<T>(content);
    public IHttpActionResult Ok() => Ok();
    public IHttpActionResult InternalServerError() => InternalServerError();
    public IHttpActionResult InternalServerError(Exception exception) => InternalServerError(exception);
    public IHttpActionResult BadRequest() => BadRequest();
    public IHttpActionResult BadRequest(ModelStateDictionary modelState) => BadRequest(modelState);
    public IHttpActionResult BadRequest(string message) => BadRequest(message);
    public IHttpActionResult Redirect(Uri location) => Redirect(location);
    public IHttpActionResult Redirect(string location) => Redirect(location);
}

然后我的 UserController 只是继承了这个基本控制器:

public class UsersController : BaseApiController

然后我可以将提供商方法更改为:

public async Task<IHttpActionResult> GetExternalLoginAsync(BaseApiController controller, IIdentity identity, string provider, string error = null)
{

    // If we have an error, return it
    if (error != null) return controller.BadRequest(Uri.EscapeDataString(error));

    // If the current user is not authenticated
    if (!identity.IsAuthenticated) return new ChallengeResult(provider, controller);

    // Validate the client and the redirct url
    var redirectUri = await ValidateClientAndRedirectUriAsync(controller.Request);

    // If we have no validated
    if (!string.IsNullOrWhiteSpace(redirectUri)) return controller.BadRequest(redirectUri);

    // Get the current users external login
    var externalLogin = GetExternalLoginDataFromIdentity(identity as ClaimsIdentity);

    // If the current user has no external logins, throw an error
    if (externalLogin == null) return controller.InternalServerError();

    // if the login provider doesn't match the one specificed
    if (externalLogin.LoginProvider != provider)
    {

        // Sign the user out
        AuthenticationManager(controller.Request).SignOut(DefaultAuthenticationTypes.ExternalCookie);

        // Challenge the result
        return new ChallengeResult(provider, controller);
    }

    // Get the user
    var user = await FindAsync(new UserLoginInfo(externalLogin.LoginProvider, externalLogin.ProviderKey));

    // Have they registered
    bool hasRegistered = user != null;

    // Update our url
    redirectUri += $"{redirectUri}#external_access_token={externalLogin.ExternalAccessToken}&provider={externalLogin.LoginProvider}&haslocalaccount={hasRegistered.ToString()}&external_user_name={externalLogin.UserName}";

    // Return our url
    return controller.Redirect(redirectUri);
}

这似乎奏效了。如果有任何更好的解决方案我会感兴趣,因为我并不想将整个控制器发送到我的方法。

答案 2 :(得分:0)

或者,如果您知道需要使用Redirect方法,则可以将重定向lambda传递给您的方法。您还可以传递您想要使用的控制器中的所有数据。或者创建一个将包含这些lambda作为属性的对象:

class Context
{
    Func<string, IHttpActionResult> Redirect { get; set; }
    ...
}

行动方法:

var context = new Context {Redirect = this.Redirect};
return UserProvider.GetExternalLoginAsync(..., context)

在提供者中:

return context.Redirect(redirectUri);

这可能是一种解决方法,但我宁愿重新考虑您的服务及其职责。