Skip to main content

Notifications

Announcements

No record found.

Dynamics 365 Community / Blogs / Learn with Subs / Calling Dynamics365 Invento...

Calling Dynamics365 Inventory Visibility API using Azure Functions

Subhad365 Profile Picture Subhad365 7 User Group Leader
Hi friends, wishing you a Happy New Year 2025 😊
Let us today discuss about calling D365 Inventory Visibility APIs using Azure Functions.
We all know capabilities of D365 Inventory Visibility Add-in. To give you a short introduction –
The Inventory Visibility Add-in (also referred to as the Inventory Visibility service) gives an independent and highly scalable microservice that enables real-time on-hand inventory change postings and visibility tracking across all your data sources and channels (retail, POS, D365F&O or any source of transaction). It provides a platform that lets you manage your global inventory by using functionality that includes (but isn't limited to) the following list:
  • Centrally track the latest inventory status (such as on-hand, ordered, purchased, in-transit, returned, and quarantined) across all your data sources, warehouses, and locations by connecting your Supply Chain Management or third-party logistics data sources (such as order management systems, third-party enterprise resource planning [ERP] systems, point of sale [POS] systems, and warehouse management systems) to the Inventory Visibility service.
  • You can query on-hand stock availability and shortages, and obtain immediate responses by calling the Inventory Visibility service directly.
  • You can dodge overselling, especially when your demand comes from different channels, by making real-time soft reservations in the Inventory Visibility service.
  • Better manage promised orders and customer expectations by providing accurate current or next-available dates, so that the omnichannel available-to-promise (ATP) feature can calculate expected order fulfillment dates.
Saying that, let me give you background of you can actually connect a D365 Inventory Visibility API from any external resource:
Step 1: Request an Azure Active Directory (AAD) token by calling the token generation URL, registered by creating an Entra ID APP registration.
Step 2: Call the Inventory visibility (IV) token generation URL, by passing on the token generated from the above step.
Step 3: Call the actual IV URL endpoint to query on-hand inventory or soft reserve your items, by passing on the token generated from step-2.

 Saying that, let us create an Azure Function, that has an HTTP trigger type binding, as follows:

This would result in a project, that would contain a number of built-in lines, like this:

Remove all the code from the “Run” method, so as to make it look like this:

Note that:
  1. We are going to create a Function called: AzureFunctionIVQuery
  2. It’s a POST message
  3. The requestMessage is string variable that we have obtained by reqding the incoming JSON payload body of the function.
  4. We have initialized a variable called responsMessage, (for now it’s empty), that we will be returning eventually.

Declare all your variables in your local.settings.JSON:

Begin by declaring all your variables in your local settings JSON file:

The Client Id: is part of your AAD Entra ID APP registration
The Client secret: is part of your AAD Entra ID APP registration (you need to make decision here – should you choose to keep the Client secret here or in Azure Key Vault).
aadURL: is https://login.microsoftonline.com/<Tenant_Id>/oauth2/token
Scope: this is hardcoded to: https://inventoryservice.operations365.dynamics.com/.default, and is the URL Scope from which you need to generate the token (described in Step-2).
Context: this you can keep hardcoded as: 271f08f7-84d9-4ccd-bb85-79e595cabf94
Tenant: is part of your AAD Entra ID APP registration
Resource: this is hardcoded to “0cdb527f-a8d1-4bf8-9436-b352c68682b2”, which is Inventory Visibility Microservice
Environment: this you can find by visiting your D365 Inventory Visibility page >> Settings:

And lastly:
IVUrl: this is crafted by the following format:
<Inventory visibility endpoint url>/api/environment/<environment>/onhand/indexquery

Create a new class to obtain AAD token

Create a new class

We are creating a new class called ManageSessionStates

Create a new method to obtain token values

We would be creating a new method that will obtain new token:
public string GetToken()
{
    string responseString = string.Empty;
    using (var wb = new WebClient())
    {
        var data = new NameValueCollection();
        data["client_id"] = Environment.GetEnvironmentVariable("client_id");
        data["grant_type"] = Environment.GetEnvironmentVariable("grant_type");
        data["client_secret"] = Environment.GetEnvironmentVariable("client_secret");
        data["resource"] = Environment.GetEnvironmentVariable("resource");

        var response = wb.UploadValues(Environment.GetEnvironmentVariable("aadURL"), "POST", data);
        responseString = this.GetTokenValue(System.Text.Encoding.UTF8.GetString(response));
    }
    return responseString;
}
The Name-value collection is a variable that stores Client_Id, client_secret, resource and grant_type. This works as a key-value pair of the body-JSON payload, that is needed for generating the token. Normally the token response looks like this:
{
    "token_type": "Bearer",
    "expires_in": "3599",
    "ext_expires_in": "3599",
    "expires_on": "1735730794",
    "not_before": "1735726894",
    "resource": "0cdb527f-a8d1-4bf8-9436-b352c68682b2",
    "access_token": <token_value>
}

Hence we need to parse the value of access_token. This we can do, by using another method called: GetTokenValue –
public string GetTokenValue(string responsePayload)
{
    dynamic json = JObject.Parse(responsePayload);

    string accessToken = json.access_token;

    return accessToken;

}

Create a new method to obtain IV token value

We are creating this method that can obtain token from IV URL:
    public string GetIVToken(string aadTokenVal, ILogger log)
    {
        string URL = Environment.GetEnvironmentVariable("aadIVUrl");
        string ret = string.Empty ;

        string payload = this.CreateIVBody(aadTokenVal, log);

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);

        request.Method = "POST";

        request.ContentType = "application/JSON";

        request.ContentLength = payload.Length;

        StreamWriter requestWriter = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);
           
        requestWriter.Write(payload);

        requestWriter.Close();

        string response = String.Empty;

        try
        {
            WebResponse webResponse = request.GetResponse();
            Stream webStream = webResponse.GetResponseStream();
            StreamReader responseReader = new StreamReader(webStream);
            response = responseReader.ReadToEnd();
            ret = this.GetTokenValue(response);
            responseReader.Close();
        }

        catch (Exception e)
        {
            log.LogInformation(e.Message);
        }
        return ret;
    }
}

