1

I'm having a problem with consuming OData Services using Breeze, I set up a Web API OData service by following this guide, from Fiddler it works excellent as expected, but when I try to use it with breeze it fails and gives an error message of "OK":

[Q] Unhandled rejection reasons (should be empty):Error: OK

Using fiddler I see it goes and queries for metadata and then it queries for the entities which are returned correctly, what could be the problem here?

breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager(serverAddress);

var query = new breeze.EntityQuery.from("Laboratories");

manager.executeQuery(query).then(function (data) {
    ko.applyBindings(data);
}).fail(function (e) {
    alert(e);
});

I enabled CORS by using the nightly build of ASP.NET Web API CORS support, it all works fine and I can retrieve the entities since I can see in fiddler that they are returned ... it's just that it doesn't go to the then promise instead it lands in fail.

UPDATE:

In response to @Ward testing from newly created projects I did the following:

PROJECT 1

Created a Web API Project.

Added Microsoft ASP.MET Web API Cross-Origin Resource Sharing (CORS) Reference from Nuget.

Added the following Controller:

namespace CORSBreezeTest1.Controllers
{
    public class ValuesController : EntitySetController<Value, int>
    {
        ValuesDbContext _context = new ValuesDbContext();

        [Queryable]
        public override IQueryable<Value> Get()
        {
            return _context.Values;
        }

        protected override Value GetEntityByKey(int key)
        {
            return _context.Values.Find(key);
        }

        protected override Value CreateEntity(Value entity)
        {
            Value value = _context.Values.Find(entity.Id);
            if (value != null)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Conflict));
            }
            _context.Values.Add(entity);
            _context.SaveChanges();
            return entity;
        }

        protected override int GetKey(Value entity)
        {
            return entity.Id;
        }

        protected override void Dispose(bool disposing)
        {
            _context.Dispose();
            base.Dispose(disposing);
        }
    }
}

And the following Code First Database:

namespace CORSBreezeTest1
{
    public class ValuesDbContext : DbContext
    {
        public ValuesDbContext()
            : base("DefaultConnection")
        {

        }

        public DbSet<Value> Values { get; set; }
    }

    public class Value
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        public string Name { get; set; }

        public int Quantity { get; set; }
    }
}

Added the following lines in WebApiConfig.cs

public static void Register(HttpConfiguration config)
{

 // Default code left out here ...

 config.Routes.MapODataRoute("Values", "odata", GetEdmModel());
 config.EnableQuerySupport();
 config.EnableCors(new EnableCorsAttribute("*", "*", "*"));

}

private static IEdmModel GetEdmModel()
{
    ODataModelBuilder builder = new ODataConventionModelBuilder();
    builder.Namespace = "CORSBreezeTest1";
    builder.EntitySet<Value>("Values");
    return builder.GetEdmModel();
}

PROJECT 2 Then created another Web API Project.

Added Breeze for ASP.NET Web API Projects Nuget Package

Added datajs Nuget Package.

Added the following lines of code to Index.cshtml:

<p data-bind="visible: !results">Fetching data ... </p>
<ul data-bind="foreach: results, visible: results" style="display: none">
    <li>
        <span data-bind="text: Name"></span>
        <span data-bind="text: Quantity"></span>
    </li>
</ul>


@section Scripts {
    <script src="~/Scripts/knockout-2.2.0.debug.js"></script>
    <script src="~/Scripts/q.js"></script>
    <script src="~/Scripts/datajs-1.1.0.js"></script>
    <script src="~/Scripts/breeze.min.js"></script>
    <script type="text/javascript">
        $(function () {
            breeze.config.initializeAdapterInstances({ dataService: "OData" });
            var manager = new breeze.EntityManager("http://serverAddress/odata")
            var query = new breeze.EntityQuery.from("Values");
            manager.executeQuery(query).then(function (data) {
                ko.applyBindings(data);
            }).fail(function (e) {
                alert(e);
            });
        });
    </script>
}

Tested as is and it worked since both websites are on localhost.

Published PROJECT 1 to a web server so that the test will actually see different origins, and tested.

