Skip to content

Commit

Permalink
adjust clients for updated API of Dynamics and Business Central
Browse files Browse the repository at this point in the history
  • Loading branch information
Dmitry Kozlov committed Nov 20, 2024
1 parent ce30f56 commit 018fc48
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 138 deletions.
36 changes: 36 additions & 0 deletions FunctionApp/DebugRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Plumsail.DataSource
{
class DebugRequestHandler : DelegatingHandler
{
public DebugRequestHandler(HttpMessageHandler? innerHandler = null)
{
InnerHandler = innerHandler ?? new HttpClientHandler();
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("");
Console.WriteLine(string.Format("Request: {0} {1}", request.Method, request.RequestUri));
Console.WriteLine("Request headers:");
foreach (var header in request.Headers)
{
Console.WriteLine(string.Format("{0}: {1}", header.Key, string.Join(',', header.Value)));
}
if (request.Content is not null)
{
Console.WriteLine("");
Console.WriteLine("Request body:");
var body = await request.Content.ReadAsStringAsync();
Console.WriteLine(body);
}

return await base.SendAsync(request, cancellationToken);
}
}
}
37 changes: 37 additions & 0 deletions FunctionApp/DebugResponseHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Plumsail.DataSource
{
class DebugResponseHandler : DelegatingHandler
{
public DebugResponseHandler(HttpMessageHandler? innerHandler = null)
{
InnerHandler = innerHandler ?? new HttpClientHandler();
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);

Console.WriteLine("");
Console.WriteLine("Response headers:");
foreach (var header in response.Headers)
{
Console.WriteLine(string.Format("{0}: {1}", header.Key, string.Join(',', header.Value)));
}
if (response.Content is not null)
{
Console.WriteLine("");
Console.WriteLine("Response body:");
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}

return response;
}
}
}
2 changes: 1 addition & 1 deletion FunctionApp/Dynamics365/BusinessCentral/Authorize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public Authorize(IOptions<AppSettings> settings, ILogger<Authorize> logger)
[Function("D365-BC-Authorize")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
{
var scopes = new string[] { "https://graph.microsoft.com/.default", "offline_access" };
var scopes = new string[] { "https://api.businesscentral.dynamics.com/.default", "offline_access" };

if (req.Method == "POST" && req.Form.ContainsKey("code"))
{
Expand Down
29 changes: 12 additions & 17 deletions FunctionApp/Dynamics365/BusinessCentral/Customers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Graph.Beta;
using Microsoft.Graph.Beta.Models;
using Plumsail.DataSource.Dynamics365.BusinessCentral.Settings;
using Plumsail.DataSource.Dynamics365.CRM;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

namespace Plumsail.DataSource.Dynamics365.BusinessCentral
{
public class Customers
{
private readonly Settings.Customers _settings;
private readonly GraphServiceClientProvider _graphProvider;
private readonly HttpClientProvider _httpClientProvider;
private readonly ILogger<Customers> _logger;

public Customers(IOptions<AppSettings> settings, GraphServiceClientProvider graphProvider, ILogger<Customers> logger)
public Customers(IOptions<AppSettings> settings, HttpClientProvider httpClientProvider, ILogger<Customers> logger)
{
_settings = settings.Value.Customers;
_graphProvider = graphProvider;
_httpClientProvider = httpClientProvider;
_logger = logger;
}

Expand All @@ -30,24 +33,16 @@ public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "
{
_logger.LogInformation("Dynamics365-BusinessCentral-Customers is requested.");

var graph = await _graphProvider.Create();
var company = await graph.GetCompanyAsync(_settings.Company);
if (company == null)
var client = _httpClientProvider.Create();
var companyId = await client.GetCompanyIdAsync(_settings.Company);
if (companyId == null)
{
return new NotFoundResult();
}

var customersPage = await graph.Financials.Companies[company.Id.Value].Customers.GetAsync();
var customers = new List<Customer>();
var pageIterator = PageIterator<Customer, CustomerCollectionResponse>
.CreatePageIterator(graph, customersPage, customer =>
{
customers.Add(customer);
return true;
});

await pageIterator.IterateAsync();
return new OkObjectResult(customers);
var customersJson = await client.GetStringAsync($"companies({companyId})/customers");
var customers = JsonValue.Parse(customersJson);
return new OkObjectResult(customers?["value"]);
}
}
}

This file was deleted.

This file was deleted.

15 changes: 15 additions & 0 deletions FunctionApp/Dynamics365/BusinessCentral/HttpClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Nodes;

namespace Plumsail.DataSource.Dynamics365.BusinessCentral
{
internal static class HttpClientExtensions
{
internal static async System.Threading.Tasks.Task<string> GetCompanyIdAsync(this HttpClient client, string companyName)
{
var companiesJson = await client.GetStringAsync("companies?$select=id,name");
var contacts = JsonValue.Parse(companiesJson)["value"].AsArray();

return contacts.FirstOrDefault(c => c["name"]?.GetValue<string>() == companyName)?["id"]?.GetValue<string>();
}
}
}
66 changes: 66 additions & 0 deletions FunctionApp/Dynamics365/BusinessCentral/HttpClientProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.Options;
using Microsoft.Graph.Beta;
using Microsoft.Identity.Client;
using Microsoft.Kiota.Abstractions.Authentication;
using Plumsail.DataSource.Dynamics365.BusinessCentral.Settings;
using Plumsail.DataSource.Dynamics365.CRM;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Plumsail.DataSource.Dynamics365.BusinessCentral
{
public class HttpClientProvider
{
private readonly AzureApp _azureAppSettings;

public HttpClientProvider(IOptions<AppSettings> settings)
{
_azureAppSettings = settings.Value.AzureApp;
}

public HttpClient Create()
{
// for debugging requests
//var debugHandler = new DebugRequestHandler(new DebugResponseHandler());
//var client = new HttpClient(new OAuthMessageHandler(_azureAppSettings, debugHandler));

var client = new HttpClient(new OAuthMessageHandler(_azureAppSettings));
client.BaseAddress = new Uri($"https://api.businesscentral.dynamics.com/v2.0/{_azureAppSettings.InstanceId}/Production/api/v2.0/");
client.Timeout = new TimeSpan(0, 2, 0);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

return client;
}
}

class OAuthMessageHandler : DelegatingHandler
{
private readonly AzureApp _azureAppSettings;

public OAuthMessageHandler(AzureApp azureAppSettings, HttpMessageHandler? innerHandler = null) : base(innerHandler ?? new HttpClientHandler())
{
_azureAppSettings = azureAppSettings;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var app = ConfidentialClientApplicationBuilder.Create(_azureAppSettings.ClientId)
.WithClientSecret(_azureAppSettings.ClientSecret)
.WithTenantId(_azureAppSettings.Tenant)
.Build();

var cache = new TokenCacheHelper(AzureApp.CacheFileDir);
cache.EnableSerialization(app.UserTokenCache);

var account = await app.GetAccountAsync(cache.GetAccountIdentifier());
var result = await app.AcquireTokenSilent(["https://api.businesscentral.dynamics.com/.default"], account).ExecuteAsync();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
}
29 changes: 11 additions & 18 deletions FunctionApp/Dynamics365/BusinessCentral/Items.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,40 @@
using Microsoft.Graph.Beta.Models;
using Plumsail.DataSource.Dynamics365.BusinessCentral.Settings;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

namespace Plumsail.DataSource.Dynamics365.BusinessCentral
{
public class Items
{
private readonly Settings.Items _settings;
private readonly GraphServiceClientProvider _graphProvider;
private readonly HttpClientProvider _httpClientProvider;
private readonly ILogger<Items> _logger;

public Items(IOptions<AppSettings> settings, GraphServiceClientProvider graphProvider, ILogger<Items> logger)
public Items(IOptions<AppSettings> settings, HttpClientProvider httpClientProvider, ILogger<Items> logger)
{
_settings = settings.Value.Items;
_graphProvider = graphProvider;
_httpClientProvider = httpClientProvider;
_logger = logger;
}

[Function("D365-BC-Items")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req)
{
_logger.LogInformation("Dynamics365-BusinessCentral-Vendors is requested.");
_logger.LogInformation("Dynamics365-BusinessCentral-Items is requested.");

var graph = await _graphProvider.Create();
var company = await graph.GetCompanyAsync(_settings.Company);
if (company == null)
var client = _httpClientProvider.Create();
var companyId = await client.GetCompanyIdAsync(_settings.Company);
if (companyId == null)
{
return new NotFoundResult();
}

var itemsPage = await graph.Financials.Companies[company.Id.Value].Items.GetAsync();
var items = new List<Item>();
var pageIterator = PageIterator<Item, ItemCollectionResponse>
.CreatePageIterator(graph, itemsPage, item =>
{
items.Add(item);
return true;
});

await pageIterator.IterateAsync();
return new OkObjectResult(items);
var itemsJson = await client.GetStringAsync($"companies({companyId})/items");
var items = JsonValue.Parse(itemsJson);
return new OkObjectResult(items?["value"]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ public class AzureApp
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string Tenant { get; set; }
public string InstanceId { get; set; }
}
}
Loading

0 comments on commit 018fc48

Please sign in to comment.