В 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 я не нашел, буду рад если кто-то подскажет.
Разобраться в этом помогут только исходники :).