I want to handle HEAD requests in my Web API 2 application. I copy-pasted the code from StrathWeb's blog post Adding HTTP HEAD support to ASP.NET Web API:
public class HeadHandler : DelegatingHandler
{
private const string Head = "IsHead";
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Head)
{
request.Method = HttpMethod.Get;
request.Properties.Add(Head, true);
}
var response = await base.SendAsync(request, cancellationToken);
object isHead;
response.RequestMessage.Properties.TryGetValue(Head, out isHead);
if (isHead != null && ((bool) isHead))
{
var oldContent = await response.Content.ReadAsByteArrayAsync();
var content = new StringContent(string.Empty);
content.Headers.Clear();
foreach (var header in response.Content.Headers)
{
content.Headers.Add(header.Key, header.Value);
}
content.Headers.ContentLength = oldContent.Length;
response.Content = content;
}
return response;
}
}
When I make a HEAD request from Fiddler:
HEAD http://localhost:54225/api/books/5 HTTP/1.1
User-Agent: Fiddler
Host: localhost:54225
I get the following response back:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcc2ltb25lXERvY3VtZW50c1xTaW1vbnNEb2N1bWVudHNcSVRcQyNcRGVtb0NvZGVcUkVTVGZ1bFdlYlNlcnZpY2VDbGllbnRERU1PXEJvb2tTZXJ2aWNlXGFwaVxib29rc1w1?=
X-Powered-By: ASP.NET
Date: Sun, 19 Feb 2017 12:09:52 GMT
The Content-Length is 0 but it should be 104 bytes.
When I add a breakpoint to the HeadHandler and step through the code it seems to set the Content-Length header value correctly, to 104 bytes, before returning the response.
Could there be some other step in the pipeline that is running after the HeadHandler, which recognises the response has no body, and sets the Content-Length to 0? The only things in the WebApiConfig.cs that come after the HeadHandler has been added are the mapping of the HttpAttribute routes and the setting up of the default route, neither of which seem likely to me to result in the Content-Length header being reset. Alternatively, could a configuration setting somewhere possibly affect the Content-Length returned in the response?
All you need to do is set the Content-Length of the response's Content.Headers - no need to create new content for the response. The act of requesting HEAD removes the body content from the response anyway.
This may have been a change since the article you quoted was written (2013), and no longer needs to create content from scratch...
So this is all you should need:
public class HeadHandler : DelegatingHandler
{
private const string Head = "IsHead";
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Head)
{
request.Method = HttpMethod.Get;
request.Properties.Add(Head, true);
}
var response = await base.SendAsync(request, cancellationToken);
object isHead;
response.RequestMessage.Properties.TryGetValue(Head, out isHead);
if (isHead != null && ((bool)isHead))
{
var oldContent = await response.Content.ReadAsByteArrayAsync();
response.Content.Headers.ContentLength = oldContent.Length;
return response;
}
return response;
}
}
Related
I use Asp.Net API (.Net 5) in my backend.
I use blazor wasm in my frontend.
A method of my controller is:
public FileContentResult GetExport()
{
var IndividualBl = new IndividualBl();
return ResultBlob(IndividualBl.GetExport(), "individual-export.xlsx");
}
protected FileContentResult ResultBlob(byte[] byteArray, string fileName)
{
return File(byteArray, "application/octet-stream", fileName);
}
When I use Swagger, I download the file with the name included in the header.
My Response Headers:
access-control-allow-origin: *
content-disposition: attachment; filename=individual-export.xlsx; filename*=UTF-8''individual-export.xlsx
content-length: 4218
content-type: application/octet-stream
date: Mon, 23 Nov 2020 14:39:34 GMT
server: Microsoft-IIS/10.0
status: 200
x-powered-by: ASP.NET
Now I try to get this file by service :
public async Task<StreamFileModel> GetExport()
{
var url = $"{_httpClient.BaseAddress}{IndividualUrls.IndividualController}/{IndividualUrls.GetExport}";
var result = await _httpClient.GetAsync(url);
var header = result.Content.Headers.ContentDisposition;
var content = await result.Content.ReadAsByteArrayAsync();
return new StreamFileModel
{
File = content,
Name = header.FileName
};
}
My content is OK but the header is null (= ContentDisposition is null)
It is not the good method to get the content-disposition value?
Thanks
In API project, you need to use Cors policy to allow content-disposition in header:
app.UseCors(x => x.WithExposedHeaders("content-disposition"));
Just for learning purpose, I am logging all http requests to my Web API 2 application using a handler.
enum LogType {Information = 1, Warning = 2, Error = 3 }
public class LogHandler: DelegatingHandler
{
async protected override Task SendAsync(HttpRequestMessage httpRequest, CancellationToken cancellationToken)
{
Trace.WriteLine(httpRequest.ToString(), LogType.Information.ToString());
var response = await base.SendAsync(httpRequest, cancellationToken);
return response;
}
}
This just prints the Request Headers as follows:
Information: Method: POST, RequestUri: 'http://localhost:49964/school/title?number=1&name=swanand pangam', Version: 1.1, Content: System.Web.Http.WebHost.HttpControllerHandler+LazyStreamContent, Headers:
{
Cache-Control: no-cache
Connection: keep-alive
Accept: text/csv
Accept-Encoding: gzip
Accept-Encoding: deflate
Host: localhost:49964
User-Agent: PostmanRuntime/7.1.1
Postman-Token: 074c3aab-3427-4368-be25-439cbabe0654
Content-Length: 31
Content-Type: text/plain
}
But I am also sending a json object in POST body which is not printed. I want to print both the Headers as well as body. Also I can't find anything in the 'HttpRequestMessage' object while debugging.
You should read the content as below
// log request body
string requestBody = await httpRequest.Content.ReadAsStringAsync();
Trace.WriteLine(requestBody);
This will log the request body.
You can read the post request like following.
string requestBody = await request.Content.ReadAsStringAsync();
var response = await base.SendAsync(httpRequest, cancellationToken);
In case you wan't to log the response also which has been generated, you can try like following.
var responseBody = await response.Content.ReadAsStringAsync();
Above code will have performance implication in you application as it will hit for each and every operation call, you need to be cautious about this. You may consider a flag to enable and disable this.
var body = new StreamReader(Request.Body);
//The modelbinder has already read the stream and need to reset the stream index
body.BaseStream.Seek(0, SeekOrigin.Begin);
var requestBody = body.ReadToEnd();
I'm working on an application which makes a web service request using the HttpClient API to a third party service over which I have no control. The service seems to respond to requests with an ETag HTTP header containing an invalid value according to the HTTP specification (the value is not enclosed in quotation marks). This causes the HttpClient to fail with a System.FormatException.
In this case I am not interested in the ETag header and would like to be able to ignore the invalid value. Is it possible to do this using HttpClient?
I would prefer to use the HttpClient API over WebClient or WebRequest since this particular use case requires me to use a SOCKS proxy which is a lot simpler when using HttpClient.
Try this:
class Example
{
static void Main()
{
var client = HttpClientFactory.Create(new Handler());
client.BaseAddress = new Uri("http://www.example.local");
var r = client.GetAsync("").Result;
Console.WriteLine(r.StatusCode);
Console.WriteLine(r.Headers.ETag);
}
}
class Handler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var headerName = "ETag";
var r = await base.SendAsync(request, cancellationToken);
var header = r.Headers.FirstOrDefault(x => x.Key == headerName);
var updatedValue = header.Value.Select(x => x.StartsWith("\"") ? x : "\"" + x + "\"").ToList();
r.Headers.Remove(headerName);
r.Headers.Add(headerName, updatedValue);
return r;
}
}
My response headers:
HTTP/1.1 200 OK
Date: Fri, 25 Jan 2013 16:49:29 GMT
FiddlerTemplate: True
Content-Length: 310
Content-Type: image/gif
ETag: rtt123
I'm using RestSharp in Windows Phone 8.1 application. RestClient throws exception when server returns response with code different than 200. Wiki says that I should get response with correct status code. I want to get content of response because server returns error messages.
private async Task<T> ExecuteAsync<T>(IRestRequest request)
{
if (!_networkAvailableService.IsNetworkAvailable)
{
throw new NoInternetException();
}
request.AddHeader("Accept", "application/json");
IRestResponse<T> response;
try
{
response = await _client.Execute<T>(request); //here I get exception
}
catch (Exception ex)
{
throw new ApiException();
}
HandleApiException(response);
return response.Data;
}
private void HandleApiException(IRestResponse response)
{
if (response.StatusCode == HttpStatusCode.OK)
{
return;
}
//never reach here :(
ApiException apiException;
try
{
var apiError = _deserializer.Deserialize<ApiErrorResponse>(response);
apiException = new ApiException(apiError);
}
catch (Exception)
{
throw new ApiException();
}
throw apiException;
}
sample of server response:
HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 86
Content-Type: application/json;charset=UTF-8
Expires: -1
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: *
X-Powered-By: ASP.NET
Date: Mon, 28 Sep 2015 12:30:10 GMT
Connection: close
{"error":"invalid_token","error_description":"The user name or password is incorrect"}
If you are working under Windows Phone 8.1, you're using RestSharp Portable (https://github.com/FubarDevelopment/restsharp.portable) (probably).
Use this:
var client = new RestClient();
client.IgnoreResponseStatusCode = true;
With this, you don't get exception with 404 for example.
I hope it will be helpful :)
I'm trying to set cache headers in ASP.NET MVC Web API, but the response from IIS suggests that the CacheControl values set are being ignored.
My original assumption was that I was using the EnableCorsAttribute in System.Web.Http.Cors, which is necessary in this use case. However, even without that attribute, the response Cache-Control header is still 'private'.
Is there something I am doing wrong here?
// GET api/<version>/content
// [EnableCors(origins: "*", headers: "*", methods: "*")]
public HttpResponseMessage Get(HttpRequestMessage request)
{
int cacheMaxAgeSeconds;
string cacheMaxAgeString = request.GetQueryString("cache-max-age") ?? request.GetQueryString("cache-max-age-seconds");
string rawUri = request.RequestUri.ToString();
try
{
cacheMaxAgeSeconds = cacheMaxAgeString == null ? Config.ApiCacheControlMaxSeconds : int.Parse(cacheMaxAgeString);
}
catch (Exception ex)
{
cacheMaxAgeSeconds = Config.ApiCacheControlMaxSeconds;
//...
}
try
{
//...
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("...", Encoding.UTF8, "application/json")
};
response.Headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(cacheMaxAgeSeconds)
};
return response;
}
catch (Exception apiEx)
{
//...
}
}
Response
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Date: Thu, 23 Jul 2015 10:53:17 GMT
Server: Microsoft-IIS/7.5
Set-Cookie: ASP.NET_SessionId=knjh4pncbrhad30kjykvwxyz; path=/; HttpOnly
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Length: 2367
Connection: keep-alive
Code below sets "cache-control: public, max-age=15" correctly in vanilla WebApi application (System.Web.Http 4.0.0.0). So... it's probably not the WebApi itself that causes the issue.
You may have some magic in your project that changes cache settings (think of global action filters or something similar). Or maybe you are going through proxy which rewrites HTTP headers.
public HttpResponseMessage Get()
{
var content = new JavaScriptSerializer().Serialize(new { foo = "bar" });
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(content, Encoding.UTF8, "application/json")
};
response.Headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromSeconds(15)
};
return response;
}
// returns in the response: "Cache-Control: public, max-age=15"
The answer, having picked this up some weeks later:
Cache-Control header appears to be set to 'private' when running debug builds. The issue goes away when I run with a release build.
To add another thing that can cause this:
You run through an Owin pipeline.
In that case, you need to set the headers in an Owin middleware defined as this:
class MiddleWare : OwinMiddleware
{
public MiddleWare(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
context.Response.Headers["Pragma"] = "no-cache";
context.Response.Headers["Expires"] = "0";
await Next.Invoke(context);
}
}