C# Possible to attach an Object to a method call without having it as a parameter?

Viewed 337

I am designing a program with AOP architecture(postsharp) that will intercept all method calls but I need a way to attach a class to every call. The problem is I don't want to have to pass the class explicitly in every single method call. So is there a way to attach a class to a method call in C#?

For example, In angular I can use a custom interceptor to attach anything I want to a header for every outgoing call. This saves down on repeating code. Is there anything like this in C#?

@Injectable()
export class CustomInterceptor implements HttpInterceptor {
  constructor() { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    request = request.clone({ withCredentials: true });        
    return next.handle(request);
  }
}

This is my interface in C#

    public class Wrapper: IMyInterface
    {       
        private IMyInterface_wrapped;

        public Wrapper(IMyInterface caller)
        {
            _wrapped = caller;
        }

        public FOO GetUserStuff(string userName)
        {
            return _wrapped.GetUserStuff(req);
        }
     }

   }

Is there a way that I can call the interface like this

          var wrapper = new Wrapper(new MyInterface());

           LoginRequest req = new LoginRequest <------ this needs to be attached to every single method call
            {
                ClientId = "ABCDEFG",
                ClientSecret = "123456"
            };

            wrapper.GetUserStuff("Username", req);   <------- My interface only takes one argument.
            wrapper.GetUserStuff("UserName").append(req) <----of course this doesn't work either

Is there a way that I can call the interface method and attach the object to it without actually implementing it in the interface?

4 Answers

Basically what you want is - whenever the wrapper.GetUserStuff method gets called, a LoginRequest object be available to the Wrapper class object.

But as you answered in the comment section, the value for ClientId and ClientSecret don't change. Then you can avoid the whole hassle of creating the LoginRequest object outside each time and passing it inside as a method parameter by simply creating the LoginRequest object inside the Wrapper class -

public class Wrapper : IMyInterface
{
    private IMyInterface _wrapped;
    private LoginRequest _req;

    public Wrapper(IMyInterface caller)
    {
        _wrapped = caller;
        _req = new LoginRequest { ClientId = "ABCDEFG", ClientSecret = "123456" };
    }

    public int GetUserStuff(string userName)
    {
        return _wrapped.GetUserStuff(_req);
    }
}

Usually, you will store the ClientId and ClientSecret values somewhere else (instead of hard coding them) and read them accordingly.

And, if you don't have access to the LoginRequest class from the Wrapper class (may be its on a separate layer/project that doesn't have the required assembly reference), then you can declare a class like ClientInfo and use it like -

public class ClientInfo
{
    public string UserName { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
}

public class Wrapper : IMyInterface
{
    private IMyInterface _wrapped;
    private ClientInfo _info;

    public Wrapper(IMyInterface caller)
    {
        _wrapped = caller;
        _info = new ClientInfo { ClientId = "ABCDEFG", ClientSecret = "123456" };
    }

    public int GetUserStuff(string userName)
    {
        _info.UserName = userName;
        return _wrapped.GetUserStuff(_info);
    }
}

then the caller can create the LoginRequest object from the ClientInfo passed to it.

To slightly alter @atiyar's approach you can use an accessor. This is a generic version of what is used in core for the HTTPAccessor. The AsyncLocal will be set once for the main thread and then propagate to any threads spawned.

public class GenericAccessor<T> where T : class
{
    private static AsyncLocal<Holder<T>> _current = new AsyncLocal<Holder<T>>();

    public T Value
    {
        get => _current.Value?.Context;
        set
        {
            var holder = _current.Value;
            if (holder != null)
            {
                // Clear current trapped in the AsyncLocals, as its done.
                holder.Context = null;
            }

            if (value != null)
            {
                // Use an object indirection to hold the in the AsyncLocal,
                // so it can be cleared in all ExecutionContexts when its cleared.
                _current.Value = new Holder<T> { Context = value };
            }
        }
    }

    private class Holder<T>
    {
        public T Context;
    }
}

With the implementation

public class ClientInfo
{
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
}

public class UserInfo: ClientInfo
{
    public UserInfo(ClientInfo clientInfo)
    {
         this.ClientId = clientInfo.ClientId;
         this.ClientSecret = clientInfo.ClientSecret;
    }

    public string UserName { get; set; }
}

public interface IClientInfoAccessor
{
    ClientInfo ClientInfo { get; set; }
}

public class ClientInfoAccessor : GenericAccessor<ClientInfo>, IClientInfoAccessor
{
    public ClientInfo ClientInfo{ get => Value; set => Value = value; }
}

public class Wrapper: IMyInterface
{
    private IMyInterface _wrapped;
    private IClientInfoAccessor _accessor;

    public Wrapper(IMyInterface caller, IClientInfoAccessor accessor)
    {
        _wrapped = caller;
        _accessor = accessor;
    }

    public int GetUserStuff(string userName)
    {
        var req = new UserInfo(_accessor.ClientInfo);
        req.UserName = userName;
        return _wrapped.GetUserStuff(req);
    }
}

All that you would need to do is to set the ClientInfo in middleware for each operation and you can use the accessor anywhere even in singletons.

Via DI container you can easily inject the IOption<> interface to the class constructor with:

public class Wrapper: IMyInterface
{       
    private IMyInterface_wrapped;
    private MySettings _mySettings;

    public Wrapper(IMyInterface caller, IOptions<MySettings> mySettings)
    {
        _wrapped = caller;
        _mySettings = mySettings.Value;
    }

    private LoginRequest GetLoginRequest()
    {
        return new LoginRequest
        {
            ClientId = _mySettings.ClientId,
            ClientSecret = _mySettings.ClientSecret
        };
    }

    public FOO GetUserStuff(string userName)
    {
        return _wrapped.GetUserStuff(GetLoginRequest());
    }
 }

You can make it a static class and call a static method whenever it's needed. Or if you want to make it like in Angular you can add it to the pipeline (Startup Configure method):

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        LoginRequest req = new LoginRequest
        {
            ClientId = "ABCDEFG",
            ClientSecret = "123456"
        };
        context.Response.Headers["ClientId"] = "ABCDEFG";
        await next();
    });
}
Related