Look at the code, very carefully. In this case, we are not using any name-value pair and passing it in body. Instead we are passing the value of the AAD token obtained by calling another method called: createIVBody, which looks like this:

public string CreateIVBody(string aadTokenVal, ILogger log)
{
    string ivBody = string.Empty;
    using (var wb = new WebClient())
    {
        TokenStateValues data = new TokenStateValues();
        data.grant_type = Environment.GetEnvironmentVariable("grant_type");
        data.client_assertion = aadTokenVal;              
        data.Scope = Environment.GetEnvironmentVariable("Scope");
        data.Context = Environment.GetEnvironmentVariable("Context");
        data.context_type = "finops-env";
        data.client_assertion_type = "aad_app";
        ivBody = JsonConvert.SerializeObject(data);
    }
    return ivBody;
}
Here we have created one more class which is mentioned as below. And once we get the response back, again you need to get the value of the token, by parsing the JSON response, which is done as:
            ret = this.GetTokenValue(response);
Marked in yellow.

Create a new class to maintain IV variables session states

class TokenStateValues
{
    public string grant_type;
    public string client_assertion_type;
    public string client_assertion;
    public string Scope;
    public string Context;
    public string context_type;
}
This class we are calling, as mentioned in the above section, to obtain the IV URL.

Call the IV endpoint:

Create a new class called CallIVSession

This would contain variables to contain the incoming JSON payload and the token generated:
    private string inqData;
    private string tokenVal;

    public string InqData
    {
        get { return inqData; }
        set { inqData = value; }
    }

    public string TokenVal
    {
        get { return tokenVal; }
        set { tokenVal = value; }
    }

Call the inventory URL:

We need to call the inventory URL like this –
    public string callIVURL(ILogger log)
    {
        string response = string.Empty;

        string URL = Environment.GetEnvironmentVariable("IVUrl");

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);

        request.Method = "POST";

        request.ContentType = "application/json";

        request.ContentLength = inqData.Length;

        StreamWriter requestWriter = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);

        request.Headers.Add("Authorization", string.Format("Bearer {0}", tokenVal));
        request.Headers.Add("api-version", "1.0");

        requestWriter.Write(inqData);

        requestWriter.Close();

        try
        {
            WebResponse webResponse = request.GetResponse();

            Stream webStream = webResponse.GetResponseStream();

            StreamReader responseReader = new StreamReader(webStream);
            response = responseReader.ReadToEnd();
            responseReader.Close();
        }
        catch (Exception e)
        {
            log.LogInformation(e.Message);
        }

        return response;
    }
This would simply call the IV URL by passing the token generated from Step-2, simply by invoking (marked in yellow) –
        request.Headers.Add("Authorization", string.Format("Bearer {0}", tokenVal));
And then we are also marking the request as api-version as 1.0 (marked in Turquoise):
request.Headers.Add("api-version", "1.0");
This is essentially identifies the IV URL as a version 1.0 – version 2.0 leads to query multiple legal entities, multiple Item Ids, multiple site, warehouse, etc.

Final step: call the tokens and endpoints

Finally you can call the entire block of code from the Azure Function like this:
        [FunctionName("AzureFunctionQueryIV")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");           

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
           
            ManageSessionStates manageSessionStates = new ManageSessionStates();
            string getToken = manageSessionStates.GetToken();
            if (getToken != string.Empty)
            {
                string getIVToken = manageSessionStates.GetIVToken(getToken, log);

                CallIVSession session = new CallIVSession();
                session.InqData = requestBody;
                session.TokenVal = getIVToken;
                string result = session.callIVURL(log);

                return new OkObjectResult(result);
            }
            else
            {
                return new OkObjectResult("Token generation failed");
            }
        }
    }
Please note:
  1. We are passing on the Logger class to log the errors between one method to the next.
  2. Calling the AAD token first
  3. If it’s not blank, call the IV token URL to obtain the IV token
  4. Finally we are calling the IV URL to query the actual IV endpoint, by passing on the token generated from the above step.
  5. And then passing on the result as an outcome.
Let us run this now (from POSTMAN):

This gives the inventory details of the Product Id: ‘1000’, for the Legal entity: ‘USMF’. you can change you query accordingly: example – let us pass just the legal entity, it will return the entire set of the inventory availability:


With that, let me conclude the blog here. Will be coming back again with new hacks and features using Inventory Visibility and Azure services/Copilot soon. Till then keep in touch and much love 😊

Comments

*This post is locked for comments