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

4

6

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?

Undulatory answered 3/3, 2020 at 18:35 Comment(13)
Sort of seems like this might be something dependency injection patterns could solve, if the Wrapper type classes also accepted the required object instance as a ctor argument.Luedtke
I want to ask about your interface. It seems a bit weird that Wrapper implements MyInterface and also takes an MyInterface type parameter in its constructor. Is this the way you intended to write this class ? Also, is it possible to give req object as parameter to your wrapper and call the other methods as they are ? MyInterface myInterface= new Wrapper(req); wrapper.GetUserStuff("Username");Palestrina
@Palestrina reason being is since this is AOP then postsharp will intercept the method call. I just need to have it exposed so that postsharp can see the object. Currently postsharp can only see the username as a parameter.Undulatory
@TerranceJackson So I assume postsharp can only see username param because of MyInterface having GetUserStuff(string userName). So does MyInterface have "append" method? I'm shooting blind here but maybe you can add this to your interface, have Wrapper class implement it then you can call wrapper.append(req) and wrapper.GetUserStuff("Username", req); Sorry if this is completely off the mark.Palestrina
So, if I understood correctly, every time the GetUserStuff method gets called, you want the LoginRequest object to be attached as a parameter to that GetUserStuff method?Lenorelenox
@Lenorelenox yes that is correct this way when post sharp intercepts the call it will see the object. The only way for it to see the object currently is if i have the object as a second parameter. I don't want to have to add this parameter manually on every single method. Its tedious and looks awful in code.Undulatory
C# is a statically typed language, and I don't think you have the option to dynamically add a parameter to method, because then the type would not have a specific definition. But there might be other ways available to do what you are trying. Is it possible to change the parameter type of your GetUserStuff method?Lenorelenox
@Lenorelenox Yes I can change the parameter type however I have well over 1000 methods so my goal was to not have to repeat myClass.Foo(req) 1000 times. So if there is any way possible I am openUndulatory
Are you making call to different WebService APIs? I mean, do the ClientId and ClientSecret values change for different GetUserStuff call?Lenorelenox
No they dont change. They are necessary to get a refresh token if it has expiredUndulatory
@TerranceJackson I have written incorrectly in my previous post, It would be "wrapper.append(req) and wrapper.GetUserStuff("Username"); ". But I'm guessing It is not possible to modify your Interface ?Palestrina
Could you please include more detail about the specific problem you're trying to solve? Do you want to attach an HTTP header to all outgoing requests, similar to the angular example, or are you interested in a more general interception that works for any C# method?Fortuitous
This is not really something PostSharp can do currently, as it would really require to add an argument to a method (or method overload) and transforming every call site. It's possible, but would be quite non-trivial to implement. I think AsyncLocal<T> answer by @ICodeGorilla is what you need since the value stored in AsyncLocal would be available as long as you flow ExecutionContext correctly (which is not a concern if you are not using some custom threading).Connatural
L
3

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.

Lenorelenox answered 30/9, 2020 at 21:20 Comment(0)
L
3

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.

Leanaleanard answered 5/10, 2020 at 20:8 Comment(0)
D
2

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());
    }
 }
Dickinson answered 30/9, 2020 at 21:9 Comment(0)
T
0

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();
    });
}
Tachyphylaxis answered 30/9, 2020 at 19:36 Comment(6)
If I'm using a console application or a dll how would this work?Undulatory
It depends, what are you exactly do inside _wrapped.GetUserStuff(req)? are you attaching the clientId and the client Secret as an header? If so you can use context.Request.Header and modify it here.Dickinson
no its not a header its an client api to webservice api callUndulatory
Oh, maybe it is possible by de-serializing the request and attach the property and then serializing it again, and you need to point out this will occur for all (!) of the requests of the application, if its ok fine, if not you should be using here MapWhen in order for it to apply only for the given interfaceDickinson
If your console app or dll will go over http then that middleware will filter all the requestsTachyphylaxis
where you can easily apply some custom logicTachyphylaxis

© 2022 - 2024 — McMap. All rights reserved.