Sam
Sam

Reputation: 14586

breezejs inlineCount is not working (CORS cross domain http request)

here's my query:

 var query = breeze.EntityQuery
                .from("Mandates").skip(offset).take(pageSize).inlineCount(true);

        return manager.executeQuery(query);

In the response headers, I can see the count is returned by the server:

X-InlineCount:63

However, in the succeed handler when I do:

 var results = data.results;
 var recordCount = data.inlineCount;

results contains the correct records. However data.inlineCount is null.

How come ?

EDIT

getAllResponseHeaders does not return all headers ! I've just noticed most of them are missing, including X-InlineCount.

I believe this is because I'm doing a cross-domain http request (website is in localhost but my webapi (web service) is on a server on the domain). How can this be fixed ?

Upvotes: 0

Views: 876

Answers (2)

jvrdelafuente
jvrdelafuente

Reputation: 2002

I had the same problem. You have to configure your server to expose the X-InlineCount header. If you are using a project ASP .NET Web API you have to put the next lines in your web.config:

  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
        <add name="Access-Control-Allow-Methods" value="GET,POST,OPTIONS" />
        <add name="Access-Control-Expose-Headers" value="X-InlineCount" />
      </customHeaders>
    </httpProtocol>

Also, I had problem with firefox, but in the last version of firefox, the problem is solved.

(Yo don't need to use all the lines, it's only an example) ;)

-----EDIT-----

I had forgotten, because it was log time ago. The browser ,when you are doing a CORS call, sends first a Option call to check if it is possible to do a CORS call and what things are allowed. So you have to implement a message handler in your WEB API project for manage this OPTION call. The implementation is the next:

public class OptionsHttpMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request.Method == HttpMethod.Options)
            {
                var apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();


                var controllerRequested = request.GetRouteData().Values["controller"] as string;
                var supportedMethods =  apiExplorer.ApiDescriptions
                    .Where(d =>
                    {

                        var controller = d.ActionDescriptor.ControllerDescriptor.ControllerName;
                        return string.Equals(
                            controller, controllerRequested, StringComparison.OrdinalIgnoreCase);
                    })
                    .Select(d => d.HttpMethod.Method)
                    .Distinct();

                if (!supportedMethods.Any())
                    return Task.Factory.StartNew(
                        () => request.CreateResponse(HttpStatusCode.NotFound));

                return Task.Factory.StartNew(() =>
                {
                    var resp = new HttpResponseMessage(HttpStatusCode.OK);
                    resp.Headers.Add("Access-Control-Allow-Origin", "*");
                    resp.Headers.Add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept, Security,Token-Access");
                    resp.Headers.Add("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
                    resp.Headers.Add("Access-Control-Expose-Headers", "X-InlineCount");

                    return resp;
                });
            }

            return base.SendAsync(request, cancellationToken);
        }
    }

You can omit the next section if you put that in your web.config.

resp.Headers.Add("Access-Control-Allow-Origin", "*");
resp.Headers.Add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Accept, Security,Token-Access");
resp.Headers.Add("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
resp.Headers.Add("Access-Control-Expose-Headers", "X-InlineCount");

I hope that this help to solve your problem.

PD: Sorry for my english, it is not my first language.

Upvotes: 1

Ward
Ward

Reputation: 17863

We have confirmed the problem. We plan to fix it by moving the inlineCount out of the headers and into the results payload where it probably should have been in the first place. No firm date for that but soon.

UPDATE: 4/18/2013. We have implemented this fix internally and it passes our tests. We'll deploy in the next NuGet (the one after 1.3.0). I'll update this answer again when that happens.

We reproduced the problem, as anyone can do, by tweaking this jsFiddle.

The fiddle makes requests to a Breeze server that exposes Todos. Naturally, the Breeze server is in a different domain than jsFiddle and therefore requires CORS to perform query and save operations.

I changed the getAllTodos method to the following:

function getAllTodos() {
    var query = breeze.EntityQuery.from("Todos")

    // added for Cross-Origin inlineCount header testing
    .orderBy('Description')
    .skip(1).take(3)
    .inlineCount(true);

    log("Getting Todos");
    return manager.executeQuery(query)
    .then(querySucceeded).fail(failed);

    function querySucceeded(data) {
        var count = data.results.length;
        log("Retrieved " + count);

        // added for Cross-Origin inlineCount header testing
        log("Inline count: "+ data.inlineCount);
        log("Headers: " + data.XHR.getAllResponseHeaders());

        if (!count) {
            log("No Todos"); return;
        }
        viewModel.items(data.results);
    }
}

@jvrdelafuente - I'm surprised that the Web.config change you recommended actually worked. I'd have thought that such a change to the Web.config would only affect what the server does, not what the browser does.

When I examine network traffic, I can see that the server is sending the "X-InlineCount" header, with or without your suggested change. The browser (IE10 and Chrome at least) appears to be hiding the header from jQuery.AJAX, which is responsible for AJAX communications in the typical breeze app.

I see this effect when I add the following to a query success callback:

 console.log("Headers: " + data.XHR.getAllResponseHeaders());

The log shows very few of the headers actually returned by the server.

Perhaps when you add

<add name="Access-Control-Expose-Headers" value="X-InlineCount" />

to the Web.config, the server can tell the browser that it is OK to expose the "X-InlineCount" header to the JavaScript client.

I confess I didn't investigate that possibility; perhaps it is a viable work-around in the short run.

But it's only a band-aide as far as we're concerned. We don't want developers to have to worry about this kind of thing; CORS creates enough headaches as it is. So we're going to communicate the inlineCount in the payload rather than the headers ... and remove this concern all together.

Upvotes: 3

Related Questions