enter image description here

And this is what Nugget saw:

enter image description here

The first request headers are OPTIONS

OPTIONS /odata/Values HTTP/1.1

And the second request headers are GET

GET /odata/Values HTTP/1.1

And if I change my fail code to:

fail(function (e) {
 ko.applyBindings(e.body.value);
});

And my knockout code to:

<p data-bind="visible: !$data">Fetching data ... </p>
<ul data-bind="foreach: $data, visible: $data" style="display: none">
    <li>
        <span data-bind="text: Name"></span>
        <span data-bind="text: Quantity"></span>
    </li>
</ul>

Voila! It came through with the data:

enter image description here

And this is what the Console saw:

SEC7118: XMLHttpRequest for http://serverAddress/odata/$metadata required Cross Origin Resource Sharing (CORS). 
localhost:53317
SEC7119: XMLHttpRequest for http://serverAddress/odata/$metadata required CORS preflight. 
localhost:53317
SEC7118: XMLHttpRequest for http://serverAddress/odata/Values required Cross Origin Resource Sharing (CORS). 
localhost:53317
SEC7119: XMLHttpRequest for http://serverAddress/odata/Values required CORS preflight. 
localhost:53317
[Q] Unhandled rejection reasons (should be empty):Error: OK 

PROJECTS 1 & 2 using the BreezeControllerAttribute

If I in another test add a new controller following Breeze Nuget example and add Breeze for ASP.NET Web API project Nuget package and add the following controller:

namespace CORSBreezeTest1.Controllers
{
    [BreezeController]
    public class BreezeValuesController : ApiController
    {
        readonly EFContextProvider<ValuesDbContext> _context =
            new EFContextProvider<ValuesDbContext>();

        [HttpGet]
        public string Metadata()
        {
            return _context.Metadata();
        }

        [HttpGet]
        public IQueryable<Value> Values()
        {
            return _context.Context.Values;
        }

