.Net Core Api - Custom JSON Resolver based on Request Values












1















I'm looking to have all OkObjectResult responses coming out of my api get run through a custom JSON resolver that I have. The resolver relies on some request-specific data - namely, the user's roles. It's effectively like the Authorize attribute on a controller, but for data transfer objects passed from the API to the UI.



I can add the resolver in Configure Services via AddJsonOptions, but it doesn't have access to that user info there.



How can I pass values that are based on the request to this resolver? Am I looking at some sort of custom middleware, or something else?



As a sample, if I have an object with some custom attribute decorators, like so:



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


And call my custom serializer with different roles, like so:



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)
});


Then the output JSON will skip anything the user can't access, like so:



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









share|improve this question


















  • 2





    Can you pass the userRoles into TestObject when it is constructed? If all your objects that have RequireRoleViewAttribute applied could have userRoles as an internal property, you could make a custom contract resolver that does the necessary checks in a custom JsonProperty.ShouldSerialize predicate.

    – dbc
    Nov 13 '18 at 20:28






  • 1





    Yeah, my hesitance on ShouldSerialize is in potential for name changes and stuff like that in the future - changing a property meaning we need to remember to change ShouldSerialize, etc. I can probably update MyCustomResolver to check for that property on the object being serialized though, instead of needing the roles passed to it when called. I'll poke around with that and see how it goes. Thanks!

    – user1874135
    Nov 13 '18 at 20:39








  • 2





    Just to be clear, I meant that the custom contract resolver itself could add a synthetic ShouldSerialize predicate based on the current user roles and value for RequireRoleView. I.e. the logic would be similar to the contract resolver from this answer but rather than doing the checks directly in CreateProperty, CreateProperty() would add a ShouldSerialize predicate that would check against object's roles.

    – dbc
    Nov 13 '18 at 20:46
















1















I'm looking to have all OkObjectResult responses coming out of my api get run through a custom JSON resolver that I have. The resolver relies on some request-specific data - namely, the user's roles. It's effectively like the Authorize attribute on a controller, but for data transfer objects passed from the API to the UI.



I can add the resolver in Configure Services via AddJsonOptions, but it doesn't have access to that user info there.



How can I pass values that are based on the request to this resolver? Am I looking at some sort of custom middleware, or something else?



As a sample, if I have an object with some custom attribute decorators, like so:



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


And call my custom serializer with different roles, like so:



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)
});


Then the output JSON will skip anything the user can't access, like so:



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









share|improve this question


















  • 2





    Can you pass the userRoles into TestObject when it is constructed? If all your objects that have RequireRoleViewAttribute applied could have userRoles as an internal property, you could make a custom contract resolver that does the necessary checks in a custom JsonProperty.ShouldSerialize predicate.

    – dbc
    Nov 13 '18 at 20:28






  • 1





    Yeah, my hesitance on ShouldSerialize is in potential for name changes and stuff like that in the future - changing a property meaning we need to remember to change ShouldSerialize, etc. I can probably update MyCustomResolver to check for that property on the object being serialized though, instead of needing the roles passed to it when called. I'll poke around with that and see how it goes. Thanks!

    – user1874135
    Nov 13 '18 at 20:39








  • 2





    Just to be clear, I meant that the custom contract resolver itself could add a synthetic ShouldSerialize predicate based on the current user roles and value for RequireRoleView. I.e. the logic would be similar to the contract resolver from this answer but rather than doing the checks directly in CreateProperty, CreateProperty() would add a ShouldSerialize predicate that would check against object's roles.

    – dbc
    Nov 13 '18 at 20:46














1












1








1








I'm looking to have all OkObjectResult responses coming out of my api get run through a custom JSON resolver that I have. The resolver relies on some request-specific data - namely, the user's roles. It's effectively like the Authorize attribute on a controller, but for data transfer objects passed from the API to the UI.



I can add the resolver in Configure Services via AddJsonOptions, but it doesn't have access to that user info there.



How can I pass values that are based on the request to this resolver? Am I looking at some sort of custom middleware, or something else?



