Compression response filter fails on breeze.js Metadata call - filter

I have an http module where I'm adding a response filter below for compression. This works for all API calls except for 1, the call to MetaData. If I remove the [BreezeController] decoration it works fine. I think it has to do with action filter attribute that converts the string return type into an HttpResponse return type with string content.
The error I'm getting is " Exception message: The stream state of the underlying compression routine is inconsistent."
I've done some testing where a method thats defined to return an HttpResponse works fine. So I think its the scenario where the method is defined to return string, and then the action filter changes it to HttpResponse at runtime.
Any ideas how I can get this to work?
Here's the response filter being added in BeginRequest:
HttpApplication app = (HttpApplication)sender;
// Check the header to see if it can accept compressed output
string encodings = app.Request.Headers.Get("Accept-Encoding");
if (encodings == null)
return;
Stream s = app.Response.Filter;
encodings = encodings.ToLower();
if (encodings.Contains("gzip"))
{
app.Response.Filter = new GZipStream(s, CompressionMode.Compress);
app.Response.AppendHeader("Content-Encoding", "gzip");
}

Don't know the specifics of what you're doing but I know that the [BreezeController] attribute strips out filters and adds back just the ones that breeze wants.
One approach might be to define a separate controller (ModelMetadataController) that only serves the metadata. This controller doesn't have the [BreezeController] attribute; it's a plain old Web API controller.
Then you create a "Breeze controller" (ModelController) with all of the usual methods except the Metadata method.
You call the metadata controller from the client during app launch via MetadataStore.fetchMetadata just to get metadata.
Once you have populated a metadataStore in this fashion, you use it in your EntityManager which sends query and save requests to the "real" Web API data controller.
The client code might look something like this:
var ds = new breeze.DataService({
serviceName: 'breeze/Model' // the breeze query & save controller
});
var ms = new MetadataStore({
namingConvention: breeze.NamingConvention.camelCase, // assuming that's what you want
});
ms.addDataService(ds); // associate the metadata-to-come with the "real" dataService
var manager = new breeze.EntityManager({
dataService: ds,
metadataStore: ms
});
// the fun bit: fetch the metadata from a different controller
var promise = ms.fetchMetadata('breeze/ModelMetadata') // the metadata-only controller!
return promise; // wait on it appropriately

Related

How to download file using IHttpClientFactory in .Net Core 2.2?

