3

I have a OWIN middleware class to do some authentication based on some custom tokens. All works fine. However I would like to return a useful error response to the client. My reasoning is that if the client asked for a 'application/json' response and they are expecting a serialize object, then that's what they should get, even if it is a 401 status code.

Here is the Invoke section of my middleware:

    public override async Task Invoke(IOwinContext context)
    {
        try
        {
            this.DoAuthorization(context);
            await this.Next.Invoke(context);
        }
        catch (UnauthorizedAccessException ex)
        {
            this.GenerateErrorResult(context, HttpStatusCode.Unauthorized, this.ExceptionToString(ex));
        }
        catch (Exception ex)
        {
            this.GenerateErrorResult(context, HttpStatusCode.InternalServerError, this.ExceptionToString(ex));
        }
    }

    private void GenerateErrorResult(IOwinContext context, HttpStatusCode code, string errorMessage)
    {
        var result = new Result { Status = Result.EStatus.Error, ErrorText = errorMessage };

        context.Response.StatusCode = (int)code;
        context.Response.ContentType = "application/json";
        context.Response.Write(JsonConvert.SerializeObject(result));
    }

This all works fine, however:

  • is this the 'correct' way?
  • what if the client asks for 'application/xml', which obviously Web API is quite capable of supporting

Is there a better way to return a custom response object ('Result' in my case) that is serialized as the client would expect?

Paul
  • 144
  • 1
  • 6
  • This answer might help you: http://stackoverflow.com/questions/30918649/unhandled-exception-global-handler-for-owin-katana – Stephen Zeng Jul 18 '16 at 14:18
  • Looks like a useful trick, but I still don't see how to use the IOwinContext to automatically serialize a response object (without doing manual serialization with JsonConvert, etc). The controllers just need to return an object which the framework will serialize to json or xml. Why can't the middleware do this? – Paul Jul 19 '16 at 06:22
  • I believe you have to serialize it yourself since owin middleware is out of webapi scope. – Stephen Zeng Jul 19 '16 at 06:24
  • That's what I feared, however it seems that I can use the built in media formatters: `var mediaTypeValue = new MediaTypeHeaderValue(context.Request.ContentType); var formatter = this.config.Formatters.FindWriter(typeof(Result), mediaTypeValue); ` – Paul Jul 19 '16 at 07:57

1 Answers1

3

Well this seems to work, using an extra OwinMiddleware inserted first:

   public override async Task Invoke(IOwinContext context)
    {
        try
        {
            await Next.Invoke(context);
        }
        catch (UnauthorizedAccessException ex)
        {
            var result = new Result { Status = Result.EStatus.Error, ErrorText = ExceptionToString(ex) };

            this.ReturnFormattedResult(result, HttpStatusCode.Unauthorized, context);
        }
        catch (Exception ex)
        {
            var result = new Result { Status = Result.EStatus.Error, ErrorText = ExceptionToString(ex) };

            this.ReturnFormattedResult(result, HttpStatusCode.InternalServerError, context);
        }
    }

    private void ReturnFormattedResult(Result result, HttpStatusCode code, IOwinContext context)
    {
        // what should our response be?
        var mediaType = context.Request.MediaType ?? context.Request.ContentType;

        // use the accept header (unless it is empty or '*/*' in which case use the content-type
        if (!string.IsNullOrEmpty(context.Request.Accept) && !context.Request.Accept.Contains("*/*"))
        {
            mediaType = context.Request.Accept;
        }

        // find a formatter for this media type, if no match then use the first one
        var formatter = this.config.Formatters.FindWriter(typeof(Result), new MediaTypeHeaderValue(mediaType));
        if (formatter == null)
        {
            formatter = this.config.Formatters.First();
            mediaType = formatter.SupportedMediaTypes.First().MediaType;
        }

        context.Response.StatusCode = (int)code;
        context.Response.ContentType = mediaType;
        formatter.WriteToStreamAsync(typeof(Result), result, context.Response.Body, null, null).Wait();
    }
Paul
  • 144
  • 1
  • 6