Apr 20, 2010

WCF NHibernate управление сессией и транзакциями

В WCF службах, как и в любых других приложениях где есть БД и ORM становится вопрос об управлении жизненным циклом объектов и транзакциями. Для собственных служб мне хотелось реализовать схему как в веб приложениях, т.е. :

  • ISession живет один запрос.
  • Транзация открывается в начале запроса, и закрывается в конце.
  • В случае наличия ошибки транзакция откатывается, в обратном подтверждается.

Реализовать это можно при помощи IDispatchMessageInspector. Данный интерфейс позволяет обработать события начала и окончания запроса. Итак реализация:

public class TransactionManager : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
    {
        SessionStorage.OpenSession();
        SessionStorage.CurrentSession.BeginTransaction(IsolationLevel.ReadCommitted);
        return null;
    }
    
    public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
        var session = SessionStorage.CurrentSession;

        if (session.Transaction.IsActive)
        {
            if (reply.IsFault)
            {
                session.Transaction.Rollback();
            }
            else
            {
                session.Transaction.Commit();
            }
        }
    }
}

Чтобы зарегистрировать TransactionManager нам потребуется еще два вспомогательных класса. 1й добавит TransactionManager к службе:

public class TransactionBehaviour : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        throw new Exception("Behavior not supported on the consumer side!");
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        TransactionManager inspector = new TransactionManager();
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

Второй будет использоваться для регистрации секции в конфигурационном файле:

public class TransactionBehaviorExtensionElement : BehaviorExtensionElement
{
    protected override object CreateBehavior()
    {
        return new TransactionBehaviour();
    }
    
    public override Type BehaviorType
    {
        get
        {
            return typeof(TransactionBehaviour);
        }
    }
}

И последний штрих – настройка самого сервиса где все это собирается воедино:

<system.serviceModel>
  <services>
    <service name="WcfTransactions.TestService">
      <endpoint behaviorConfiguration="transactionsEnabledBehaviour" address="" binding="basicHttpBinding" contract="WcfTransactions.ITestService"/>
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
    </service>
  </services>

  <behaviors>
    <serviceBehaviors>
      <behavior>
        <serviceMetadata httpGetEnabled="true"/>
        <serviceDebug includeExceptionDetailInFaults="true"/>
      </behavior>
    </serviceBehaviors>

    <endpointBehaviors>
      <behavior name="transactionsEnabledBehaviour">
        <transactionBehaviour />
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <extensions>
    <behaviorExtensions>
      <add
        name="transactionBehaviour"
        type="WcfTransactions.Transactions.TransactionBehaviorExtensionElement, WcfTransactions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </behaviorExtensions>
  </extensions>
</system.serviceModel>

Тут есть маленькая особенность, когда мы регистрируем behaviorExtension обязательно указывать полный путь к типу, с указанием версии, культуры и токена сборки.

Как по мне это слишком сложно и слишком много всего приходится делать. В Asp.net mvc есть ActionFilters которые позволяют сделать все тоже самое, но с помощью всего одного атрибута на контроллере. Подобных вещей для WCF я не нашел, буду рад если кто-то подскажет.

Разобраться в этом помогут только исходники :).

3 comments:

  1. Полезненько, надо мне сесть и довети до ума, подумать как облегчить.

    ReplyDelete
  2. Thank you so much for this.

    ReplyDelete