3

I'm implementing security features for my .NET Core application and I'm finding myself repeating the same conditional logic over and over. Is there a way where I can generalize this in one place and have it applied to the segments I want? I recall using delegates or Func for this type of thing but I'm not quite sure... Any ideas?

Below is the code I'm trying to write once and apply in multiple places.

var currentUser = _httpContext.HttpContext.Session.GetCurrentUser<SessionContext>();
if(currentUser.Roles.Any())
{
    // ex query here. This could be any piece of code
    var q = from u in _dbContext.Users
            join d in _dbContext.Users on u.DoctorId equals d.Id into ud
            from docU in ud.DefaultIfEmpty()
            select new
            {
                User = u,
                Doctor = docU
            };

    if(!currentUser.Roles.Contains("Administrator"))
    {
        if(currentUser.Roles.Contains("Doctor"))
        {
            //do something here
           //ex.
           q = q.Where(x => (x.Doctor != null ? x.Doctor.Id == currentUserId : false));
        }
        else if (currentUser.Roles.Contains("Patient"))
        {
            //do something else here
            //ex.
            q = q.Where(x => x.User.Id == currentUserId);
        }
    }
}
else
    throw new Exception("No roles applied to logged in user");
roonz11
  • 795
  • 1
  • 13
  • 24
  • Are you saying you're having this particular block of code in multiple places, or are you fretting over the multiple if-statements in this single block of code? – Lasse V. Karlsen Apr 28 '20 at 19:10
  • I am having this block of code in multiple places. I'm not too worried about the multiple if-statements. – roonz11 Apr 28 '20 at 19:12
  • it is hard to know what could change without having an idea of what you are doing in your If statements. If this was for an API on the controller methods you could add an Authorize attribute like ```[Authorize(Roles = "Doctor", "Patient")]``` – ejwill Apr 28 '20 at 19:22
  • I'm writing linq queries inside my if-statements. But I guess my objective is to write anything inside those if statements but maintain the outer logic. I've updated my example. Cheers! – roonz11 Apr 28 '20 at 19:32
  • 1
    I think you should take a look at Specification pattern in c# – Vadim Bondaruk Apr 28 '20 at 19:45

2 Answers2

0

You could create a new service.

public class MyHttpContextService : IMyHttpContextService
{
    IHttpContextAccessor _httpContext;

    public MyHttpContextService(IHttpContextAccessor httpContext)
    {
        _httpContext = httpContext;
    }

    public string CheckUserRoles()
    {
        try
        {
            var currentUser = _httpContext?.HttpContext?.Session?.GetCurrentUser<SessionContext>();
            if (currentUser != null)
            {
                if(currentUser.Roles.Any())
                {
                    if(!currentUser.Roles.Contains("Administrator"))
                    {
                        if(currentUser.Roles.Contains("Doctor"))
                        {
                            //do something here
                        }
                        else if (currentUser.Roles.Contains("Patient"))
                        {
                            //do something else here
                        }
                    }
                }
            }
            else
            {
                // if currentUser == null
            }
        }
        catch (Exception ex)
        {
            // exception handling
        }

    }
}

Notice the line

var currentUser = _httpContext.HttpContext.Session.GetCurrentUser<SessionContext>();

is replaced by

var currentUser = _httpContext?.HttpContext?.Session?.GetCurrentUser<SessionContext>();

Create appropriate interface.

public interface IMyHttpContextService
{
    string CheckUserRoles();
}

In this example string is return type, but it does not need to be.

Finally, register this service using line

services.AddScoped<IMyHttpContextService, MyHttpContextService>();

where services is

IServiceCollection services

Instead of AddScoped, you could use AddTransient, or AddSingleton. More about objects' lifetime and dependency injection. These three keywords determine the lifetime of an object, or in this case, of service.

Registration is starting in Startup.cs (but to be honest, everything is starting in Startup.cs, hence the name). More about Startup.cs. In other words, in Startup.cs call method

public void ConfigureServices(IServiceCollection services)

which is called by runtime.

Inside method ConfigureServices call another one, for instance MapInterfaces, for interface mapping and pass it services. Method MapInterfaces will be a static method inside ServiceExtensions.cs.

public static void MapInterfaces(IServiceCollection services)
{
    services.AddScoped<IMyHttpContextService, MyHttpContextService>();
}

Even better is to create an extension method More about extension methods.

ServiceExtensions.cs is a static class, which is a condition for creating extension method. Extension method also needs to be static. This would be the methods signature

static void MapInterfaces(this IServiceCollection services)

Of course, don't forget the access modifier (at least the same visibility as the ServiceExtensions.cs class). Notice this keyword.

Extension method MapInterfaces from ServiceExtensions.cs is then called inside ConfigureServices method from Startup.cs like this

services.MapInterfaces();

In the end, whenever you need method CheckUserRoles, call it like this

_myHttpContextService.CheckUserRoles();

EDIT: You've changed the implementation of method, but that doesn't change the way you could do the rest of solution.

Marko Radivojević
  • 458
  • 2
  • 7
  • 11
0

Here's some code written in Swift. I'm using functional oriented programming, with dictionaries


struct User {
    var Roles: Set<String> = ["Doctor"]
}

func channel(user: User, _ roles: [String:() -> ()]) {
    for i in roles {
        if user.Roles.contains(i.key) { i.value() }
    }
}

let currentUser = User()
channel(user: currentUser,
       [
        "Doctor": {
        // Code for doctor
        },

        "Admin": {
        // Code for admin
        },

        "Blah": {
        // Code for blah
        },

        // You can even add more
    ]
)

You can enum Create an Enum
Why an Enum?
You can easily make typos with regular Strings
With an Enum, if you make a typo, Swift gives you an error. Super Helpful!

enum UserRolls { case doctor, admin, patient, other(String) }
extension UserRolls: Hashable {}

struct User {
    var Roles: Set<UserRolls> = [.doctor]
}

func channel(user: User, _ roles: [UserRolls:() -> ()]) {
    for i in roles {
        if user.Roles.contains(i.key) { i.value() }
    }
}

let currentUser = User()
channel(user: currentUser,
       [
        .doctor: {
        // Code for doctor
        },

        .admin: {
        // Code for admin
        },

        .other("Blah"): {
        // Code for blah
        },

        // You can even add more
    ]
)
0-1
  • 702
  • 1
  • 10
  • 30