I'm moving to Azure which uses a security token to authenticate a user. So now I'm passing the security token in the header of the message and reading it using an IDispatchMessageInspector behavior on the server side. The basics mechanics work pretty well, but when the token can't be authenticated I need to reject it just as if it had failed the UserNamePasswordValidator. Any ideas how I finish this code:
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
if (!this.IsAuthenticated(securityToken))
{
<Return Error Message Structure>
}
}
Simple option is to throw a FaultException.
object IDispatchMessageInspector.AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
try
{
if (!this.IsAuthenticated(securityToken))
{
throw new FaultException<string>("some message");
}
}
catch (FaultException e)
{
throw new FaultException<string>(e.Message);
}
return null;
}
If you want something more elegant and control over the HttpStatus code that is returned (default is 500) then you can implement a Endpoint Behavior Extension which registers a Dispatch Message Inspector that watches for faults. See following post:
http://www.shulerent.com/2016/05/31/returning-custom-http-status-codes-for-wcf-soap-exceptions/
Related
ASP.NET Core MVC provides approach to handle situations when request is aborted by the client. Framework passes CancellationToken that can be accessed via HttpContext.RequestAborted property, or can be bound into controller's action.
In terms of .NET, this approach looks pretty clear, consistent and natural. What doesn't look natural and logical to me is that framework, which initializes, populates and 'cancels' this access token doesn't handle appropriate TaskCancelledException.
So, if
I create a new project from the "ASP.NET Core Web API" template,
Add an action with CancellationToken argument, something like this:
[HttpGet("Delay")]
public async Task<IActionResult> GetDelayAsync(CancellationToken cancellationToken)
{
await Task.Delay(30_000, cancellationToken);
return Ok();
}
And then send request via postman and cancel it before completion
Then the application records this error in the log:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HMCHB3SQHQQR", Request id "0HMCHB3SQHQQR:00000002": An unhandled exception was thrown by the application.
System.Threading.Tasks.TaskCanceledException: A task was canceled.
<<>>
My expectation is that exception in this particular case exception is handled and absorbed by asp.net, with no "fail" records in logs.
Error-wise behavior should be the same as with synchronous action:
[HttpGet("Delay")]
public IActionResult GetDelay()
{
Thread.Sleep(30_000);
return Ok();
}
This implementation doesn't record any errors in logs when request is aborted.
Technically exception can be absorbed and hided by exception filter, but this approach looks weird and overcomplicated. At least because this is routine situation, and writing code for any application doesn't make any sense. Also, I want to hide "exception caused by aborted request when client isn't interested in response" and behavior related to other unhandled TaskCancelledException should remain as is...
I'm wondering how and when it's supposed to properly handle and absorb exception when request is aborted by client?
There are number of articles how to access cancellation token, however I was unable to find any explicit statement that answers my question.
From https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation:
If you are waiting on a Task that transitions to the Canceled state, a
System.Threading.Tasks.TaskCanceledException exception (wrapped in an
AggregateException exception) is thrown. Note that this exception
indicates successful cancellation instead of a faulty situation.
Therefore, the task's Exception property returns null.
That's why this block does not throw (there's no task awaited that is tied to a cancellation token):
[HttpGet("Delay")]
public IActionResult GetDelay(CancellationToken cancellationToken)
{
Thread.Sleep(30_000);
return Ok();
}
I stumbled upon the same issue you described in your post. Genuinely speaking, middleware might not be the worst approach. I found good example in Ocelot API gateway on Github.
Pay attention it will return HTTP 499 Client Closed Request afterwards.
You may modify it in way that no logs will be written.
/// <summary>
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500.
/// </summary>
public class ExceptionHandlerMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IRequestScopedDataRepository _repo;
public ExceptionHandlerMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository repo)
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
{
_next = next;
_repo = repo;
}
public async Task Invoke(HttpContext httpContext)
{
try
{
httpContext.RequestAborted.ThrowIfCancellationRequested();
var internalConfiguration = httpContext.Items.IInternalConfiguration();
TrySetGlobalRequestId(httpContext, internalConfiguration);
Logger.LogDebug("ocelot pipeline started");
await _next.Invoke(httpContext);
}
catch (OperationCanceledException) when (httpContext.RequestAborted.IsCancellationRequested)
{
Logger.LogDebug("operation canceled");
if (!httpContext.Response.HasStarted)
{
httpContext.Response.StatusCode = 499;
}
}
catch (Exception e)
{
Logger.LogDebug("error calling middleware");
var message = CreateMessage(httpContext, e);
Logger.LogError(message, e);
SetInternalServerErrorOnResponse(httpContext);
}
Logger.LogDebug("ocelot pipeline finished");
}
private void TrySetGlobalRequestId(HttpContext httpContext, IInternalConfiguration configuration)
{
var key = configuration.RequestId;
if (!string.IsNullOrEmpty(key) && httpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
{
httpContext.TraceIdentifier = upstreamRequestIds.First();
}
_repo.Add("RequestId", httpContext.TraceIdentifier);
}
private void SetInternalServerErrorOnResponse(HttpContext httpContext)
{
if (!httpContext.Response.HasStarted)
{
httpContext.Response.StatusCode = 500;
}
}
private string CreateMessage(HttpContext httpContext, Exception e)
{
var message =
$"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
if (e.InnerException != null)
{
message =
$"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
}
return $"{message} RequestId: {httpContext.TraceIdentifier}";
}
}
If you use multiple middlewares it should be first on the invocation list (It's .NET 6)
app.UseMiddleware(typeof(ExceptionHandlerMiddleware));
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
I write a lot of WCF services to other services. I have had trouble with getting Fiddler to log the SOAP messages before but now I have that working. But my boss wants something without Fiddler at all where I can take the SOAP message going out and the one coming in logged to the database. I have looked a lot at WCF Logging and Diagnostics and extending it with Database Source Listener but I cant find an implementation of a Database Source Listener to use.
I don't think that's what he even wants. He wants the equivalent of Fiddler's SOAP request/response displays written to the database. Can anyone help me please?
You have two possibilities:
Write custom WCF Trace Listener, calling database procedure to store logging data:
public class AsyncMSMQTraceListener : TraceListener
{
public override void TraceData(TraceEventCache eventCache, string source,
TraceEventType eventType, int id, object data)
{
// eventType like for example TraceEventType.Information
var message = data.ToString();
// Here call datbase save log with message
}
public override void Write(string message)
{
}
public override void WriteLine(string message)
{
}
}
but in this way you'll get trace messages, where request/response is only a part of it.
Write custom Message Inspector class:
public class ConsoleOutputMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
// Here you can use buffer.CreateMessage().ToString()
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
reply = buffer.CreateMessage();
// here you can use buffer.CreateMessage().ToString()
}
}
Note: Regardless which method you choose, I would suggest to make some kind of an asynchronous call to the database to not block normal service flow.
I am passing the TokenId as Soap Header for all the requests.
<soapenv:Header> <tem:TokenIdentity>12345</tem:TokenIdentity> </soapenv:Header>
for example I have 5 webmethods.
I would like that ValidateTokenId() method which shoule be called automatically before accessing any webmethods.
Anybody done this before?
I got the solution to validate the token
WCF Service implemented(IDispatchMessageInspector) the following two methods to take care of Soap header validation and
Logging the SOAP Requests and SOAP Responses.
AfterReceiveRequest
So all the incoming SOAP requests are automatically called for ValidateToken() method and will be logged too.
BeforeSendReply
All the response SOAP messages are logged here.
#region IDispatchMessageInspector Members
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
{
int headerIndex1 = OperationContext.Current.IncomingMessageHeaders.FindHeader("TokenIdentity", "");
XmlReader r = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(0).ReadSubtree();
XElement data = XElement.Load(r);
var tokenValue = (string)data;
ValidateToken(tokenValue);
//Log the Request with Log4Net or something
//Console.WriteLine("IDispatchMessageInspector.AfterReceiveRequest called.");
return null;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
//Log the Response with Log4Net or something
//Console.WriteLine("IDispatchMessageInspector.BeforeSendReply called.");
}
#endregion
I have a WCF REST service which is consumed on the client by the classical :
WebResponse response = request.GetResponse();
I want to intercept any errors that appear in the service and deliver them to the client. By default behavior, when an exception occurs in the service an faultexception is thrown and the channel is faulted, therefore on the client I receive a Bad request.
I want to be able to return the client the stackstrace and override the behaviour not to fault the channel.
For that I have implemented the IErrorHandler
public class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
fault = Message.CreateMessage(version, string.Empty, String.Format("An unknown error has occurred. The error identifier "), new DataContractJsonSerializer(typeof(string)));
fault.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Json));
fault.Properties.Add(HttpResponseMessageProperty.Name, HttpStatusCode.Accepted);
}
}
the question is even if I register this on the service, I can debug the errorhandler, but the channel is still faulted so I still receive a bad request in the client.
I use the following factory for the client:
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var host = base.CreateServiceHost(serviceType, baseAddresses);
ServiceEndpoint ep = host.AddServiceEndpoint(serviceType, new WebHttpBinding(), "");
host.Description.Endpoints[0].Behaviors.Add(new WebHttpBehavior { HelpEnabled = true });
return host;
}
The question is how can I prevent the channel from getting faulted in the errorhandler.
It should work. I have the same situation and I set the fault also in the ProvideFault method. The only thing I can think of is that I am not seeing you create a FaultException from which you'd call CreateMessageFault().
Here is an example:
public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
{
// we don't want the communication channel to fault, so we'll provide a general purpose fault with the exception provided.
var fe = new FaultException(error.Message);
MessageFault msg = fe.CreateMessageFault();
fault = Message.CreateMessage(version, msg, "YourActionNamespace");
}
One of the things I never understood about WCF is why no Exception message details are propagated back to the calling client when the server encounters an unhandled exception.
For example, if I have the following server code
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Server : IServer
{
public DTO GetDTO()
{
DTO dto = new DTO();
dto.dto = dto;
return dto;
}
}
public class DTO
{
public DTO dto;
}
[ServiceContract]
public interface IServer
{
[OperationContract]
DTO GetDTO();
}
I deliberately introduced an ObjectGraph to cause a serialization exception when the DTO object is returned.
If I have a client that calls this Server's GetDTO() method, I will get the following CommunicationException.
The socket connection was aborted. This could be caused by an error
processing your message or a receive timeout being exceeded by the
remote host, or an underlying network resource issue. Local socket
timeout was '00:00:58.9350000'.
Which is absolutely useless. It has no inner exception and not even the real exception message.
If you then use Microsoft Service TraceViewer, you will see the exception but you must turn on the Diagnostics tracing for this.
The exception message that should be sent back is
There was an error while trying to serialize parameter
http://tempuri.org/:GetDTOResult. The InnerException message was
'Object graph for type 'TestWCFLib.DTO' contains cycles and cannot be
serialized if reference tracking is disabled.'. Please see
InnerException for more details.
So can anybody tell me how get the right exception message show up on the client side? Obviously, setting IncludeExceptionDetailInFaults to true doesn't make a difference.
I think that it is by design that the server errors are not propogated to client. This is in general a practice to not expose server internals to clients as the main purpose of Client Server architecture is independence of server.
You can still achieve this by using Fault Exception
Decorate your service declaration with a fault contract
[ServiceContract]
public interface IServer
{
[OperationContract]
[FaultContract(typeof(MyApplicationFault))]
DTO GetDTO();
}
Then catch errors in servcie implementation and throw a fault exception.
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Server : IServer
{
public DTO GetDTO()
{
try
{
DTO dto = new DTO();
dto.dto = dto;
return dto;
}
catch (Exception ex)
{
MyApplicationFault fault = new MyApplicationFault(...);
throw new FaultException<MyApplicationFault>(fault);
}
}
}
And catch the exception in client
IServer proxy = ...; //Get proxy from somewhere
try
{
proxy.GetDTO();
}
catch (TimeoutException) { ... }
catch (FaultException<MyApplicationFault> myFault) {
MyApplicationFault detail = myFault.Detail;
//Do something with the actual fault
}
catch (FaultException otherFault) { ... }
catch (CommunicationException) { ... }
Hope this helps.
For a nice tutorial please see Code Project Tutorial on Fault Exception