A better way to consume BC OData from .NET and C#

In this video I show how to consume OData from Microsoft Dynamics 365 Business Central with .NET and C#. Using the OData Connected Service.

https://youtu.be/UQRHHBabn0Y

In this video, Erik demonstrates a cleaner, more structured approach to consuming Business Central OData feeds from .NET and C# — using the OData Connected Service extension for Visual Studio instead of raw HTTP calls. Rather than manually parsing JSON responses, this method auto-generates strongly-typed proxy classes that make your BC data feel like native C# objects.

The Problem with Raw HTTP

Business Central exposes OData feeds over HTTPS, and you can easily view the data as JSON right in your browser. If you need to consume that data programmatically, you could use HttpClient, WebClient, or HttpWebRequest to grab the raw JSON. But there’s a better way — one that gives you IntelliSense, strong typing, and auto-generated plumbing code.

Setting Up the Project

Erik starts with a brand new .NET 5.0 console application in Visual Studio — just the default “Hello World” template. The project file shows the key NuGet packages needed:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.OData.Client" Version="7.9.0" />
    <PackageReference Include="Microsoft.OData.Core" Version="7.9.0" />
    <PackageReference Include="Microsoft.OData.Edm" Version="7.9.0" />
    <PackageReference Include="Microsoft.Spatial" Version="7.9.0" />
    <PackageReference Include="System.Text.Json" Version="5.0.2" />
  </ItemGroup>

  <ItemGroup>
    <WCFMetadata Include="Connected Services" />
  </ItemGroup>

</Project>

Installing the OData Connected Service Extension

Just as you can extend Business Central with AL extensions, you can extend Visual Studio with its own extensions. Go to Manage Extensions in Visual Studio and search for “OData Connected Service” on the marketplace. Install it (requires a Visual Studio restart), and you’ll have a new option available under your project’s dependencies.

Generating the Proxy Classes

Once the extension is installed, right-click on Dependencies in your project and select Add Connected Service. You’ll see the OData Connected Service listed as an option. Here’s where it gets interesting — and where a few gotchas come up.

Finding the Right URL

The connected service wizard needs the metadata endpoint, not a specific entity URL. If your Business Central OData URL looks like this:

http://bc18:7048/BC/ODataV4/Company('Hougaard')/Chart_of_Accounts

You need to strip it down to the service root and append $metadata:

http://bc18:7048/BC/ODataV4/$metadata

This returns an XML document describing all exposed endpoints, their fields, available functions, and actions — everything the tool needs to generate your proxy classes.

Handling Authentication in the Wizard

The wizard doesn’t have built-in username/password fields — there are simply too many authentication methods to cover in a single UI. Instead, it lets you add custom HTTP headers. For basic authentication, add a header like:

Authorization: Basic ZGVtbzpEZW1vMjAwNCE=

The value after “Basic” is your username:password Base64-encoded (in this case, demo:Demo2004!).

Configuring the Generation

After authenticating, the wizard shows all discovered services (85 in Erik’s case), functions, and actions. You can select all of them. A few important settings:

  • File name: Give the generated file a meaningful name (e.g., “bc18”)
  • Namespace: By default, the generated code uses the namespace “NAV” with a container also called “NAV” — which is confusing. Erik overrides this to use “BC” as the namespace to avoid NAV.NAV references.

Click Finish, and the tool generates a complete C# proxy file with strongly-typed classes for every exposed entity.

Writing the Code

With the proxy generated, consuming OData becomes remarkably clean. Here’s the final program:

using System;

namespace ODataFromCSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceRoot = "http://bc18:7048/BC/api/v2.0/companies(d00aced7-6ec1-eb11-b388-da215ec232af)/";
            var context = new BCAPI.NAV(new Uri(serviceRoot));
            context.BuildingRequest += Context_BuildingRequest;

            var data = context.Vendors.Execute();
            foreach (var vendor in data)
                Console.WriteLine("{0} {1}", vendor.Number, vendor.DisplayName);

            var serviceRoot2 = "http://bc18:7048/BC/ODataV4/Company('Hougaard')/";
            var context2 = new BC18.NAV(new Uri(serviceRoot2)); 
            context2.BuildingRequest += Context_BuildingRequest;

            var data2 = context2.Chart_of_Accounts.AddQueryOption("$filter", "Net_Change gt 0");
            foreach (var account in data2)
                Console.WriteLine("{0} {1} {2}", account.No, account.Name, account.Net_Change);
        }

        private static void Context_BuildingRequest(object sender, Microsoft.OData.Client.BuildingRequestEventArgs e)
        {
            e.Headers.Add("Authorization", "Basic ZGVtbzpEZW1vMjAwNCE=");
        }
    }
}

Step-by-Step Breakdown

  1. Define the service root URL — This is the base OData endpoint, including the company identifier.
  2. Create the context — The auto-generated NAV class (or whatever you named it) acts as your connection object. You instantiate it with a Uri pointing to the service root.
  3. Subscribe to the BuildingRequest event — This is a C# event on the context that fires before every request. It’s the perfect place to inject authentication headers.
  4. Execute queries — Call .Execute() on an entity set like Chart_of_Accounts or Vendors, and iterate over the results with a simple foreach.

Handling the Company Name

One gotcha Erik encountered: Business Central may return an error saying “default company cannot be found.” You need to specify the company name in your URL. The cleanest approach shown in the video is to include it directly in the service root URL:

var serviceRoot = "http://bc18:7048/BC/ODataV4/Company('Hougaard')/";

Alternatively, you can inject it in the BuildingRequest event by modifying the request URI — replacing part of the URL string to include the company path segment. It’s a bit of a hack, but it works and only needs to be done once:

e.RequestUri = new Uri(e.RequestUri.ToString().Replace("ODataV4/", "ODataV4/Company('Hougaard')/"));

Adding Query Options

The proxy also supports OData query options. Notice how the Chart of Accounts query adds a filter:

var data2 = context2.Chart_of_Accounts.AddQueryOption("$filter", "Net_Change gt 0");

This translates to the standard OData $filter query parameter, so you only get accounts with a positive net change.

Why This Approach is Better

The beauty of this approach is in the developer experience. Once you’ve done the plumbing (authentication, company name injection), the actual business logic is just a few lines of clean, readable code:

var data = context.Chart_of_Accounts.Execute();
foreach (var account in data)
    Console.WriteLine("{0} {1} {2}", account.No, account.Name, account.Net_Change);

You get full IntelliSense on property names like account.No, account.Name, and account.Net_Change. No funky JSON parsing, no manual deserialization, no guessing at field names. The proxy classes give you a strongly-typed, discoverable API that feels native to C#.

Summary

To consume Business Central OData from C# the “better way”:

  1. Install the OData Connected Service extension in Visual Studio
  2. Point it at your BC OData $metadata endpoint with proper authentication headers
  3. Generate the proxy classes (give them a sensible namespace)
  4. Create a context using the generated container class and your service root URI
  5. Subscribe to BuildingRequest to handle authentication and company name injection
  6. Query your data with strongly-typed, readable C# code

From here, the possibilities extend to updating data, calling bound and unbound functions/actions, and implementing OAuth authentication — all building on this same foundation.