0

これが私の契約です:

    [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は列挙型です。

リクエストでわかるように、2つのメッセージヘッダーと、SOAPメッセージの一部として逆シリアル化されない1つの内部オブジェクトがあります。これは、サービス実装で処理されるに、このオブジェクトをリクエストに挿入したいためです。すべての単一の操作の実装はこのオブジェクトを使用します。各操作でデータレイヤーを2回呼び出す必要はありません。

(属性を介して)WCF拡張性を使用して、次の2つのタスクを実行したいと思います。

  1. 要求しているユーザーを認証します。
  2. すべての操作で使用するために、着信要求クラスの「User」に複合/複合ビジネス・オブジェクトを移入します。

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

1 に答える 1

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
}

次に、呼び出し元は次のようにサービス契約に適用されます。

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

    [OperationContract]
    [AuthenticateUser]
    OperationResponse2 Operation2(OperationRequest2 req);
}
于 2012-10-30T11:49:28.493 に答える