WCF:“取消”操作呼叫依赖于请求消息合同

时间:2012-10-29 11:57:43

标签: c# .net wcf messagecontract wcf-extensions

这是我的合同:

    [ServiceContract]
public interface IMyServiceContract {
    [OperationContract]
    OperationResponse1 Operation1(OperationRequest1 req);

    [OperationContract]
    OperationResponse2 Operation2(OperationRequest2 req);
    }

OperationRequest1和OperationRequest2都从BaseOperationRequest继承,后者保存所有进入服务的请求的凭据信息:

    [MessageContract]
public abstract class BaseOperationRequest {
    [MessageHeader(MustUnderstand = true)]
    public Guid Token { get; set; }

    [MessageHeader(MustUnderstand = true)]
    public string Password { get; set; }

    private User User { get; set; }
}

OperationResponse1和OperationResponse2也都从基类继承:

    [MessageContract]
public abstract class BaseOperationResponse {
    [MessageHeader(MustUnderstand = true)]
    public bool Success { get; set; }

    [MessageHeader(MustUnderstand = true)]
    public ServiceErrors ErrorCode { get; set; }
}

ErrorCode是一个枚举。

正如您在请求中看到的那样,我有两个消息头,以及一个内部对象,它不会作为SOAP消息的一部分进行反序列化。原因是我希望在我的服务实现处理之前将此对象注入请求。每个操作实现都将使用此对象,我不希望每个操作都对我的数据层进行两次调用。

我想使用WCF extensiblity(通过属性)来执行两个任务:

  1. 验证请求用户。
  2. 使用复杂/复合业务对象在传入请求类上填充“User”,以便在每个操作中使用。
  3. 我已经调查了IOperationInvoker,IDispatchMessageFormatter和IDispatchMessageInspector,但我没有发现它们中的任何一个都非常合适。

    仅供参考,这是我的服务的原始示例实现,没有任何花哨的WCF可扩展性(或我的存储库/数据层调用):

    public class MyService: IMyServiceContract {
        public OperationResponse1 Operation1(OperationRequest1 req) {
            if(req.Token == new Guid("GUID VALUE") && req.Password == "password") {
                // Perform some actions....
    
                return new OperationResponse1 {
                    Success = true
                }
            } else {
                return new OperationResponse1 {
                    Success = false,
                    Error = "You are not authenticated"
                }
            }
        }
        public OperationResponse2 Operation2(OperationRequest2 req) {
            if(req.Token == new Guid("GUID VALUE") && req.Password == "password") {
                // Perform some actions....
    
                return new OperationResponse2 {
                    Success = true
                }
            } else {
                return new OperationResponse2 {
                    Success = false,
                    Error = "You are not authenticated"
                }
            }
        }
    }
    

    IOperationInvoker似乎是最合适的扩展点,但我无法弄清楚如何“取消”操作并覆盖对客户端的响应。这是我到达的地方:

    /// <summary>
    /// Provides an invoker that can be used to authenticate a BaseOperationRequest message.
    /// </summary>
    public class UserAuthenticationInvoker : IOperationInvoker {
        /// <summary>
        /// The original operation invoker.
        /// </summary>
        private IOperationInvoker _originalInvoker;
    
        /// <summary>
        /// The injected User service, for authentication.
        /// </summary>
        [Inject]
        public IUserService UserService { get; set; }
    
    
        public UserAuthenticationInvoker(IOperationInvoker originalInvoker) {
            _originalInvoker = originalInvoker;
        }
    
        #region Implementation of IOperationInvoker {
    
        public object[] AllocateInputs() {
            return _originalInvoker.AllocateInputs();
        }
    
        public object Invoke(object instance, object[] inputs, out object[] outputs) {
            // Validate base request
            if(!(inputs[0] is BaseOperationRequest)) {
                throw new ArgumentException("The request object must inherit from BaseOperationRequest in order for User authentication to take place.");
            }
    
            // Get BaseOperationRequest
            var req = (BaseOperationRequest)inputs[0];
    
            // Authenticate the User
            var authResult = UserService.AuthenticateUser(new AuthenticateUserRequest {
                Token = req.Token,
                Password = req.Password
            });
            if(authResult.Success) {
                // This is where I get stuck - do I need to modify "outputs"? If so, how do I tell the invoker that I want a particular response to be returned, and to cancel the rest of the operation?
                return _originalInvoker.Invoke(instance, inputs, outputs);
            }
            return _originalInvoker.Invoke(instance, inputs, out outputs);
        }
    
        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state) {
            throw new NotImplementedException("The operation cannot be invoked asynchronously.");
        }
    
        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result) {
            throw new NotImplementedException("The operation cannot be invoked asynchronously.");
        }
    
        public bool IsSynchronous {
            get { return true; }
        }
    
        #endregion
    }
    

