Home > Net >  C# How to get the method name of the calling method if async
C# How to get the method name of the calling method if async

Time:10-14

I have a solution that is comprised of two projects, a Blazor server website and a Class Library. In the class library, I created a class to allow me to add custom attributes to functions in other classes in the library. This allows me to decorate a function with roles that can access the function. I then have a class that gets called to pull those tags and checks the database for consensus. This works fine, when the calling function is a regular function. But, if it is an async function, the stack frame I am looking at returns a value of "MoveNext" instead of the name of the method. Is there a way to get the method name of the calling method if that method is async? Here is my pseudo code:

Class to add custom attributes:

namespace DataAccessLibrary
{
    [AttributeUsage(AttributeTargets.Method |AttributeTargets.ReturnValue)]
    public class MELAdvanceRoles: Attribute
    {
        public string[] _Roles { get; set; }
        public MELAdvanceRoles(string[] roles)
        {
            _Roles = roles;           
        }
    }
}

Example function that uses custom attributes:

namespace DataAccessLibrary
{
    public class ProgramData: IProgramData
    {
        private readonly ISqlDataAccess _db;      
        private readonly IUserRights _userRights;

        public ProgramData(ISqlDataAccess db, IUserRights userRights)
        {
            _db = db;            
            _userRights = userRights;
        }   
        [MELAdvanceRoles(new string[] { "MANAGER", "EMPLOYEE", "READ_ONLY" })]
        public async Task<int> UpdateProgram(ProgramModel program)
        {   
            if (_userRights.CanUserEditProgram(program.id))
                //protected stuff
            }
            else
            {
                throw new Exception("403 Forbidden");
            }                
        }
}

CanUserEditProgram Function in _userRights

public bool CanUserEditProgram(int program_id)
{         
    //get roles in the custom attribute  
    string[] allowed_roles = GetMethodRoles();
    // use that list for database stuff
}

public string[] GetMethodRoles()
{
    string[] roles = { };
    StackTrace stackTrace = new StackTrace();
    //it seems that functions that are async are not on the second frame
    string methodName = stackTrace.GetFrame(2).GetMethod().Name;
    //mehtodname returns 'MoveNext' if the calling method is async
    MethodInfo mInfo = stackTrace.GetFrame(2).GetMethod().ReflectedType.GetMethod(methodName);
    //which means mInfo returns null
    bool isDef = Attribute.IsDefined(mInfo, typeof(MELAdvanceRoles));
    if (isDef)
    {
        MELAdvanceRoles obsAttr = (MELAdvanceRoles)Attribute.GetCustomAttribute(mInfo, typeof(MELAdvanceRoles));
        if (obsAttr != null)
        {
            roles = obsAttr._Roles;
        }
    }
    return roles;
}

Thanks

CodePudding user response:

Having initially thought it was a crazy idea, I was intrigued enough to explore how I would approach the problem if I had to. Here's a first demo hash up of a possible approach.

Define an Interface that all classes with Authorization need to implement.

using System;
using System.Collections.Generic;

namespace StackOverflow.Answers
{
    public interface IClassAuthorization
    {
        public List<string> UserRoles { get; set; }

        public Boolean UserHasRole(string role)
        {
            if (UserRoles is null)
                throw new ApplicationException("No UserRoles defined for object.  If you are using Class Authorization you must set the UserRoles");
            return UserRoles.Any(item => item.Equals(role, StringComparison.CurrentCultureIgnoreCase));
        }
    }
}

Define a result class. Not strictly required, you could return nulls for not implemented,... This just gives you a method to return both the result and data without resorting to outs. You can see all three implemented in the new WeatherForecastService.

namespace StackOverflow.Answers
{
    public class ClassAuthorizationResult
    {
        public ClassAuthorizationResultType Status { get; private set; } = ClassAuthorizationResultType.NotDefined;

        public object Data { get; set; } = null;

        public static ClassAuthorizationResult Success(object data)
            => new ClassAuthorizationResult { Status=ClassAuthorizationResultType.Success, Data = data };

        public static ClassAuthorizationResult Failure()
            => new ClassAuthorizationResult { Status = ClassAuthorizationResultType.Failure, Data = null };

        public static ClassAuthorizationResult NotDefined()
            => new ClassAuthorizationResult { Status = ClassAuthorizationResultType.NotDefined, Data = null };

        public enum ClassAuthorizationResultType
        {
            NotDefined,
            Success,
            Failure
        }
    }
}

Here's the new WeatherForecastService:

using StackOverflow.Answers.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace StackOverflow.Answers
{
    public class AuthorizedWeatherForecastService : IClassAuthorization
    {
        public List<string> UserRoles { get; set; } = null;

        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private IClassAuthorization _interface => this;

        private List<WeatherForecast> records;

        public AuthorizedWeatherForecastService()
        {
            records = GetWeatherForecasts;
        }

        //  Null return
        public Task<List<WeatherForecast>> ForecastsAsync()
            => Task.FromResult(_interface.UserHasRole("User")
                ? this.records
                : null);
        
        // ClassAuthorizationResult return
        public Task<ClassAuthorizationResult> GetForecastsAsync()
            => Task.FromResult(_interface.UserHasRole("User")
                ? ClassAuthorizationResult.Success(this.records)
                : ClassAuthorizationResult.Failure());

        // Out return
        public Task<bool> GetForecastsAsync(out List<WeatherForecast> list)
        {
            var ok = _interface.UserHasRole("User");
            list = ok ? this.records : new List<WeatherForecast>();
            return Task.FromResult(ok);
        }

        private List<WeatherForecast> GetWeatherForecasts
        {
            get
            {
                var rng = new Random();
                return Enumerable.Range(1, 5).Select(index => new WeatherForecast
                {
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = rng.Next(-20, 55),
                    Summary = Summaries[rng.Next(Summaries.Length)]
                }).ToList();
            }
        }
    }
}

Then in FetchData

    [Inject] AuthorizedWeatherForecastService ForecastService { get; set; }

    protected override async Task OnInitializedAsync()
    {
        ForecastService.UserRoles = new List<string> { "User" };
        var result = await ForecastService.GetForecastsAsync();
        if (result.Status == ClassAuthorizationResult.ClassAuthorizationResultType.Success)
            forecasts = (List<WeatherForecast>)result.Data;
    }

You can get the user roles from AuthenticationState. If your classes are services, inject the IAuthenticationStateProvider directly and register for the AuthenticationStateChanged event to update the roles when the user changes.

  • Related