【问题标题】:C# Possible to attach an Object to a method call without having it as a parameter?C# 可以将对象附加到方法调用而不将其作为参数吗?
【发布时间】:2020-10-06 19:42:10
【问题描述】:

我正在设计一个具有 AOP 架构(postsharp)的程序,它将拦截所有方法调用,但我需要一种将类附加到每个调用的方法。问题是我不想在每个方法调用中都显式地传递类。那么有没有办法将一个类附加到 C# 中的方法调用上?

例如,在 Angular 中,我可以使用自定义拦截器将我想要的任何内容附加到每个传出呼叫的标头。这节省了重复代码。 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);
  }
}

这是我的 C# 界面

    public class Wrapper: IMyInterface
    {       
        private IMyInterface_wrapped;

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

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

   }

有没有办法可以像这样调用接口

          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

有没有一种方法可以调用接口方法并将对象附加到它而无需在接口中实际实现它?

【问题讨论】:

  • 如果 Wrapper 类型类也接受所需的对象实例作为 ctor 参数,这似乎是依赖注入模式可以解决的问题。
  • 我想问一下你的界面。 Wrapper 实现了 MyInterface 并且还在其构造函数中采用了 MyInterface 类型参数,这似乎有点奇怪。这是你打算写这个类的方式吗?此外,是否可以将 req 对象作为参数提供给包装器并按原样调用其他方法? MyInterface myInterface= new Wrapper(req); wrapper.GetUserStuff("用户名");
  • @AntiqTech 的原因是因为这是 AOP,所以 postsharp 将拦截方法调用。我只需要将它暴露出来,以便 postsharp 可以看到该对象。目前 postsharp 只能将用户名视为参数。
  • @TerranceJackson 所以我认为 postsharp 只能看到用户名参数,因为 MyInterface 有 GetUserStuff(string userName)。那么 MyInterface 有“append”方法吗?我在这里盲目射击,但也许你可以将它添加到你的界面,让 Wrapper 类实现它,然后你可以调用 wrapper.append(req) 和 wrapper.GetUserStuff("Username", req);抱歉,如果这完全不合时宜。
  • 那么,如果我理解正确的话,每次调用GetUserStuff 方法时,您希望LoginRequest 对象作为参数附加到GetUserStuff 方法吗?

标签: c# aop postsharp


【解决方案1】:

基本上,您想要的是 - 每当调用 wrapper.GetUserStuff 方法时,LoginRequest 对象可用于 Wrapper 类对象。

但正如您在评论部分回答的那样,ClientIdClientSecret 的值不会改变。然后,您可以避免每次在外部创建LoginRequest 对象并将其作为方法参数传入内部的麻烦,只需在Wrapper内部创建LoginRequest 对象-

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

通常,您会将ClientIdClientSecret 值存储在其他地方(而不是硬编码)并相应地读取它们。

而且,如果您无法从 Wrapper 类访问 LoginRequest 类(可能是它位于没有所需程序集引用的单独层/项目上),那么您可以声明一个像 ClientInfo 这样的类并像使用它一样 -

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

那么caller 可以从传递给它的ClientInfo 创建LoginRequest 对象。

【讨论】:

    【解决方案2】:

    要稍微改变@atiyar 的方法,您可以使用访问器。这是 HTTPAccessor 核心中使用的通用版本。 AsyncLocal 将为主线程设置一次,然后传播到任何产生的线程。

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

    随着实施

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

    您需要做的就是在中间件中为每个操作设置 ClientInfo,您可以在任何地方使用访问器,即使在单例中也是如此。

    【讨论】:

      【解决方案3】:

      通过 DI 容器,您可以轻松地将 IOption&lt;&gt; 接口注入到类构造函数中:

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

      【讨论】:

        【解决方案4】:

        您可以将其设为静态类并在需要时调用静态方法。或者如果你想让它像 Angular 一样,你可以将它添加到管道中(启动配置方法):

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

        【讨论】:

        • 如果我使用控制台应用程序或 dll,这将如何工作?
        • 这取决于,你在_wrapped.GetUserStuff(req)里面到底在做什么?您是否将 clientId 和 client Secret 作为标头附加?如果是这样,您可以使用context.Request.Header 并在此处进行修改。
        • 不,它不是标头,它是客户端 api 到 web 服务 api 调用
        • 哦,也许可以通过反序列化请求并附加属性然后再次序列化它,您需要指出这将发生在应用程序的所有(!)请求中,如果没问题,如果不是,你应该在这里使用MapWhen,以便它只适用于给定的接口
        • 如果您的控制台应用程序或 dll 将通过 http,那么该中间件将过滤所有请求
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多