        [HttpPost]
        public SaveResult SaveChanges(JObject saveBundle)
        {
            return _context.SaveChanges(saveBundle);
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
    }
}

And then modify the client as following:

//breeze.config.initializeAdapterInstances({ dataService: "OData" });
var manager = new breeze.EntityManager("http://serverAddress/breeze/BreezeValues")

Then the requests change:

enter image description here

And everything works ... I'm not sure if in part is something that EntitySetController handles requests differently or is Breeze making different requests when changing the dataService.

sergioadh
  • 1,461
  • 1
  • 16
  • 24
  • Whenever I get the [should be empty] error in Chrome it is due to not resolving a promise. Just for my understanding you aren't stating anywhere to query locally or from local cache, correct? – PW Kad Jul 01 '13 at 16:09
  • No, I have to rewrite the question upon some things I've found ... I am querying another website so I enabled CORS on the server ... using fiddler to query works out correctly, just querying from another site is where it fails although in fiddler I can see that when it queries it does return the results ... if I use the ASP.NET Web API OData nuget package as in the guide it does not work with breeze giving that result but if I go the way of the Breeze Web API nuget package it works ... – sergioadh Jul 01 '13 at 16:35
  • also if I use the same code in the same site that hosts the Web API it works flawlessly ... – sergioadh Jul 01 '13 at 16:36

3 Answers3

0

I don't know the answer yet. But there are a couple of misunderstandings in your question. First, CORS is a browser convention. It is a (relatively) safe way to work around the browser "Same Origin" policy.

That's a browser policy. Fiddler is not a browser. It is not constrained by "Same Origin" policies and can happily read and write across domain. So you can't tell if the server is properly configured for CORS based on what happens in Fiddler.

And of course "the same code in the same site that hosts the Web API works flawlessly"; you aren't violating the "Same Origin" policy so CORS is not involved.

You need to test your server configuration by writing a browser client application that launches from a site other than the host of your Web API. It doesn't have to be a breeze client. A simple AJAX call to an endpoint will do. You might as well write another simple, non-Breeze Web API controller while you're at it. Keep both controllers really simple. Keep your test client really simple.

My bet is that you will have the same trouble with both breeze-enabled and vanilla Web API controller. You'll power through that. And when you do, it should work for both the breeze and vanilla controllers. If you can show that your client works with one but not the other, come back and give us the code.

Sorry for your pains.

Ward
  • 17,793
  • 4
  • 37
  • 53
  • It actually works with simple ajax calls and from decorating the controller with the BreezeControllerAttribute it works fine ... and I mentioned I saw things in fiddler because when I launched my test web app fiddler captured the communications and showed the results ... actually if I process things through the fail promise I can actually retrieve the data by going through e.body.value ... But I will create two new web api's to test maybe I did something that makes it work wrong ... and I'll come back – sergioadh Jul 01 '13 at 22:40
  • And maybe we have a problem interpreting the server response. I'm eager to learn what you find and have an example to explore. If i had more time I'd spin something up myself. The Web API CORS support isn't official yet (I don't think) so I have to prioritize. – Ward Jul 01 '13 at 23:49
  • Added testing results, if I missed anything let me know, thanks – sergioadh Jul 02 '13 at 16:22
  • just to clarify about CORS, I tested using the [BreezeSimpleCorsHandler](https://github.com/IdeaBlade/Breeze/blob/master/Samples/Todo/Todo/App_Start/BreezeSimpleCorsHandler.cs) from the ToDos sample ... works just fine either the breeze way or web api way for enabling CORS ... Breeze is maybe expecting different behavior? – sergioadh Jul 02 '13 at 18:45
  • Very helpful, thanks. Now we have something to dig into. My instinct is that something is throwing an exception inside Breeze as it tries to convert the raw JSON data into entities. Two more questions before I really dig in: what is your version of Breeze (look at top of breeze.debug.js)? Does the browser console window report any errors or is what you showed the extent of it (I fear the latter)? – Ward Jul 02 '13 at 22:23
  • I added another test for you to see maybe it helps defining where the problem is exactly. – sergioadh Jul 03 '13 at 02:33
  • Thanks. We will dig in. One more question (until the next one :-) ... are you changing the Breeze.config for the dataservice as you switch between the Web API controller and the OData controller? To be clear, how do you configure Breeze on the client for each scenario. Thx again – Ward Jul 03 '13 at 04:08
  • Well they both coexist with different routes with no problem ... ODataController I used `breeze.config.initializeAdapterInstances({ dataService: "OData" });` and for the Web API Controller I just commented it out. That would be the only difference and the route ... ODataController lives in `/odata/` and Web API Controller lives in `/breeze/BreezeValues/` – sergioadh Jul 03 '13 at 12:38
  • That's what I needed. We will dig in. Appreciate your patience – Ward Jul 03 '13 at 16:23
  • Had any chance to look at this? – sergioadh Jul 09 '13 at 01:19
  • I think we have it fixed in the forthcoming release ... as in days. You will need to use the [BreezeQueryable] attribute; we can't detect with [BreezeController] attribute that you'll need this OData query treatment. But the corrected [BreezeQueryable] attribute will apply ... in the next release. – Ward Jul 10 '13 at 23:18
  • @Ward don't mean to be off topic but seems like I have a very similar situation outlined here => http://stackoverflow.com/questions/17986818/net-webapi-odata-breeze-q-unhandled-rejection-reasons-should-be-empty do you reckon [BreezeQueryable] would fix my issue? the release you mentioned, is it out? – August Bloom Aug 07 '13 at 17:01
0

The only way to get it to work was to use the BreezeControllerAttribute from Breeze.WebApi following the breeze exact way of using the api. Not using EntitySetController and going back to a regular ApiController. Detailed explanation in the question itself.

[BreezeController]
public class BreezeValuesController : ApiController
{
    // Methods here
}
sergioadh
  • 1,461
  • 1
  • 16
  • 24
0

You just need to add this extra parameter DataServiceVersion, MaxDataServiceVersion configuring enableCors.

config.EnableCors(new EnableCorsAttribute("*", "*", "*", "DataServiceVersion, MaxDataServiceVersion"));
Rahil Wazir
  • 10,007
  • 11
  • 42
  • 64