1 个答案:

答案 0 :(得分:0)

自己回答这个问题。通过实现一些动态类型结束修改_originalInvoker的“输入”,并将注入移动到属性,以便我可以模拟/单元测试调用者。这是我的代码:

<强>属性:

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class AuthenticateUserAttribute : Attribute, IOperationBehavior {
        #region Implementation of IOperationBehavior

        public void Validate(OperationDescription operationDescription) {
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) {
            // Manual injection
            var userService = NinjectWebCommon.Kernel.Get<IUserService>();

            // Assign the custom authentication invoker, passing in the original operation invoker
            dispatchOperation.Invoker = new UserAuthenticationInvoker(dispatchOperation.Invoker, operationDescription.SyncMethod.ReturnType, userService);
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) {
            throw new NotImplementedException("This behaviour cannot be applied to a server operation.");
        }

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) {
        }

        #endregion
    }

<强> IOperationInvoker:

public class UserAuthenticationInvoker : IOperationInvoker {
    /// <summary>
    ///     The original operation invoker.
    /// </summary>
    private readonly IOperationInvoker _originalInvoker;

    private readonly Type _responseType;

    private readonly IUserService _userService;

    public SupplierAuthenticationInvoker(IOperationInvoker originalInvoker, Type responseType, IUserService userService) {
        _originalInvoker = originalInvoker;
        _responseType = responseType;
        _userService = userService;
    }

    #region Implementation of IOperationInvoker {

    public object[] AllocateInputs() {
        return _originalInvoker.AllocateInputs();
    }

    public object Invoke(object instance, object[] inputs, out object[] outputs) {
        // Validate base objects request
        if(!(inputs[0] is BaseOperationRequest)) throw new ArgumentException("The request object must inherit from BaseOperationRequest in order for user authentication to take place.");

        dynamic response = Activator.CreateInstance(_responseType);
        if(!(response is BaseOperationResponse)) throw new InvalidOperationException("The response object must inherit from BaseOperationResponsein order for user authentication to take place.");

        var req = (BaseOperationRequest)inputs[0];

        // Authenticate the user
        var authResult = _userService.AuthenticateUser(new AuthenticateUserRequest {
            Token = req.Token,
            Password = req.Password
        });

        if(!authResult.Success) {
            // Authentication failed, return reflected response object.
            outputs = new object[0];
            // Set response headers
            response.Success = false;
            response.ErrorCode = ServiceErrors.AuthErrorInvalidTokenOrPassword;

            return response;
        }
        // Authentication success, set the user and call the original method
        dynamic modifiedInput = inputs;
        modifiedInput[0].User = authResult.User;

        var invoked = _originalInvoker.Invoke(instance, modifiedInput, out outputs);

        return invoked;
    }

    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state) {
        throw new NotImplementedException("The operation cannot be invoked asynchronously.");
    }

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result) {
        throw new NotImplementedException("The operation cannot be invoked asynchronously.");
    }

    public bool IsSynchronous {
        get { return true; }
    }

    #endregion
}

然后将Invoker应用于服务合同,如下所示:

[ServiceContract]
public interface IMyServiceContract {
    [OperationContract]
    [AuthenticateUser]
    OperationResponse1 Operation1(OperationRequest1 req);

    [OperationContract]
    [AuthenticateUser]
    OperationResponse2 Operation2(OperationRequest2 req);
}