As a sample, if I have an object with some custom attribute decorators, like so:



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


And call my custom serializer with different roles, like so:



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)
});


Then the output JSON will skip anything the user can't access, like so:



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









share|improve this question














I'm looking to have all OkObjectResult responses coming out of my api get run through a custom JSON resolver that I have. The resolver relies on some request-specific data - namely, the user's roles. It's effectively like the Authorize attribute on a controller, but for data transfer objects passed from the API to the UI.



I can add the resolver in Configure Services via AddJsonOptions, but it doesn't have access to that user info there.



How can I pass values that are based on the request to this resolver? Am I looking at some sort of custom middleware, or something else?



As a sample, if I have an object with some custom attribute decorators, like so:



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


And call my custom serializer with different roles, like so:



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)
});


Then the output JSON will skip anything the user can't access, like so:



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






c# json.net asp.net-core-webapi






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 13 '18 at 20:01









user1874135user1874135

8712




8712








  • 2





    Can you pass the userRoles into TestObject when it is constructed? If all your objects that have RequireRoleViewAttribute applied could have userRoles as an internal property, you could make a custom contract resolver that does the necessary checks in a custom JsonProperty.ShouldSerialize predicate.

    – dbc
    Nov 13 '18 at 20:28






  • 1





    Yeah, my hesitance on ShouldSerialize is in potential for name changes and stuff like that in the future - changing a property meaning we need to remember to change ShouldSerialize, etc. I can probably update MyCustomResolver to check for that property on the object being serialized though, instead of needing the roles passed to it when called. I'll poke around with that and see how it goes. Thanks!

    – user1874135
    Nov 13 '18 at 20:39








  • 2





    Just to be clear, I meant that the custom contract resolver itself could add a synthetic ShouldSerialize predicate based on the current user roles and value for RequireRoleView. I.e. the logic would be similar to the contract resolver from this answer but rather than doing the checks directly in CreateProperty, CreateProperty() would add a ShouldSerialize predicate that would check against object's roles.

    – dbc
    Nov 13 '18 at 20:46














  • 2





    Can you pass the userRoles into TestObject when it is constructed? If all your objects that have RequireRoleViewAttribute applied could have userRoles as an internal property, you could make a custom contract resolver that does the necessary checks in a custom JsonProperty.ShouldSerialize predicate.

    – dbc
    Nov 13 '18 at 20:28






  • 1





    Yeah, my hesitance on ShouldSerialize is in potential for name changes and stuff like that in the future - changing a property meaning we need to remember to change ShouldSerialize, etc. I can probably update MyCustomResolver to check for that property on the object being serialized though, instead of needing the roles passed to it when called. I'll poke around with that and see how it goes. Thanks!

    – user1874135
    Nov 13 '18 at 20:39








  • 2





    Just to be clear, I meant that the custom contract resolver itself could add a synthetic ShouldSerialize predicate based on the current user roles and value for RequireRoleView. I.e. the logic would be similar to the contract resolver from this answer but rather than doing the checks directly in CreateProperty, CreateProperty() would add a ShouldSerialize predicate that would check against object's roles.

    – dbc
    Nov 13 '18 at 20:46








2




2





Can you pass the userRoles into TestObject when it is constructed? If all your objects that have RequireRoleViewAttribute applied could have userRoles as an internal property, you could make a custom contract resolver that does the necessary checks in a custom JsonProperty.ShouldSerialize predicate.

– dbc
Nov 13 '18 at 20:28





Can you pass the userRoles into TestObject when it is constructed? If all your objects that have RequireRoleViewAttribute applied could have userRoles as an internal property, you could make a custom contract resolver that does the necessary checks in a custom JsonProperty.ShouldSerialize predicate.

– dbc
Nov 13 '18 at 20:28




1




1





Yeah, my hesitance on ShouldSerialize is in potential for name changes and stuff like that in the future - changing a property meaning we need to remember to change ShouldSerialize, etc. I can probably update MyCustomResolver to check for that property on the object being serialized though, instead of needing the roles passed to it when called. I'll poke around with that and see how it goes. Thanks!

