A subtle clash-of-namespace bug in Commerce

Recently I got the opportunity to do Sitecore’s “Implementing Experience Commerce” training course, and get certified in the details of how Commerce works these days. While I was doing the lab exercises for the course I hit an interesting bug, which seemed like just the sort of thing that others might encounter.

So if you’re extending the Commerce OData APIs, watch out:

The lab notes for these exercises usually suggest you copy and paste the code examples into Visual Studio. But to help my understanding I was typing most of the code, in order to pay more attention to what it was doing. In one of the exercises, you have to create an API controller which can answer OData quereries for information about products in your catalog.

The Commerce APIs are modern .Net Core code, so in general their controllers return Task<IActionResult>. One of the examples in the training had a controller that looked a bit like this:

[EnableQuery]
[Route("api/Example")]
public class ExampleController : CommerceController
{
    [EnableQuery]
    [HttpGet]
    [Route("(Id={id})")]
    public async Task<IActionResult> Get(string id)
    {
        ProductExample yourData = await yourCode.ProcessThisOperation(id);
        return new ObjectResult(yourData);
    }
}

Having entered the code, I followed the lab notes’ bland assertion that I should “resolve any namespace issues with using statements”. And I did it by accepting the suggestions that Visual Studio’s intellisense was suggesting. Having done that I compiled and ran the solution. And followed the instructions to test it with Postman.

And here’s where it got confusing. My instance returned a response that initially looked valid:

{
    "@odata.context": "https://localhost:5000/Api/$metadata#Example/$entity",
    "CompositeKey": null,
    "DateCreated": "2019-03-13T16:05:36.7849024Z",
    "DateUpdated": "2019-03-13T16:05:36.7849024Z",
    "DisplayName": "",
    "FriendlyId": null,
    "Id": "fdb359edeb5442839b41ee938fe7ac6b",
    "Version": 0,
    "IsPersisted": false,
    "Name": "Entity-ProductExample-Cart01",
    "Policies": []
}

But while it’s a success response, it’s not the right one. The controller code was returning a custom type which should have added at least one more property to that json object:

public class ProductExample : CommerceEntity
{
    [Contained]
    public IEnumerable Products { get; set; }
}

But that “Products” property was not present in my json data, despite the Products property containing data when I checked via the debugger. And when I compared results with other students on the course, they did have this data.

What was up?

Well after far too much time with a diff tool and someone else’s working copy of the code I spotted a subtle mistake: When I was resolving namespaces I’d picked one wrongly…

I had picked the namespace System.Web.Http.OData when resolving where the ObjectResult class lived. That appears to work because there is a class with that name there. But it turns out it has subtly different behaviour to the one that lives under Microsoft.AspNetCore.OData. As soon as I changed that over in my code, I started to see the right data:

{
    "@odata.context": "https://localhost:5000/Api/$metadata#Example/$entity",
    "CompositeKey": null,
    "DateCreated": "2019-03-13T16:05:36.7849024Z",
    "DateUpdated": "2019-03-13T16:05:36.7849024Z",
    "DisplayName": "",
    "FriendlyId": null,
    "Id": "fdb359edeb5442839b41ee938fe7ac6b",
    "Version": 0,
    "IsPersisted": false,
    "Name": "Entity-ProductExample-Cart01",
    "Policies": [],
    "Products@odata.context": "https://localhost:5000/Api/$metadata#Example('fdb359edeb5442839b41ee938fe7ac6b')/Example",
    "Products": [
        {
            "CompositeKey": null,
            "DateCreated": "2018-01-18T13:53:30.2133302Z",
            "DateUpdated": "2018-05-27T18:10:16.6754176Z",
            "DisplayName": "Habitat Centerpiece 1.5 Cu. Ft. Countertop Convection Microwave",
            "FriendlyId": "6042749",
            "Id": "Entity-SellableItem-6042749",
            "Version": 1,
            "ListPrice": null
        }
    ]
}

So if you find yourself writing code that makes use of ObjectResult in its return types, be very careful about accepting Visual Studio’s suggestions for what namespaces to add – if you don’t use Microsoft.AspNetCore.OData then your code probably won’t work correctly.

Don’t be like me 😉

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.