Lee
Lee

Reputation: 3969

ASP.NET Core API response headers not in expected place

I have an ASP.NET Core API that adds two headers to its response callback_uri and redirect_uri.

The strange thing (to me) is that in my AJAX call to the service, the headers are part of the JSON data, as a headers array, rather than the request object itself. I cannot use jqxhr.getResponseHeader(...) and therefore must interrogate the headers array manually within the response data.

Because the StatusCode is also part of the data it means my AJAX success callback is always called, even when I'm testing for a 400 bad request response, which makes testing less simple.

Web API controller action:

[HttpGet, Route("Authenticate")]
public HttpResponseMessage Authenticate(string applicationId)
{
    HttpResponseMessage response;

    if(!_security.IsApplicationIdValid(applicationId))
    {
        response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);

        response.ReasonPhrase = ErrorMessages.INVALID_APPLICATION_ID;

        return response;
    }

    IAuthenticationProvider authProvider = _security.GetAuthenticationProvider();

    response = new HttpResponseMessage(System.Net.HttpStatusCode.Redirect);

    response.Headers.Add(HeaderKeyNames.CALLBACK_URI_KEY_NAME, authProvider.GetCallbackUrl());

    response.Headers.Add(HeaderKeyNames.AUTHENTICATION_SERVICE_REDIRECT_URI_KEY_NAME, authProvider.GetUrl());

    return response;
}

AJAX code:

var settings = {

    data: { "applicationId": applicationId },
    success: successCallback, // at the moment just writes to console
    error: errorCallback, // at the moment just writes to console
    method: "GET"
};

$.ajax(url, settings);

Am I doing something wrong on the server-side?

Upvotes: 0

Views: 1835

Answers (3)

erikbozic
erikbozic

Reputation: 1675

What you're doing is creating a HttpResponseMessage object, serializing it to json and then returning it.
This is why the headers are in the json content, instead of the http response.

What you can do is someting like this:

[HttpGet, Route("Authenticate")]
public IActionResult Authenticate(string applicationId)
{

    if(!_security.IsApplicationIdValid(applicationId))
    {
        return BadRequest(ErrorMessages.INVALID_APPLICATION_ID);
    }

    IAuthenticationProvider authProvider = _security.GetAuthenticationProvider();

    this.Response.Headers.Add(HeaderKeyNames.CALLBACK_URI_KEY_NAME, authProvider.GetCallbackUrl());

    this.Response.Headers.Add(HeaderKeyNames.AUTHENTICATION_SERVICE_REDIRECT_URI_KEY_NAME, authProvider.GetUrl());

    return StatusCode(302);
}

Upvotes: 0

jpgrassi
jpgrassi

Reputation: 5762

You can use a combination of ResultFilters and ServiceFilterAttribute to add your custom headers. This is particularly useful because:

  1. ServiceFilter enables you to have DI access in your ResultFilter.
  2. You can apply it as an Attribute in the actions you want
  3. You can test it.

Putting all together:

  1. Create the custom result filter class
public class CustomHeadersResultFilter : IResultFilter
{
    private readonly IMyService _myService;

    public CustomHeadersResultFilter(IMyService myService)
    {
        _myService = myService;
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add("my-header", _myService.GetData());

        // if under CORS, this need to be added otherwise you can't read the headers using xhr.getResponseHeader('my-header')
        context.HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "my-header");
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // can't add headers here, since it's too late in the pipeline
    }
}
  1. Register it in your Startup.ConfigureServices
services.AddTransient<IMyService, MyService>();

// our custom result filter
services.AddTransient<CustomHeadersResultFilter>();
  1. Apply the attribute in the action you want to return the custom headers
[HttpGet("{id}")]
[ServiceFilter(typeof(CustomHeadersResultFilter))]
public ActionResult Get(string id)
{
    if (id == "something-bad")
    {
        return BadRequest("invalid application id");
    }

    // return a 200 Ok. Check the other types if you want something different
    return Ok();
}

Testing all of this with a separate web application, doing an ajax request to the API, you can access the headers:

<script>

    var settings = { method: "GET" };

    $.ajax('http://localhost:61284/api/values/test', settings)
        .done(function (data, textStatus, xhr) {
            alert(xhr.getResponseHeader('my-header'));
        })
        .fail(function () {
            alert("error");
        });

</script>

Upvotes: 1

Krist&#243;f T&#243;th
Krist&#243;f T&#243;th

Reputation: 831

Add headers like this: (ofc change the type if needed or define your own)

response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");

Upvotes: 0

Related Questions