– user1874135
Nov 13 '18 at 20:39







Yeah, my hesitance on ShouldSerialize is in potential for name changes and stuff like that in the future - changing a property meaning we need to remember to change ShouldSerialize, etc. I can probably update MyCustomResolver to check for that property on the object being serialized though, instead of needing the roles passed to it when called. I'll poke around with that and see how it goes. Thanks!

– user1874135
Nov 13 '18 at 20:39






2




2





Just to be clear, I meant that the custom contract resolver itself could add a synthetic ShouldSerialize predicate based on the current user roles and value for RequireRoleView. I.e. the logic would be similar to the contract resolver from this answer but rather than doing the checks directly in CreateProperty, CreateProperty() would add a ShouldSerialize predicate that would check against object's roles.

– dbc
Nov 13 '18 at 20:46





Just to be clear, I meant that the custom contract resolver itself could add a synthetic ShouldSerialize predicate based on the current user roles and value for RequireRoleView. I.e. the logic would be similar to the contract resolver from this answer but rather than doing the checks directly in CreateProperty, CreateProperty() would add a ShouldSerialize predicate that would check against object's roles.

– dbc
Nov 13 '18 at 20:46












2 Answers
2






active

oldest

votes


















2














Suppose you have an custom RequireRoleViewAttribute:



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

public string Role;

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



How can I pass values that are based on the request to this resolver?




You can have a IServiceProvider injected in your custom resolver :



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>>();

// ...
}
}


thus we can get the HttpContext and User as we like. If you're using the Identity, you can also get the UserManager service and roles.



and now we can follow @dbc's advice to control the 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;
}


The function GetIdentityUserRolesAsync here is helper method to retrieve roles using the current HttpContext and the UserManger service :



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;
}


How to inject the IServiceProvider in details :



The trick is all about how to configure the default MvcJwtOptions with an IServiceProvider.



Don't configure the JsonOptions by :



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


as it doesn't allow us add a IServiceProvider parameter.



We can custom a subclass of 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);
}
}


and register the services by :



services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

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


Test Case :



Let's say you have a custom 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";
}


And the Current User has roles : Admin and HR:



The result will be :



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


A screenshot of testing with an action method :



enter image description here






share|improve this answer





















  • 1





    Yaaaay, it works! I was having some issues with using UserManager.GetRolesAsync (service not registered, etc.), so I wound up falling back to getting user roles by parsing the claims, but otherwise it's pretty slick. I also set up some extension methods, so using it is just a matter of adding the RequireRoleView attribute and calling services.AddRoleBasedContractResolver() in Startup.ConfigureServices. Thanks to both of you :) I've accepted yours as the answer and I'll post my own with my tweaks in case anyone in the future finds it useful.

    – user1874135
    Nov 14 '18 at 15:53



















2














Itminus's answer covers everything that's needed, but for anyone interested, I've extended it a little for easy reuse.



First, in a class library



My RequireRoleViewAttribute, which allows multiple roles (OR, not AND):



[AttributeUsage(AttributeTargets.Property)]
public class RequireRoleViewAttribute : Attribute
{
public List<String> AllowedRoles { get; set; }

public RequireRoleViewAttribute(params String AllowedRoles) =>
this.AllowedRoles = AllowedRoles.Select(ar => ar.ToLower()).ToList();
}


My resolver is almost identical to Itminus's, but CreateProperty is adjusted to



IEnumerable<String> userRoles = this.GetIdentityUserRoles();

property.ShouldSerialize = instance =>
{
// Check if every attribute instance has at least one role listed in the user's roles.
return attrs.All(attr =>
userRoles.Any(ur =>
attr.AllowedRoles.Any(ar =>
String.Equals(ar, ur, StringComparison.OrdinalIgnoreCase)))
);
};


And GetIdentityUserRoles doesn't use UserManager



