V tomto článku jsme si ukázali jak je možné v Silverlight aplikaci ošetřovat výjimky, které vzniknout při zpracování operace WCF služby na serveru. To nám umožní buď zachytit specifickou výjimku, pro kterou ve službě definujeme fault kontrakt nebo pouze přenést na klienta debug informace v případě obecné (neošetřené) výjimky. V obou těchto případech neobsahuje ale výjimka, která je na klientu vyhozená, původní stack trace prováděného asynchronního volání. Nyní ukážu způsob jakým lze i toto řešit.
Pokud dojde v asynchronním volání WCF služby na serveru k vyhození výjimky, je v Silverlight klientu možné zachytit odpovídající výjimku typu
- System.ServiceModel.FaultException<System.ServiceModel.ExceptionDetail> (pro obecnou výjimku)
případně
- System.ServiceModel.FaultException<námi definovaný fault> (pro očekávanou výjimku)
Jde ale o to, že stack trace této výjimky bude odpovídat místu jejího vyhození WCF infrastrukturou až po dokončení volání asynchronní operace a nebude proto obsahovat informace o tom odkud bylo asynchronní volání prováděno.
Tento stack trace může vypadat například nějak takto:
v System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)
v System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
v System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
v System.ServiceModel.ClientBase`1.ChannelBase`1.EndInvoke(String methodName, Object[] args, IAsyncResult result)
v MyApplication.ServiceProxy.WCFServiceClient.WCFServiceClientChannel.EndGetUserInfo(IAsyncResult result)
v MyApplication.ServiceProxy.WCFServiceClient.MyApplication.ServiceProxy.WCFServiceClient.EndGetUserInfo(IAsyncResult result)
v MyApplication.ServiceProxy.WCFServiceClient.OnEndGetUserInfo(IAsyncResult result)
v System.ServiceModel.ClientBase`1.OnAsyncCallCompleted(IAsyncResult result)
Řešením je uložit si před vyvoláním WCF operace aktuální StackTrace sami a v případě chyby si tuto informaci přidat k objektu výjimky.
Stack trace v místě před provedením asynchronního volání bude vypadat například takto:
v MyApplication.Classes.Proxy.GetUserInfo(string sessionToken, CancellationToken cancellationToken)
v MyApplication.ViewModels.MainViewModel.<Initialize>d__0.MoveNext()
v System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
v System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
v System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
v MyApplication.ViewModels.MainViewModel.Initialize()
v MyApplication.ViewModels.LoginViewModel.<SignIn>d__2.MoveNext()
v System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()
v System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClassa.<OnCompletedInternal>b__1(Object state)
v System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
v System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
v System.Delegate.DynamicInvokeImpl(Object[] args)
v System.Windows.Threading.DispatcherOperation.Invoke()
v System.Windows.Threading.Dispatcher.Dispatch(DispatcherPriority priority)
v System.Windows.Threading.Dispatcher.OnInvoke(Object context)
v System.Windows.Hosting.CallbackCookie.Invoke(Object[] args)
v System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(IntPtr pHandle, Int32 nParamCount, ScriptParam[] pParams, ScriptParam& pResult)
Je vidět, že tyto informace jsou o mnoho lepší, je zde přesně obsaženo jaké volání a odkud bylo prováděno.
Problém je tu ale také v tom, že interní stack trace výjimky, který je dostupný vlastností StackTrace, nelze nijak modifikovat. Moje řešení tedy pouze uloží náš stack trace asynchronní operace jako hodnotu do kolekce Data výjimky. To v důsledku ale znamená, že pro výpis informací o výjimce je pak potřeba použít vlastní kód a nespoléhat na standardní Exception.ToString().
“Zaobalení” volání WCF služby do operace typu Task (které jsme si ukázali minule) tedy nyní trochu rozšíříme a provedeme v něm diskutované změny:
public static Task<UserInfo> GetUserInfo(string sessionToken, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
{
string stackTrace = new System.Diagnostics.StackTrace().ToString();
var tcs = new TaskCompletionSource<UserInfo>();
if (cancellationToken.IsCancellationRequested)
{
tcs.SetCanceled();
return tcs.Task;
}
var cancellationRegistration = cancellationToken.Register(() => tcs.TrySetCanceled());
var service = Proxy.Service;
Guid token = Guid.NewGuid();
EventHandler<GetUserInfoCompletedEventArgs> handler = null;
handler = (s, e) =>
{
if ((Guid)e.UserState != token)
{
return;
}
service.GetUserInfoCompleted -= handler;
cancellationRegistration.Dispose();
if (e.Cancelled || cancellationToken.IsCancellationRequested)
{
tcs.TrySetCanceled();
}
else if (e.Error != null)
{
tcs.SetException(e.Error.SetAsyncStackTrace(stackTrace));
}
else
{
tcs.SetResult(e.Result);
}
};
service.GetUserInfoCompleted += handler;
service.GetUserInfoAsync(sessionToken, token);
return tcs.Task;
}
Metoda SetAsyncStackTrace je extension metoda, kterou provedeme uložení předané hodnoty do dat výjimky pod klíčem “AsyncStackTrace”. Tato metoda je implementována ve třídě ExceptionExtensions . Zdrojový kód třídy ExceptionExtensions je dostupný zde (nebo ke stažení zde).
Ve třídě ExceptionExtensions je dále metoda FormatExceptionInfo, která právě umí formátovat informace o výjimce ve stylu ToString(), ale obsahuje dva rozdíly:
- Pokud je u výjimky v datech uložen "AsyncStackTrace" je vypsán místo jejího interního StackTrace (stack trace po ukončení asynchronního volání naopak vypisovat nepotřebujeme).
- Pro výjimku typu System.ServiceModel.FaultException<TDetail> a TDetail typu ExceptionDetail (nebo pro fault odvozený z ExceptionDetail) jsou do výpisu zahrnuté informace z tohoto ExceptionDetail včetně jeho hierarchie InnerException.
Celý výpis pořízený extension metodou FormatExceptionInfo může být ve výsledku následující:
System.ServiceModel.FaultException`1[[System.ServiceModel.ExceptionDetail, System.ServiceModel, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: Error from server
---> System.Exception: Error from server
at MyApplication.Web.DataClasses.Users.GetUserInfo(Int32 UserID) in c:\Develop\MyApplication\MyApplication.Web\DataClasses\Users.cs:line 156
at MyApplication.Web.WCFService.GetUserInfo(String sessionToken) in c:\Develop\MyApplication\MyApplication.Web\WCFService.svc.cs:line 89
at SyncInvokeGetUserInfo(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
--- End of remote service exception stack trace ---
v MyApplication.Classes.Proxy.GetUserInfo(string sessionToken, CancellationToken cancellationToken)
v MyApplication.ViewModels.MainViewModel.<Initialize>d__0.MoveNext()
v System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
v System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
v System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
v MyApplication.ViewModels.MainViewModel.Initialize()
v MyApplication.ViewModels.LoginViewModel.<SignIn>d__2.MoveNext()
v System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()
v System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClassa.<OnCompletedInternal>b__1(Object state)
v System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
v System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
v System.Delegate.DynamicInvokeImpl(Object[] args)
v System.Windows.Threading.DispatcherOperation.Invoke()
v System.Windows.Threading.Dispatcher.Dispatch(DispatcherPriority priority)
v System.Windows.Threading.Dispatcher.OnInvoke(Object context)
v System.Windows.Hosting.CallbackCookie.Invoke(Object[] args)
v System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(IntPtr pHandle, Int32 nParamCount, ScriptParam[] pParams, ScriptParam& pResult)
V tomto případě se právě skládá z výpisu přeneseného ze serveru jako ExceptionDetail doplněného o výpis námi pořízeného "AsyncStackTrace".