我想让我的api发出的所有OkObjectResult响应都通过我拥有的自定义JSON解析器运行。解析程序依赖于某些特定于请求的数据,即用户的角色。实际上,它类似于控制器上的Authorize属性,但是对于从API传递到UI的数据传输对象。

我可以通过AddJsonOptions在“配置服务”中添加解析器,但那里没有访问该用户信息的权限。

如何将基于请求的值传递给此解析器?我在看某种自定义中间件,还是其他?

作为示例,如果我有一个带有一些自定义属性装饰器的对象,如下所示:

public class TestObject
{
    public String Field1 => "NoRestrictions";
    [RequireRoleView("Admin")]
    public String Field2 => "ViewRequiresAdmin";
}


并用不同的角色调用我的自定义序列化程序,如下所示:

var test = new TestObject();
var userRoles = GetRoles(); // "User" for the sake of this example
var outputJson = JsonConvert.SerializeObject(test,
                    new JsonSerializerSettings {
                        ContractResolver = new MyCustomResolver(userRoles)
                    });


然后,输出JSON将跳过用户无法访问的所有内容,例如:

{
    "Field1":"NoRestrictions",
    // Note the absence of Field2, since it has [RequireRoleView("Admin")]
}

最佳答案

假设您有一个自定义RequireRoleViewAttribute

[AttributeUsageAttribute(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class RequireRoleViewAttribute : Attribute
{

    public string Role;

    public RequireRoleViewAttribute(string role){
        this.Role = role;
    }
}



  如何将基于请求的值传递给此解析器?


您可以在自定义解析器中注入IServiceProvider

public class RoleBasedContractResolver : DefaultContractResolver
{
    public IServiceProvider ServiceProvider { get; }
    public RoleBasedContractResolver( IServiceProvider sp)
    {
        this.ServiceProvider = sp;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var contextAccessor = this.ServiceProvider.GetRequiredService<IHttpContextAccessor>() ;
        var context = contextAccessor.HttpContext;
        var user = context.User;

       // if you're using the Identity, you can get the userManager :
       var userManager = context.RequestServices.GetRequiredService<UserManager<IdentityUser>>();

       // ...
    }
}


因此我们可以根据需要获取HttpContextUser。如果您使用的是Identity,还可以获取UserManager服务和角色。

现在我们可以按照@dbc's advice来控制ShouldSerialize了:

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var contextAccessor = this.ServiceProvider.GetRequiredService<IHttpContextAccessor>() ;
        var context = contextAccessor.HttpContext;
        var user = context.User;

        // if you use the Identitiy, you can get the usermanager
        //UserManager<IdentityUser>
        var userManager = context.RequestServices.GetRequiredService<UserManager<IdentityUser>>();

        JsonProperty property = base.CreateProperty(member, memberSerialization);

        // get the attributes
        var attrs=member.GetCustomAttributes<RequireRoleViewAttribute>();

        // if no [RequireResoveView] decorated, always serialize it
        if(attrs.Count()==0) {
            property.ShouldDeserialize = instance => true;
            return property;
        }

        // custom your logic to dertermine wether should serialize the property
        // I just use check if it can statisify any the condition :
        var roles = this.GetIdentityUserRolesAsync(context,userManager).Result;
        property.ShouldSerialize = instance => {
            var resource = new { /* any you need  */ };
            return attrs.Any(attr => {
                var rolename = attr.Role;
                return roles.Any(r => r == rolename ) ;
            }) ? true : false;
        };
        return property;
    }


这里的功能GetIdentityUserRolesAsync是使用当前HttpContextUserManger服务检索角色的帮助方法:

private async Task<IList<string>> GetIdentityUserRolesAsync(HttpContext context, UserManager<IdentityUser> userManager)
{
    var rolesCached= context.Items["__userRoles__"];
    if( rolesCached != null){
        return (IList<string>) rolesCached;
    }
    var identityUser = await userManager.GetUserAsync(context.User);
    var roles = await userManager.GetRolesAsync(identityUser);
    context.Items["__userRoles__"] = roles;
    return roles;
}


如何详细注入IServiceProvider

诀窍是关于如何使用MvcJwtOptions配置默认的IServiceProvider

不要通过以下方式配置JsonOptions

services.AddMvc().
    .AddJsonOptions(o =>{
        // o.
    });


因为它不允许我们添加IServiceProvider参数。

我们可以自定义MvcJsonOptions的子类:

public class MyMvcJsonOptionsWrapper : IConfigureOptions<MvcJsonOptions>
{
    IServiceProvider ServiceProvider;
    public MyMvcJsonOptionsWrapper(IServiceProvider serviceProvider)
    {
        this.ServiceProvider = serviceProvider;
    }
    public void Configure(MvcJsonOptions options)
    {
        options.SerializerSettings.ContractResolver =new RoleBasedContractResolver(ServiceProvider);
    }
}


并通过以下方式注册服务:

services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

// don't forget to add the IHttpContextAccessor
services.AddTransient<IConfigureOptions<MvcJsonOptions>,MyMvcJsonOptionsWrapper>();


测试用例 :

假设您有一个自定义POCO:

public class TestObject
{
    public string Field1 => "NoRestrictions";

    [RequireRoleView("Admin")]
    public string Field2 => "ViewRequiresAdmin";

    [RequireRoleView("HR"),RequireRoleView("OP")]
    public string Field3 => "ViewRequiresHROrOP";

    [RequireRoleView("IT"), RequireRoleView("HR")]
    public string Field4 => "ViewRequiresITOrHR";

    [RequireRoleView("IT"), RequireRoleView("OP")]
    public string Field5 => "ViewRequiresITOrOP";
}


并且当前用户具有以下角色:AdminHR

结果将是:

{"Field1":"NoRestrictions","Field2":"ViewRequiresAdmin","Field3":"ViewRequiresHROrOP","Field4":"ViewRequiresITOrHR"}


使用操作方法进行测试的屏幕截图:

c# - .Net Core Api-基于请求值的自定义JSON解析器-LMLPHP

10-01 04:55
查看更多