private IEnumerable<String> GetIdentityUserRoles()
{
IHttpContextAccessor contextAccessor = this.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
HttpContext context = contextAccessor.HttpContext;
ClaimsPrincipal user = context.User;
Object rolesCached = context.Items["__userRoles__"];
if (rolesCached != null)
{
return (List<String>)rolesCached;
}
var roles = ((ClaimsIdentity)user.Identity).Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
context.Items["__userRoles__"] = roles;
return roles;
}


And I have an extensions class which contains:



public static IServiceCollection AddRoleBasedContractResolver(this IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IConfigureOptions<MvcJsonOptions>, RoleBasedContractResolverOptions>();
return services;
}


Then in my API



I reference that class library. In Startup.cs -> ConfigureServices, I call:



public void ConfigureServices(IServiceCollection services)
{
...
services.AddRoleBasedContractResolver();
...
}


And my DTOs are tagged with the attribute:



public class Diagnostics
{
public String VersionNumber { get; set; }

[RequireRoleView("admin")]
public Boolean ViewIfAdmin => true;

[RequireRoleView("hr")]
public Boolean ViewIfHr => true;

[RequireRoleView("hr", "admin")]
public Boolean ViewIfHrOrAdmin => true;
}


And the return value as an admin is:



{
"VersionNumber": "Debug",
"ViewIfAdmin": true,
"ViewIfHrOrAdmin": true
}





