c# - .Net Core Api - 基于请求值的自定义 JSON 解析器
问题描述
我希望所有OkObjectResult
来自我的 api 的响应都通过我拥有的自定义 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>>();
// ...
}
}
因此我们可以随心所欲地得到HttpContext
and User
。如果您使用身份,您还可以获得UserManager
服务和角色。
现在我们可以按照@dbc 的建议来控制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
是使用当前HttpContext
和UserManger
服务检索角色的辅助方法:
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
by :
services.AddMvc().
.AddJsonOptions(o =>{
// o.
});
因为它不允许我们添加IServiceProvider
参数。
我们可以自定义一个子类MvcJsonOptions
:
// in .NET 3.1 and above, change this from MvcJsonOptions to MvcNewtonsoftJsonOptions
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
// in .NET 3.1 and above, change this from MvcJsonOptions to MvcNewtonsoftJsonOptions
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";
}
当前用户具有角色:Admin
和HR
:
结果将是:
{"Field1":"NoRestrictions","Field2":"ViewRequiresAdmin","Field3":"ViewRequiresHROrOP","Field4":"ViewRequiresITOrHR"}
使用操作方法进行测试的屏幕截图:
推荐阅读
- aiohttp - aiohttp 服务器 - FileResponse 返回后删除文件
- python - 如何删除源视频,只允许在 YOLOv4 中显示叠加图像跟踪?
- java - 方法不会使用接口和简单规则覆盖或实现 Eclipse 上超类型中的方法
- unity3d - 从搅拌机导入模型到统一时,我得到奇怪的材料结果
- java - 致命异常:mainProcess:com.example.blooddonation,PID:6978 java.lang.RuntimeException:无法启动活动
- r - 关于未使用的参数错误的问题
- python - 将numpy中的字节数组转换为int32
- forms - 自定义表单设计问题
- python - python3 中调用 C 脚本的子进程在“失败。错误:[Errno 32] Broken pipe”上失败
- python - 在 Python 中从 URL 播放 .mp3