In my web application (.net Core 2.2) I want to add the functionality of downloading a PDF file.
When the user clicks "download" - POST is sent to WebAPI and the server ask different server for PDF file by hardcoded URL (invisible to the user) and the file is passed to the user.
I was trying to use IHttpClientFactory
Startup:
services.AddHttpClient("demo", c =>
{
c.BaseAddress = new Uri("http://www.africau.edu/");
c.DefaultRequestHeaders.Add("Accept", "application/pdf");
});
Method for returns PDF file:
public async Task<FileStreamResult> GetPdfFile()
{
var client = _httpClientFactory.CreateClient("demo");
var uri = new UriBuilder(client.BaseAddress + "images/default/sample.pdf").Uri;
var stream = await client.GetStreamAsync(uri);
return new FileStreamResult(stream, new MediaTypeHeaderValue("application/pdf"));
}
Unfortunately it returns file download.json with: {"fileStream":{"needsDrain":true,"canRead":true,"canWrite":false,"canSeek":false
#EDIT
it seems that it's because I had Task<IActionResult> in my Controller instead of Task<FileStreamResult> but what if i prefer to returns ActionResult?
public async Task<IActionResult> GetPdf()
{
var result = await _service.GetPdfFile();
return Ok(result);
}
In your GetPdf method, you're passing a FileStreamResult to the Ok() method. This will serialize the FileStreamResult using the default serializer and send it to the client, hence why you're seeing the JSON.
Usually your service layer should not return an MVC WhateverResult object but rather an object intended to represent the actual data being retrieved - in your case this would be a PDF file. The easiest way to solve your particular case would probably be to do the following:
change GetPdfFile to return Task<FileStream> instead of Task<FileStreamResult>
remember to change its return statement to return stream;
change the return statement of GetPdf to return File(result, "application/pdf");
This will allow your service layer to return the PDF file as a stream, and then have your GetPdf endpoint return that stream as a file with the application/pdf file type.

Switching ASP.Net Web API returns between XML and JSON; where's it done?

I have an ASP.Net Web API project. In my controller is this simple 'boiler plate' API method:
using System.Web.Http;
public class DataController : ApiController
{
private static Random _Random = new Random();
[Route("api/getrandomdoubles/{count:int}")]
[AcceptVerbs("GET", "POST")]
public double[] GetRandomDoubles(int count)
{
var doubles = new double[count];
for (var i = 0; i < count; i++)
{
doubles[i] = _Random.NextDouble();
}
return doubles;
}
}
(N.B. I've cut out the other methods.)
If I call this in the browser thus http://localhost:1165/api/GetRandomDoubles/2 I get XML returned:
<ArrayOfdouble xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<double>0.3777879822895806</double>
<double>0.46401416811347668</double>
</ArrayOfdouble>
And if I call it from JavaScript thus:
d3.json('api/getrandomdoubles/2', function (error, data) {
//Do stuff
});
I get back JSON [0.6679551008473873,0.9205140638726363].
What is deciding when my controller API method returns XML and when it returns JSON? I'm guessing it is decided based on the HTTP verb, i.e. PUT or GET but I cannot see where that is specified. How would I control the return type myself?
========== EDIT ==========
I have just realised that this is browser specific. Calling http://localhost:1165/api/GetRandomDoubles/2 in IE returns JSON, calling it in Chrome returns XML.
It is called as Content Negotiation in Web API.
First, the pipeline gets the IContentNegotiator service from the HttpConfiguration object. It also gets the list of media formatters from the HttpConfiguration.Formatters collection.
Next, the pipeline calls IContentNegotiatior.Negotiate, passing in:
The type of object to serialize
The collection of media formatters
The HTTP request
The Negotiate method returns two pieces of information:
Which formatter to use
The media type for the response
If no formatter is found, the Negotiate method returns null, and the client recevies HTTP error 406 (Not Acceptable).

Do Get request with a complex type parameter in the request body with web api

I want to do an integration test for the below action.
How can I pass my requestDto object in the integration test?
Neither the GetAsync nor SendAsync method has an overload parameter to pass a custom object to the server.
[Route("{startDate:datetime}")]
[HttpGet]
public HttpResponseMessage Get(DateTime startDate, [FromBody]LessonplannerGetRequest request)
{
request.StartDate = startDate;
var lessonplannerResponse = _service.GetPeriodsByWeekStartDate(request);
return Request.CreateResponse<LessonplannerResponse>(HttpStatusCode.OK, lessonplannerResponse);
}
[Test]
public void Get_Lessons_By_Date()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, _server.BaseAddress + "/api/lessonplanner/2014-01-14");
var myRequestDto = new LessonplannerGetRequest();
// Act => QUESTION: HOW do I pass the myRequestDto ???
var response = _client.SendAsync(request, new CancellationToken()).Result;
// Assert
Assert.That(response.StatusCode == HttpStatusCode.OK);
}
UPDATE
As Darrel Miller said:"Technically HTTP says you can send a body, it just says the body doesn't mean anything and cannot be used. HttpClient won't let you send one."
I post here my integration test with HttpClient doing a Get request with complex type + FromBody:
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, _server.BaseAddress + "/api/lessonplanner/2014-01-14");
var myRequestDto = new LessonplannerGetRequest{ FirstDayOfWeek = DayOfWeek.Sunday, SchoolyearId = 1, StartDate = DateTime.Today};
request.Content = new ObjectContent<LessonplannerGetRequest>(myRequestDto, new JsonMediaTypeFormatter());
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Act
var response = _client.SendAsync(request, new CancellationToken()).Result;
// Assert
Assert.That(response.StatusCode == HttpStatusCode.OK);
Of course is this is not the Http way some might consider doing it differentlly sending complex type via FromUri/query string.
HTML specifications says you cannot send a GET with a body.
HTTP specs allows it.
WebAPI allows it, because it is a service/REST and implements HTTP but not HTML, but many clients and browser won't allow it because they implement both specs and try to be strict.
As for the specifications (RFC1866, page 46; HTML 4.x section 17.13.3) itself, it states:
If the method is "get" and the action is an HTTP URI, the user agent takes the value of action, appends a `?' to it, then appends the form data set, encoded using the "application/x-www-form-urlencoded" content type.
(e.g. if you do a <form> with GET, it will parse all the form params and set them in the query string ?a=b).
In term of pure HTTP and in the context of REST services, nothing prevents that behavior, but not all clients will be able to handle it. It's mostly a best-practice advise when it comes to REST/WebAPI to not handle body data from HttpGet, only URI data (the opposite, POST /action?filter=all is usually tolerated for metadata/action qualifiers, but that's another discussion).
So yeah, it's at your own risk, even if used only internally. As not all clients handle it (e.g. HttpRequestMessage), so you might run into trouble like you have.
You should NOT pass a GET body with HTTPClient.

WebApi Output Cache

I'm trying to implement an output cache in WebApi that can cache the responses already processed by filters and generate responses that are not processed by formatters.
From what I've seen ActionFilterAttribute's OnActionExecuting and OnActionExecuted are execute before the serialization formatters and so if you cache a response, at a cache hit you will respond the exact same content and that content will be serialized again to transmission.
As a possible solution in MVC I think that you can do this by implementing a IResultFilter that override OnResultExecuted by caching the serialized response. With this approach I don't know how to intercept the request handling to avoid the serialization formatters, I think that a possible solution to intercept is create a custom ActionResult to be handled directly by IResultFilter. Please note that this solution is not suitable for me because I'm implementing OutputCache in a WebApi application.
While writing the response, formatters in Web API come into action for HttpContents of type ObjectContent only.
In you OnActionExecuted method, you could force the serialization to happen by doing something like below and then set the response content as StreamContent (this way the formatters wouldn't be coming into picture):
An example below:
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
HttpResponseMessage response = actionExecutedContext.Response;
if (response != null && response.IsSuccessStatusCode)
{
ObjectContent originalContent = response.Content as ObjectContent;
if (originalContent != null)
{
MemoryStream ms = new MemoryStream();
// NOTE:
// 1. We are forcing serialization to occur into a buffered stream here
// 2. This can cause exception. You can leave it as it is and Web API's exception handling mechanism should
// do the right thing.
originalContent.CopyToAsync(ms).Wait();
// reset the position
ms.Position = 0;
StreamContent newContent = new StreamContent(ms);
// Copy the headers
foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
{
newContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
//dispose the original content
originalContent.Dispose();
//TODO: cache this new httpcontent 'newContent' (so we are caching both serialized body + headers too)
//Set the response
//NOTE: This newContent will not hit the formatters
actionExecutedContext.ActionContext.Response.Content = newContent;
}
}
}

servicestack - caching a service response using redis

I have a servicestack service which when called via the browser (restful) Url ex:http://localhost:1616/myproducts, it works fine.
The service method has RedisCaching enabled. So first time it hits the data repository and caches it for subsequent use.
My problem is when I try calling it from a c# client via Soap12ServiceClient. It returns the below error:
Error in line 1 position 183. Expecting element '<target response>'
from namespace 'http://schemas.datacontract.org/2004/07/<target namespace>'..
Encountered 'Element' with name 'base64Binary',
namespace 'http://schemas.microsoft.com/2003/10/Serialization/'.
Below is my Client code:
var endpointURI = "http://mydevelopmentapi.serverhostingservices.com:1616/";
using (IServiceClient client = new Soap12ServiceClient(endpointURI))
{
var request = new ProductRequest { Param1 = "xy23432"};
client.Send<ProductResponse>(request);
}
It seems that the soapwsdl used is giving the problem, but I appear to have used the defaults as generated by servicestack..
Any help will be much appreciated.
Update
I was able over come this error by changing the cache code at the service end:
Code that returned error at client end:
return RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() =>
new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
});
Code that works now:
var result = this.CacheClient.Get<ProductResponse>(cacheKey);
if (result == null)
{
this.CacheClient.Set<ProductResponse>(cacheKey, productResult);
result = productResult;
}
return result;
But I am still curious to know why the first method (RequestContext.ToOptimizedResultUsingCache) returned error at c# client?
But I am still curious to know why the first method (RequestContext.ToOptimizedResultUsingCache) returned error at c# client?
From what I can tell, the ToOptimizedResultUsingCache is trying to pull a specific format (xml, html, json, etc) out of the cache based on the RequestContext's ResponseContentType (see code here and here). When using the Soap12ServiceClient the ResponseContentType is text/html (not sure if this is correct/intentional within ServiceStack). So what ToOptimizedResultUsingCache is pulling out of the cache is a string of html. The html string is being returned to the Soap12ServiceClient and causing an exception.
By pulling directly out of the cache you are bypassing ToOptimizedResultUsingCache's 'format check' and returning something the Soap12ServiceClient can handle.
** If you are using Redis and creating your key with UrnId.Create method you should see a key like urn:ProductResponse:{yourkey}.html
Thanks for your response paaschpa.
I revisited the code and I was able to fix it. Since your response gave me the direction, I have accepted your answer. Below is my fix.
I moved the return statement from RequestContext to the response DTO.
Code which throws error when used via c# client (code was returning entire requestcontext):
return RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() =>
new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
});
Fixed Code (return moved to response DTO):
RequestContext.ToOptimizedResultUsingCache(this.CacheClient, cacheKey,
() => {
return new ProductResponse(){CreateDate = DateTime.UtcNow,
products = new productRepository().Getproducts(request)
}
});

Resources