Когда и где установить пользовательский IOperationInvoker?

Я пытаюсь расширить WCF, чтобы иметь веб-службу RESTful, в которой для каждой операции я выполняю проверку заголовка HTTP-авторизации, значение которого я использую для вызова метода Login().

После того, как вход выполнен, я хочу вызвать соответствующий метод операции, проверяющий, выдано ли исключение безопасности, и в этом случае я отвечу специальным сообщением "Отказано в доступе", используя соответствующий код состояния HTTP.

Имея это в виду, я подумал, что реализация IEndpointBehavior, которая применяет реализацию IOperationInvoker к каждой операции (установка свойства DispatchOperation.Invoker), будет хорошей идеей.

Я решил реализовать IOperationInvoker, используя шаблон проектирования Decorator. Моей реализации понадобится другой IOperationInvoker в своем конструкторе, которому будут делегированы вызовы метода.

Это моя IOperationInvokerImplementation:

    public class BookSmarTkOperationInvoker : IOperationInvoker{

    private readonly IOperationInvoker invoker;

    public BookSmarTkOperationInvoker(IOperationInvoker decoratee)
    {
        this.invoker = decoratee;
    }

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

    public object Invoke(object instance, object[] inputs, out object[] outputs)
    {
        BeforeOperation(); // Where there's code to perform the login using WebOperationContext.Current
        object o = null;
        try
        {
            o = this.invoker.Invoke(instance, inputs, out outputs);
        }
        catch (Exception exception)
        {
            outputs = null;
            return AfterFailedOperation(exception); // Return a custom access denied response
        }

        return o;
    }

    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
    {
        throw new Exception("The operation invoker is not asynchronous.");
    }

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
    {
        throw new Exception("The operation invoker is not asynchronous.");
    }

    public bool IsSynchronous
    {
        get
        {
            return false;
        }
    }
}

Я решил реализовать IEndpointBehavior, расширив поведение, в котором я уже нуждался (WebHttpBehavior), так что я использую только один beavior. Вот код, который я написал:

public class BookSmarTkEndpointBehavior : WebHttpBehavior
{
    public override void Validate(ServiceEndpoint endpoint)
    {
        base.Validate(endpoint);
    }

    public override void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
        base.AddBindingParameters(endpoint, bindingParameters);
    }

    public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        base.ApplyDispatchBehavior(endpoint, endpointDispatcher);

        foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
        {
            IOperationInvoker defaultInvoker = operation.Invoker;
            IOperationInvoker decoratorInvoker = new BookSmarTkOperationInvoker(defaultInvoker);
            operation.Invoker = decoratorInvoker;

            Console.Write("Before: " + ((object)defaultInvoker ?? "null"));
            Console.WriteLine(" After: " + operation.Invoker);
        }
    }

    public override void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        base.ApplyClientBehavior(endpoint, clientRuntime);
        throw new Exception("The BookSmarTkEndointBehavior cannot be used in client endpoints.");
    }
}

Теперь вот проблема:

  1. В IOperationInvoker вызывается только конструктор, ни один из других методов - нет.
  2. IOperationInvoker (тот, который передается в конструктор декоратора) имеет значение null.

Я предполагаю, что, возможно, какой-то другой код из какого-то другого поведения устанавливает другой IOperationInvoker в настройке OperationDispatcher.Invoker впоследствии. Таким образом, переопределил мой. Это ясно объяснило бы мою ситуацию.

Что происходит и что мне делать?

Мой сервис самостоятельно.

В случае, если вам нужно это увидеть, вот моя конфигурация в файле app.config в system.serviceModel.

<services>
  <service name="BookSmarTk.Web.Service.BookSmarTkService">
    <host>
      <baseAddresses>
        <add baseAddress="http://localhost:8080/service"/>
      </baseAddresses>
    </host>
    <endpoint  
      address=""
      behaviorConfiguration="BookSmaTkEndpointBehavior"
      binding="webHttpBinding" 
      bindingConfiguration="BookSmarTkBinding"
      contract="BookSmarTk.Web.Service.BookSmarTkService">
    </endpoint>
  </service>
</services>

<behaviors>
  <serviceBehaviors>
    <behavior name ="BookSmartkServiceBehavior">
      <serviceDebug httpHelpPageEnabled="true" httpHelpPageUrl="/help.htm" includeExceptionDetailInFaults="true" />
    </behavior>
  </serviceBehaviors>
  <endpointBehaviors>
    <behavior name="BookSmaTkEndpointBehavior">
      <!--<webHttp/>-->
      <bookSmarTkEndpointBehavior />
    </behavior>
  </endpointBehaviors>
</behaviors>

<bindings>
  <webHttpBinding>
    <binding name="BookSmarTkBinding">
    </binding>
  </webHttpBinding>
</bindings>

<extensions>
  <behaviorExtensions>
    <add name="bookSmarTkEndpointBehavior" type="BookSmarTk.Web.Service.BookSmarTkEndpointBehaviorElement, BookSmarTk.Web.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </behaviorExtensions>
</extensions>

Если вы читаете это далеко, я глубоко благодарен вам. Большое спасибо!

2 ответа

Вместо того, чтобы устанавливать вызывающие в методе ApplyDispatchBehavior(), вы должны сделать IOperationBehavior реализатор:

 public class MyOperationBehavior: IOperationBehavior
 {
  public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
  {
  }

  public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
  {
  }

  public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
  {
   dispatchOperation.Invoker = new BookSmarTkOperationInvoker(dispatchOperation.Invoker);
  }

  public void Validate(OperationDescription operationDescription)
  {
  }
 }

и затем в ApplyDispatchBehavior() вы должны установить это поведение:

  public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  {
    foreach (var operation in endpoint.Contract.Operations) {
      if (operation.Behaviors.Contains(typeof(MyOperationBehavior)))
       continue;

      operation.Behaviors.Add(new MyOperationBehavior());
   }
  }

Я создаю что-то подобное (я думаю - у меня нет времени, чтобы просмотреть весь ваш код), но пошел по-другому.

Для этого я использую следующее:

  • IMessageInspector для чтения заголовков входящих HTTP-запросов (в этом случае извлечение идентификатора сеанса из файла cookie и извлечение объекта сеанса из кэша).
  • Комбинация IPrincipal и IAuthorizationPolicy для реализации моего собственного пользовательского кода авторизации (WCF автоматически вызовет мой код для запросов к методам веб-службы, для которых установлен атрибут [PrincipalPermission(SecurityAction.Demand, Role="somerole")]'),
  • IErrorHandler, который перехватывает любые неперехваченные исключения из методов веб-службы (включая исключение, в котором отказано в разрешении, если авторизация не удалась - т.е. метод IsRole, который вы реализуете в IPrincipal, возвращает false). Если вы поймете исключение безопасности отказано, вы можете использовать WebOperationContext.Current для установки пользовательских кодов ошибок HTTP для ответного сообщения.
  • Пользовательское поведение (IContractBehavior - но вы также можете использовать поведение EndPoint или Service или что угодно), которое создает все вышеперечисленное во время выполнения и присоединяет их к соответствующим конечным точкам.

Я знаю, что это очень старый, но для меня ответ Алексея сработал. Однако только тогда, когда метод ApplyDispatchBehaviour вызывает базовый метод. Как это:

public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        base.ApplyDispatchBehavior(endpoint, endpointDispatcher);

        foreach (var operation in endpoint.Contract.Operations)
        {
            if (operation.Behaviors.Contains(typeof(AccessControlOperationBehaviour)))
                continue;
    
            operation.Behaviors.Add(new AccessControlOperationBehaviour());
        }
    }
Другие вопросы по тегам