WCF: операция "Отмена" Вызов зависит от контракта с сообщением запроса
Вот мой контракт:
[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 (через атрибут) для выполнения двух задач:
- Аутентифицировать запрашивающего пользователя.
- Заполните "Пользователь" в классе входящего запроса сложным / составным бизнес-объектом для использования в каждой операции.
Я исследовал 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 ответ
Ответил на это сам. Закончилось изменение "входных данных" в _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);
}