Служба маршрутизации WCF - динамическая обработка ошибок
Я узнаю о том, что можно сделать с помощью службы маршрутизации WCF. Все еще в фазе "крутись с ним, чтобы посмотреть, что он может сделать".
Насколько я понимаю, служба маршрутизации заключается в том, что при получении сообщения служба пытается передать его той конечной точке, которая появляется первой в списке резервных копий. Если это не удастся, он будет пытаться выполнить следующее, а затем следующее, пока либо что-то не сработает, либо не останется ничего попробовать.
Я хотел бы получить доступ к этому событию, чтобы я мог:
- Зарегистрировать ошибку
- Отправьте уведомление по электронной почте, что конечная точка не работает
- При желании удалите конечную точку из списка резервных копий, чтобы она не замедляла прохождение будущих сообщений через систему.
Возникли проблемы с поиском того, как расширить структуру WCF, чтобы попасть на это конкретное мероприятие.
Может ли это сделать служба маршрутизации WCF? Любой толчок в правильном направлении будет принята с благодарностью.
На данный момент у меня есть 30-ти динамически генерируемые сервисы маршрутизации, размещенные под IIS (или, точнее, ASP.NET Development Server для Visual Studio 2010). Я настраиваю маршруты к сервисам в Global.asax, как показано ниже.
protected void Application_Start(object sender, EventArgs e)
{
List<Type> serviceTypes = ServiceUtility.GetServiceTypes();
foreach (Type st in serviceTypes)
{
string route = String.Format("Services/{0}.svc", ServiceUtility.GetServiceName(st));
RouteTable.Routes.Add(new ServiceRoute(route, new RoutingServiceHostFactory(st), typeof(System.ServiceModel.Routing.RoutingService)));
}
}
ServiceUtility и Routing ServiceHostFactory являются пользовательскими классами. Обратите внимание, что IPolicyService - это интерфейс контракта службы WCF в интересующей сборке.
public static class ServiceUtility
{
public static List<Type> GetServiceTypes()
{
Type policyInterfaceType = typeof(IPolicyService);
Assembly serviceContractsAssembly = Assembly.GetAssembly(policyInterfaceType);
Type[] serviceContractsAssemblyTypes = serviceContractsAssembly.GetTypes();
List<Type> serviceTypes = new List<Type>();
foreach (Type t in serviceContractsAssemblyTypes)
{
if (!t.IsInterface)
continue;
object[] attrib = t.GetCustomAttributes(typeof(ServiceContractAttribute), false);
if (attrib == null || attrib.Length <= 0)
continue;
serviceTypes.Add(t);
}
return serviceTypes;
}
// Other stuff
}
Я создаю свои ServiceHosts следующим образом. Для краткости я опустил некоторые из моих вспомогательных методов.
public class RoutingServiceHostFactory : ServiceHostFactory
{
private Type BackendServiceType { get; set; }
private Binding BackendServiceBinding { get; set; }
public RoutingServiceHostFactory(Type backendServiceType)
{
this.BackendServiceType = backendServiceType;
this.BackendServiceBinding = ServiceUtility.GetBinding(this.BackendServiceType);
}
private const string DOMAIN_LIVE = "http://localhost:2521/";
private const string DOMAIN_DEAD_1 = "http://localhost:2522/";
private const string DOMAIN_DEAD_2 = "http://localhost:2524/";
private const string DOMAIN_DEAD_3 = "http://localhost:2525/";
private const string DOMAIN_DEAD_4 = "http://localhost:2526/";
private const string DOMAIN_DEAD_5 = "http://localhost:2527/";
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
this.BindEndpoints(host, baseAddresses);
this.ConfigureRoutingBehavior(host);
this.ConfigureServiceMetadataBehavior(host);
this.ConfigureDebugBehavior(host);
host.Description.Behaviors.Add(new RoutingServiceErrorHandlerInjector());
return host;
}
// Other Stuff
private void ConfigureRoutingBehavior(ServiceHost host)
{
string deadAddress1 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_1, this.BackendServiceType);
string deadAddress2 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_2, this.BackendServiceType);
string deadAddress3 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_3, this.BackendServiceType);
string deadAddress4 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_4, this.BackendServiceType);
string deadAddress5 = ServiceUtility.GetServiceUrl(DOMAIN_DEAD_5, this.BackendServiceType);
string realAddress = ServiceUtility.GetServiceUrl(DOMAIN_LIVE, this.BackendServiceType);
RoutingConfiguration rc = new RoutingConfiguration();
ContractDescription contract = new ContractDescription("IRequestReplyRouter");
ServiceEndpoint deadDestination1 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress1));
ServiceEndpoint deadDestination2 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress2));
ServiceEndpoint deadDestination3 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress3));
ServiceEndpoint deadDestination4 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress4));
ServiceEndpoint deadDestination5 = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(deadAddress5));
ServiceEndpoint realDestination = new ServiceEndpoint(contract, this.BackendServiceBinding, new EndpointAddress(realAddress));
List<ServiceEndpoint> backupList = new List<ServiceEndpoint>();
backupList.Add(deadDestination1);
backupList.Add(deadDestination2);
backupList.Add(deadDestination3);
backupList.Add(deadDestination4);
backupList.Add(deadDestination5);
backupList.Add(realDestination);
rc.FilterTable.Add(new MatchAllMessageFilter(), backupList);
RoutingBehavior rb = new RoutingBehavior(rc);
host.Description.Behaviors.Add(rb);
}
// Other Stuff
}
Порт 2521 имеет действующий веб-сайт на другом конце, на котором размещены некоторые службы WCF. Другие порты, упомянутые выше, ничего не слушают.
Для контекста, вот мой Web.config для сайта маршрутизации. Заметьте, что тайм-ауты и тому подобное - просто результат моих ошибок, не принимайте их слишком серьезно.
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<bindings>
<wsHttpBinding>
<binding
name="TestBinding"
allowCookies="True"
closeTimeout="00:04:00"
openTimeout="00:00:10"
receiveTimeout="00:05:00"
sendTimeout="00:05:00"
maxReceivedMessageSize="15728640">
<security>
<message establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
РЕДАКТИРОВАТЬ
В ответ на ответ TheDoctor ниже я подумала, что мне следует подробнее рассказать о том, что я делаю с этим пробным решением с тех пор, как я первоначально разместила сообщение. Я попытался реализовать интерфейс IErrorHandler. Однако мне не очень повезло с этим.
Обратите внимание, что в приведенном выше примере мой Routing ServiceHostFactory немного изменился. Теперь я добавляю поведение Routing ServiceErrorHandlerInjector к описанию сервиса. Обратите внимание, что я также добавил дополнительные тупиковые точки в свой резервный список для иллюстрации.
public class RoutingServiceErrorHandlerInjector : IServiceBehavior
{
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher chanDisp in serviceHostBase.ChannelDispatchers)
{
chanDisp.ErrorHandlers.Add(new RoutingServiceErrorHandler());
}
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
#endregion
}
public class RoutingServiceErrorHandler : IErrorHandler
{
#region IErrorHandler Members
public bool HandleError(Exception error)
{
throw new NotImplementedException(error.Message, error);
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
throw new NotImplementedException(error.Message, error);
}
#endregion
}
Я ожидал, что я должен вызвать событие ProvideFault или HandleError для deadDestination1 через deadDestination5. У меня есть точки останова на NotImplementedExceptions выше в моем отладчике. Но этот код никогда не активируется. В конце концов, вызовы доходят до реального адреса в конце списка резервного копирования, и клиент-серверное приложение, которое я использую для тестирования этой службы Routing Service, работает нормально. Связь медленнее, но все еще хорошо в пределах тайм-аута.
Однако, если я закомментирую строку backupList.Add(realDestination);
из метода ConfigureRoutingBehavior, описанного выше, метод Routing ServiceErrorHandler.ProvideFault приведен ниже... Но он содержит только информацию, относящуюся к deadDestination5. Любые исключения или ошибки, которые могли быть сгенерированы для deadDestination1 через deadDestination4, просто исчезают у меня.
Кроме того, я попробовал отладчик RedGate, следуя отраженному коду для Routing Service. Это было сложно для меня, так как я не привык к отладке оптимизированного кода, так что мне едва ли были доступны какие-либо переменные для чтения. Но от глазного шага до логики ниже:
// This has been taken from System.ServiceModel.Routing.RoutingService
// via the RedGate decompiler - unsure about it's ultimate accuracy.
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed), ServiceBehavior(AddressFilterMode=AddressFilterMode.Any, InstanceContextMode=InstanceContextMode.PerSession, UseSynchronizationContext=false, ValidateMustUnderstand=false)]
public sealed class RoutingService : ISimplexDatagramRouter, ISimplexSessionRouter, IRequestReplyRouter, IDuplexSessionRouter, IDisposable
{
[OperationBehavior(Impersonation=ImpersonationOption.Allowed), TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
IAsyncResult IRequestReplyRouter.BeginProcessRequest(Message message, AsyncCallback callback, object state)
{
return this.BeginProcessRequest<IRequestReplyRouter>(message, callback, state);
}
private IAsyncResult BeginProcessRequest<TContract>(Message message, AsyncCallback callback, object state)
{
IAsyncResult result;
try
{
System.ServiceModel.Routing.FxTrace.Trace.SetAndTraceTransfer(this.ChannelExtension.ActivityID, true);
result = new ProcessRequestAsyncResult<TContract>(this, message, callback, state);
}
catch (Exception exception)
{
if (TD.RoutingServiceProcessingFailureIsEnabled())
{
TD.RoutingServiceProcessingFailure(this.eventTraceActivity, OperationContext.Current.Channel.LocalAddress.ToString(), exception);
}
throw;
}
return result;
}
}
Соответствующие разделы из System.ServiceModel.Routing.ProcessRequestAsyncResult показаны ниже. Они также отладки через RedGate, поэтому не могут быть изменены. Я верю, что RedGate и выпущенный источник от Microsoft точны. #hesaiddubiously
internal class ProcessRequestAsyncResult<TContract> : TransactedAsyncResult
{
public ProcessRequestAsyncResult(RoutingService service, Message message, AsyncCallback callback, object state) : base(callback, state)
{
this.allCompletedSync = true;
this.service = service;
this.messageRpc = new System.ServiceModel.Routing.MessageRpc(message, OperationContext.Current, service.ChannelExtension.ImpersonationRequired);
if (TD.RoutingServiceProcessingMessageIsEnabled())
{
TD.RoutingServiceProcessingMessage(this.messageRpc.EventTraceActivity, this.messageRpc.UniqueID, message.Headers.Action, this.messageRpc.OperationContext.EndpointDispatcher.EndpointAddress.Uri.ToString(), (this.messageRpc.Transaction != null) ? "True" : "False");
}
try
{
EndpointNameMessageFilter.Set(this.messageRpc.Message.Properties, service.ChannelExtension.EndpointName);
this.messageRpc.RouteToSingleEndpoint<TContract>(this.service.RoutingConfig);
}
catch (MultipleFilterMatchesException exception)
{
throw System.ServiceModel.Routing.FxTrace.Exception.AsError(new ConfigurationErrorsException(System.ServiceModel.Routing.SR.ReqReplyMulticastNotSupported(this.messageRpc.OperationContext.Channel.LocalAddress), exception));
}
while (this.StartProcessing())
{
}
}
private bool StartProcessing()
{
bool flag = false;
SendOperation operation = this.messageRpc.Operations[0];
this.currentClient = this.service.GetOrCreateClient<TContract>(operation.CurrentEndpoint, this.messageRpc.Impersonating);
if (TD.RoutingServiceTransmittingMessageIsEnabled())
{
TD.RoutingServiceTransmittingMessage(this.messageRpc.EventTraceActivity, this.messageRpc.UniqueID, "0", this.currentClient.Key.ToString());
}
try
{
Message message;
if ((this.messageRpc.Transaction != null) && operation.HasAlternate)
{
throw System.ServiceModel.Routing.FxTrace.Exception.AsError(new ConfigurationErrorsException(System.ServiceModel.Routing.SR.ErrorHandlingNotSupportedReqReplyTxn(this.messageRpc.OperationContext.Channel.LocalAddress)));
}
if (operation.AlternateEndpointCount > 0)
{
message = this.messageRpc.CreateBuffer().CreateMessage();
}
else
{
message = this.messageRpc.Message;
}
operation.PrepareMessage(message);
IAsyncResult result = null;
using (base.PrepareTransactionalCall(this.messageRpc.Transaction))
{
using (IDisposable disposable = null)
{
try
{
}
finally
{
disposable = this.messageRpc.PrepareCall();
}
result = this.currentClient.BeginOperation(message, this.messageRpc.Transaction, base.PrepareAsyncCompletion(ProcessRequestAsyncResult<TContract>.operationCallback), this);
}
}
if (!base.CheckSyncContinue(result))
{
return flag;
}
if (this.OperationComplete(result))
{
base.Complete(this.allCompletedSync);
return flag;
}
return true;
}
catch (Exception exception)
{
if (Fx.IsFatal(exception))
{
throw;
}
if (!this.HandleClientOperationFailure(exception))
{
throw;
}
return true;
}
}
}
На мой взгляд, мне кажется, что ProcessRequestAsyncResult выполняет пошаговое выполнение списка резервных копий с помощью метода ProcessRequestAsyncResult.StartProcessing. Тем не менее, StartProcess(), по-видимому, не генерирует каждое исключение, а скорее выборочно выбирает, генерировать исключения или нет.
Кажется, что только StartProcess () на самом деле генерирует только исключение для окончательного мертвого адреса, а затем передается предложением Routing Service.BeginProcessRequest, чтобы затем, наконец, полностью перейти к активации в моей реализации IErrorHandler.
Это настоятельно подсказывает мне, что то, что я пытаюсь сделать здесь, не может быть сделано с текущей реализацией пространства имен System.ServiceModel.Routing. Обратите внимание, что Routing Service является запечатанным классом, поэтому я не могу расширить его собственным базовым классом, чтобы изменить это поведение, даже если я думал, что это хорошая идея (а я нет).
Но опять же, обратите внимание, что это поверхностное чтение. Я легко могу ошибаться. На самом деле, я бы очень хотел оказаться неправым. Я бы предпочел найти способ заставить Routing Service делать то, что я от него хочу, вместо того, чтобы кататься самостоятельно.
1 ответ
WCF обеспечивает обработку ошибок ( http://msdn.microsoft.com/en-us/library/ee517422.aspx), поэтому вы можете создать функцию, которая активируется в CommunicationException ( http://msdn.microsoft.com/en-us/library/system.servicemodel.communicationexception.aspx) и записывает коды ошибок из данных, переданных в функцию. Вы можете перейти оттуда к службе рутирования почты и тому, что вам еще нужно.