share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53288633%2fnet-core-api-custom-json-resolver-based-on-request-values%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    2














    Suppose you have an custom RequireRoleViewAttribute:



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

    public string Role;

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



    How can I pass values that are based on the request to this resolver?




    You can have a IServiceProvider injected in your custom resolver :



    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>>();

    // ...
    }
    }


    thus we can get the HttpContext and User as we like. If you're using the Identity, you can also get the UserManager service and roles.



    and now we can follow @dbc's advice to control the 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;
    }


    The function GetIdentityUserRolesAsync here is helper method to retrieve roles using the current HttpContext and the UserManger service :



    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;
    }


    How to inject the IServiceProvider in details :



    The trick is all about how to configure the default MvcJwtOptions with an IServiceProvider.



    Don't configure the JsonOptions by :



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


    as it doesn't allow us add a IServiceProvider parameter.



    We can custom a subclass of 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);
    }
    }


    and register the services by :



    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

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


    Test Case :



    Let's say you have a custom 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";
    }


    And the Current User has roles : Admin and HR:



    The result will be :



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


    A screenshot of testing with an action method :



    enter image description here






    share|improve this answer





















    • 1





      Yaaaay, it works! I was having some issues with using UserManager.GetRolesAsync (service not registered, etc.), so I wound up falling back to getting user roles by parsing the claims, but otherwise it's pretty slick. I also set up some extension methods, so using it is just a matter of adding the RequireRoleView attribute and calling services.AddRoleBasedContractResolver() in Startup.ConfigureServices. Thanks to both of you :) I've accepted yours as the answer and I'll post my own with my tweaks in case anyone in the future finds it useful.

      – user1874135
      Nov 14 '18 at 15:53
















    2














    Suppose you have an custom RequireRoleViewAttribute:



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

    public string Role;

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



    How can I pass values that are based on the request to this resolver?




    You can have a IServiceProvider injected in your custom resolver :



    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>>();

    // ...
    }
    }


    thus we can get the HttpContext and User as we like. If you're using the Identity, you can also get the UserManager service and roles.



    and now we can follow @dbc's advice to control the 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;
    }


    The function GetIdentityUserRolesAsync here is helper method to retrieve roles using the current HttpContext and the UserManger service :



    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;
    }


    How to inject the IServiceProvider in details :



    The trick is all about how to configure the default MvcJwtOptions with an IServiceProvider.



    Don't configure the JsonOptions by :



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


    as it doesn't allow us add a IServiceProvider parameter.



    We can custom a subclass of 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);
    }
    }


    and register the services by :



    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

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


    Test Case :



    Let's say you have a custom 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";
    }


    And the Current User has roles : Admin and HR:



    The result will be :



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


    A screenshot of testing with an action method :



    enter image description here






    share|improve this answer





















    • 1





      Yaaaay, it works! I was having some issues with using UserManager.GetRolesAsync (service not registered, etc.), so I wound up falling back to getting user roles by parsing the claims, but otherwise it's pretty slick. I also set up some extension methods, so using it is just a matter of adding the RequireRoleView attribute and calling services.AddRoleBasedContractResolver() in Startup.ConfigureServices. Thanks to both of you :) I've accepted yours as the answer and I'll post my own with my tweaks in case anyone in the future finds it useful.

      – user1874135
      Nov 14 '18 at 15:53














    2












    2








    2







    Suppose you have an custom RequireRoleViewAttribute:



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

    public string Role;

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



    How can I pass values that are based on the request to this resolver?




    You can have a IServiceProvider injected in your custom resolver :



    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>>();

    // ...
    }
    }


    thus we can get the HttpContext and User as we like. If you're using the Identity, you can also get the UserManager service and roles.



    and now we can follow @dbc's advice to control the 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;
    }


    The function GetIdentityUserRolesAsync here is helper method to retrieve roles using the current HttpContext and the UserManger service :



    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;
    }


    How to inject the IServiceProvider in details :



    The trick is all about how to configure the default MvcJwtOptions with an IServiceProvider.



    Don't configure the JsonOptions by :



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


    as it doesn't allow us add a IServiceProvider parameter.



    We can custom a subclass of 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);
    }
    }


    and register the services by :



    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

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


    Test Case :



    Let's say you have a custom 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";
    }


    And the Current User has roles : Admin and HR:



    The result will be :



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


    A screenshot of testing with an action method :



    enter image description here






    share|improve this answer















    Suppose you have an custom RequireRoleViewAttribute:



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

    public string Role;

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



    How can I pass values that are based on the request to this resolver?




    You can have a IServiceProvider injected in your custom resolver :



    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>>();

    // ...
    }
    }


    thus we can get the HttpContext and User as we like. If you're using the Identity, you can also get the UserManager service and roles.



    and now we can follow @dbc's advice to control the 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;
    }


    The function GetIdentityUserRolesAsync here is helper method to retrieve roles using the current HttpContext and the UserManger service :



    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;
    }


    How to inject the IServiceProvider in details :



    The trick is all about how to configure the default MvcJwtOptions with an IServiceProvider.



    Don't configure the JsonOptions by :



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


    as it doesn't allow us add a IServiceProvider parameter.



    We can custom a subclass of 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);
    }
    }


    and register the services by :



    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

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


    Test Case :



    Let's say you have a custom 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";
    }


    And the Current User has roles : Admin and HR:



    The result will be :



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


    A screenshot of testing with an action method :



    enter image description here







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 14 '18 at 8:37

























    answered Nov 14 '18 at 8:23









    itminusitminus

    3,4961321




    3,4961321








    • 1





      Yaaaay, it works! I was having some issues with using UserManager.GetRolesAsync (service not registered, etc.), so I wound up falling back to getting user roles by parsing the claims, but otherwise it's pretty slick. I also set up some extension methods, so using it is just a matter of adding the RequireRoleView attribute and calling services.AddRoleBasedContractResolver() in Startup.ConfigureServices. Thanks to both of you :) I've accepted yours as the answer and I'll post my own with my tweaks in case anyone in the future finds it useful.

      – user1874135
      Nov 14 '18 at 15:53














    • 1





      Yaaaay, it works! I was having some issues with using UserManager.GetRolesAsync (service not registered, etc.), so I wound up falling back to getting user roles by parsing the claims, but otherwise it's pretty slick. I also set up some extension methods, so using it is just a matter of adding the RequireRoleView attribute and calling services.AddRoleBasedContractResolver() in Startup.ConfigureServices. Thanks to both of you :) I've accepted yours as the answer and I'll post my own with my tweaks in case anyone in the future finds it useful.

      – user1874135
      Nov 14 '18 at 15:53








    1




    1





    Yaaaay, it works! I was having some issues with using UserManager.GetRolesAsync (service not registered, etc.), so I wound up falling back to getting user roles by parsing the claims, but otherwise it's pretty slick. I also set up some extension methods, so using it is just a matter of adding the RequireRoleView attribute and calling services.AddRoleBasedContractResolver() in Startup.ConfigureServices. Thanks to both of you :) I've accepted yours as the answer and I'll post my own with my tweaks in case anyone in the future finds it useful.

    – user1874135
    Nov 14 '18 at 15:53





    Yaaaay, it works! I was having some issues with using UserManager.GetRolesAsync (service not registered, etc.), so I wound up falling back to getting user roles by parsing the claims, but otherwise it's pretty slick. I also set up some extension methods, so using it is just a matter of adding the RequireRoleView attribute and calling services.AddRoleBasedContractResolver() in Startup.ConfigureServices. Thanks to both of you :) I've accepted yours as the answer and I'll post my own with my tweaks in case anyone in the future finds it useful.

    – user1874135
    Nov 14 '18 at 15:53













    2














    Itminus's answer covers everything that's needed, but for anyone interested, I've extended it a little for easy reuse.



    First, in a class library



    My RequireRoleViewAttribute, which allows multiple roles (OR, not AND):



    [AttributeUsage(AttributeTargets.Property)]
    public class RequireRoleViewAttribute : Attribute
    {
    public List<String> AllowedRoles { get; set; }

    public RequireRoleViewAttribute(params String AllowedRoles) =>
    this.AllowedRoles = AllowedRoles.Select(ar => ar.ToLower()).ToList();
    }


    My resolver is almost identical to Itminus's, but CreateProperty is adjusted to



    IEnumerable<String> userRoles = this.GetIdentityUserRoles();

    property.ShouldSerialize = instance =>
    {
    // Check if every attribute instance has at least one role listed in the user's roles.
    return attrs.All(attr =>
    userRoles.Any(ur =>
    attr.AllowedRoles.Any(ar =>
    String.Equals(ar, ur, StringComparison.OrdinalIgnoreCase)))
    );
    };


    And GetIdentityUserRoles doesn't use UserManager



    private IEnumerable<String> GetIdentityUserRoles()
    {
    IHttpContextAccessor contextAccessor = this.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
    HttpContext context = contextAccessor.HttpContext;
    ClaimsPrincipal user = context.User;
    Object rolesCached = context.Items["__userRoles__"];
    if (rolesCached != null)
    {
    return (List<String>)rolesCached;
    }
    var roles = ((ClaimsIdentity)user.Identity).Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
    context.Items["__userRoles__"] = roles;
    return roles;
    }


    And I have an extensions class which contains:



    public static IServiceCollection AddRoleBasedContractResolver(this IServiceCollection services)
    {
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddTransient<IConfigureOptions<MvcJsonOptions>, RoleBasedContractResolverOptions>();
    return services;
    }


    Then in my API



    I reference that class library. In Startup.cs -> ConfigureServices, I call:



    public void ConfigureServices(IServiceCollection services)
    {
    ...
    services.AddRoleBasedContractResolver();
    ...
    }


    And my DTOs are tagged with the attribute:



    public class Diagnostics
    {
    public String VersionNumber { get; set; }

    [RequireRoleView("admin")]
    public Boolean ViewIfAdmin => true;

    [RequireRoleView("hr")]
    public Boolean ViewIfHr => true;

    [RequireRoleView("hr", "admin")]
    public Boolean ViewIfHrOrAdmin => true;
    }


    And the return value as an admin is:



    {
    "VersionNumber": "Debug",
    "ViewIfAdmin": true,
    "ViewIfHrOrAdmin": true
    }





    share|improve this answer




























      2














      Itminus's answer covers everything that's needed, but for anyone interested, I've extended it a little for easy reuse.



      First, in a class library



      My RequireRoleViewAttribute, which allows multiple roles (OR, not AND):



      [AttributeUsage(AttributeTargets.Property)]
      public class RequireRoleViewAttribute : Attribute
      {
      public List<String> AllowedRoles { get; set; }

      public RequireRoleViewAttribute(params String AllowedRoles) =>
      this.AllowedRoles = AllowedRoles.Select(ar => ar.ToLower()).ToList();
      }


      My resolver is almost identical to Itminus's, but CreateProperty is adjusted to



      IEnumerable<String> userRoles = this.GetIdentityUserRoles();

      property.ShouldSerialize = instance =>
      {
      // Check if every attribute instance has at least one role listed in the user's roles.
      return attrs.All(attr =>
      userRoles.Any(ur =>
      attr.AllowedRoles.Any(ar =>
      String.Equals(ar, ur, StringComparison.OrdinalIgnoreCase)))
      );
      };


      And GetIdentityUserRoles doesn't use UserManager



      private IEnumerable<String> GetIdentityUserRoles()
      {
      IHttpContextAccessor contextAccessor = this.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
      HttpContext context = contextAccessor.HttpContext;
      ClaimsPrincipal user = context.User;
      Object rolesCached = context.Items["__userRoles__"];
      if (rolesCached != null)
      {
      return (List<String>)rolesCached;
      }
      var roles = ((ClaimsIdentity)user.Identity).Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
      context.Items["__userRoles__"] = roles;
      return roles;
      }


      And I have an extensions class which contains:



      public static IServiceCollection AddRoleBasedContractResolver(this IServiceCollection services)
      {
      services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
      services.AddTransient<IConfigureOptions<MvcJsonOptions>, RoleBasedContractResolverOptions>();
      return services;
      }


      Then in my API



      I reference that class library. In Startup.cs -> ConfigureServices, I call:



      public void ConfigureServices(IServiceCollection services)
      {
      ...
      services.AddRoleBasedContractResolver();
      ...
      }


      And my DTOs are tagged with the attribute:



      public class Diagnostics
      {
      public String VersionNumber { get; set; }

      [RequireRoleView("admin")]
      public Boolean ViewIfAdmin => true;

      [RequireRoleView("hr")]
      public Boolean ViewIfHr => true;

      [RequireRoleView("hr", "admin")]
      public Boolean ViewIfHrOrAdmin => true;
      }


      And the return value as an admin is:



      {
      "VersionNumber": "Debug",
      "ViewIfAdmin": true,
      "ViewIfHrOrAdmin": true
      }





      share|improve this answer


























        2












        2








        2







        Itminus's answer covers everything that's needed, but for anyone interested, I've extended it a little for easy reuse.



        First, in a class library



        My RequireRoleViewAttribute, which allows multiple roles (OR, not AND):



        [AttributeUsage(AttributeTargets.Property)]
        public class RequireRoleViewAttribute : Attribute
        {
        public List<String> AllowedRoles { get; set; }

        public RequireRoleViewAttribute(params String AllowedRoles) =>
        this.AllowedRoles = AllowedRoles.Select(ar => ar.ToLower()).ToList();
        }


        My resolver is almost identical to Itminus's, but CreateProperty is adjusted to



        IEnumerable<String> userRoles = this.GetIdentityUserRoles();

        property.ShouldSerialize = instance =>
        {
        // Check if every attribute instance has at least one role listed in the user's roles.
        return attrs.All(attr =>
        userRoles.Any(ur =>
        attr.AllowedRoles.Any(ar =>
        String.Equals(ar, ur, StringComparison.OrdinalIgnoreCase)))
        );
        };


        And GetIdentityUserRoles doesn't use UserManager



        private IEnumerable<String> GetIdentityUserRoles()
        {
        IHttpContextAccessor contextAccessor = this.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
        HttpContext context = contextAccessor.HttpContext;
        ClaimsPrincipal user = context.User;
        Object rolesCached = context.Items["__userRoles__"];
        if (rolesCached != null)
        {
        return (List<String>)rolesCached;
        }
        var roles = ((ClaimsIdentity)user.Identity).Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
        context.Items["__userRoles__"] = roles;
        return roles;
        }


        And I have an extensions class which contains:



        public static IServiceCollection AddRoleBasedContractResolver(this IServiceCollection services)
        {
        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddTransient<IConfigureOptions<MvcJsonOptions>, RoleBasedContractResolverOptions>();
        return services;
        }


        Then in my API



        I reference that class library. In Startup.cs -> ConfigureServices, I call:



        public void ConfigureServices(IServiceCollection services)
        {
        ...
        services.AddRoleBasedContractResolver();
        ...
        }


        And my DTOs are tagged with the attribute:



        public class Diagnostics
        {
        public String VersionNumber { get; set; }

        [RequireRoleView("admin")]
        public Boolean ViewIfAdmin => true;

        [RequireRoleView("hr")]
        public Boolean ViewIfHr => true;

        [RequireRoleView("hr", "admin")]
        public Boolean ViewIfHrOrAdmin => true;
        }


        And the return value as an admin is:



        {
        "VersionNumber": "Debug",
        "ViewIfAdmin": true,
        "ViewIfHrOrAdmin": true
        }





        share|improve this answer













        Itminus's answer covers everything that's needed, but for anyone interested, I've extended it a little for easy reuse.



        First, in a class library



        My RequireRoleViewAttribute, which allows multiple roles (OR, not AND):



        [AttributeUsage(AttributeTargets.Property)]
        public class RequireRoleViewAttribute : Attribute
        {
        public List<String> AllowedRoles { get; set; }

        public RequireRoleViewAttribute(params String AllowedRoles) =>
        this.AllowedRoles = AllowedRoles.Select(ar => ar.ToLower()).ToList();
        }


        My resolver is almost identical to Itminus's, but CreateProperty is adjusted to



        IEnumerable<String> userRoles = this.GetIdentityUserRoles();

        property.ShouldSerialize = instance =>
        {
        // Check if every attribute instance has at least one role listed in the user's roles.
        return attrs.All(attr =>
        userRoles.Any(ur =>
        attr.AllowedRoles.Any(ar =>
        String.Equals(ar, ur, StringComparison.OrdinalIgnoreCase)))
        );
        };


        And GetIdentityUserRoles doesn't use UserManager



        private IEnumerable<String> GetIdentityUserRoles()
        {
        IHttpContextAccessor contextAccessor = this.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
        HttpContext context = contextAccessor.HttpContext;
        ClaimsPrincipal user = context.User;
        Object rolesCached = context.Items["__userRoles__"];
        if (rolesCached != null)
        {
        return (List<String>)rolesCached;
        }
        var roles = ((ClaimsIdentity)user.Identity).Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList();
        context.Items["__userRoles__"] = roles;
        return roles;
        }


        And I have an extensions class which contains:



        public static IServiceCollection AddRoleBasedContractResolver(this IServiceCollection services)
        {
        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddTransient<IConfigureOptions<MvcJsonOptions>, RoleBasedContractResolverOptions>();
        return services;
        }


        Then in my API



        I reference that class library. In Startup.cs -> ConfigureServices, I call:



        public void ConfigureServices(IServiceCollection services)
        {
        ...
        services.AddRoleBasedContractResolver();
        ...
        }


        And my DTOs are tagged with the attribute:



        public class Diagnostics
        {
        public String VersionNumber { get; set; }

        [RequireRoleView("admin")]
        public Boolean ViewIfAdmin => true;

        [RequireRoleView("hr")]
        public Boolean ViewIfHr => true;

        [RequireRoleView("hr", "admin")]
        public Boolean ViewIfHrOrAdmin => true;
        }


        And the return value as an admin is:



        {
        "VersionNumber": "Debug",
        "ViewIfAdmin": true,
        "ViewIfHrOrAdmin": true
        }






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 14 '18 at 16:12









        user1874135user1874135

        8712




        8712






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53288633%2fnet-core-api-custom-json-resolver-based-on-request-values%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Full-time equivalent

            Bicuculline

            さくらももこ