在API方法之前转换HttpContext.User

时间:2018-10-05 08:58:22

标签: api asp.net-core .net-core asp.net-core-mvc asp.net-core-webapi

我目前有一个.Net Core API应用程序,其中包含一堆API get方法。当前,在每种方法中,我都需要编写以下行:

        [ProducesResponseType(200, Type = typeof(MetadataAttributeModel))]
        [ProducesResponseType(400, Type = typeof(ValidationResultModel))]
        [ProducesResponseType(500, Type = typeof(ErrorResultModel))]
        public ActionResult<MetadataAttributeModel> GetAsync(string name)
        {
            List<Entities.DocumentAttributeView> attributes = documentAttributeViewRepo.GetByAttributeName(name);

            SiteUser currentUser = new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);

            return Unauthorized();
        }

在获得方法之前,是否可以将HttpContext.User对象转换为我们自己的SiteUser对象?我不想在所有API方法中都写这一行:

 SiteUser currentUser = new SiteUser(db, HttpContext.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);

TIA, 亚历克斯

2 个答案:

答案 0 :(得分:1)

用于“为每个操作执行操作”的AspNet Mvc机制为Filters

过滤器可以在调用该方法之前运行,并且可以设置Http.Context.User

过滤器可以全局地应用于动作,控制器或(通过在Startup中编写代码)。

[SwapUserToAuthorizedDatabaseUser]
public class MyController
{
    public IActionResult About() => Ok(User);
}

哪个将为Controller上的每个Action调用此过滤器:

public class SwapUserToAuthorizedDatabaseUserAttribute : Attribute, IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        SiteUser currentUser = new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
        if (currentUser == null)
        {
            context.Result= new RedirectToRouteResult("/Identity/Logout");
        }
        else
        {
            var claimsIdentity =
                new ClaimsIdentity(
                    new Claim[]
                    {
                        new Claim("Id", currentUser.Id),
                        new Claim("UserName", currentUser.UserName),
                        new Claim("WhateverElseYourSiteUserHas", currentUser.Something.ToString()),
                    }
                );
            context.HttpContext.User = new ClaimsPrincipal(new[]{claimsIdentity});
        }
    }
    public void OnActionExecuted(ActionExecutedContext context){}
}

如果您不需要覆盖HttpContext.User,则使用HttpContext.Items的代码要少得多:

public class SwapUserToAuthorizedDatabaseUserAttribute : Attribute, IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        context.HttpContext.Items["SiteUser"]= new SiteUser(db, User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
    }
    public void OnActionExecuted(ActionExecutedContext context){}
}

您可以使用具有IActionFilter方法的IAuthorizationFilter来代替在每个Action上运行的public void OnAuthorization(AuthorizationFilterContext context)。这样可以省去重复调用数据库的麻烦,但这确实意味着您必须将currentUser缓存在某个地方,大概在Session中。

问题是,您如何访问数据库?如果您选择通过在“启动”中添加“全局过滤器”来添加路由的方法:

public void ConfigureServices(IServiceCollection services)
{
        services
            .AddMvc(o=>o.Filters.Add(new SwapUserToAuthorizedDatabaseUserAttribute(provide a db instance here)));
}

然后,您可以为Filter提供一个构造函数,并传入数据库。使用DependencyInjection系统还有很多负担。

如果您不使用启动方法,则必须进行一些DIY注入,例如,使用静态方法来返回DbContext

答案 1 :(得分:0)

您可以将此逻辑移至服务:

public class UserService : IUserService
{
    private readonly HttpContext context;
    private readonly Db db;

    public UserService(IHttpContextAccessor context, Db db)
    {
        this.context = context.HttpContext;
        this.db = db;
    }

    public SiteUser GetUser()
    {
        return new SiteUser(db, context.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress").Value);
    }
}

将其注入需要的控制器:

public MyController(IUserService userService) { ... }

IHttpContextAccessor(应为单例)一起在Startup.cs的ConfigureServices中将其注册为范围服务:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<UserService>();
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}