Compare commits
1 Commits
1f4ac29030
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 59f26c3d3d |
@@ -21,6 +21,12 @@ namespace Epi2O365
|
|||||||
private string tenantID;
|
private string tenantID;
|
||||||
private string BaseUrl;
|
private string BaseUrl;
|
||||||
|
|
||||||
|
private const int CreateVerifyDelayMs = 2500;
|
||||||
|
private const int CreateVerifyMaxRetries = 4;
|
||||||
|
private const int CreateHttpRetryMax = 1; // 1 zusätzlicher Versuch bei 429/5xx
|
||||||
|
private const int CreateRetryCount = 1; // Anzahl zusätzlicher Versuche
|
||||||
|
|
||||||
|
|
||||||
public O365Connector(string clientID, string clientSecret, string tenantID, string BaseUrl)
|
public O365Connector(string clientID, string clientSecret, string tenantID, string BaseUrl)
|
||||||
{
|
{
|
||||||
this.clientID = clientID;
|
this.clientID = clientID;
|
||||||
@@ -29,14 +35,18 @@ namespace Epi2O365
|
|||||||
this.BaseUrl = BaseUrl;
|
this.BaseUrl = BaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SyncContact(Contact contact, string destinationAccountPrimaryAddress)
|
// OData-Escape für Filterwerte
|
||||||
|
private static string O(string s) => (s ?? string.Empty).Replace("'", "''");
|
||||||
|
|
||||||
|
public async Task SyncContact(Contact contact, string destinationAccountPrimaryAddress, int depth = 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var graphClient = await GetGraphClient();
|
var graphClient = await GetGraphClient();
|
||||||
|
|
||||||
if (await DoesContactExist(contact, destinationAccountPrimaryAddress, graphClient))
|
if (await DoesContactExist(contact, destinationAccountPrimaryAddress, graphClient))
|
||||||
{
|
{
|
||||||
Logger.Info("Contact " + contact.DisplayName + " found in O365");
|
Logger.Info(destinationAccountPrimaryAddress + " | Contact " + contact.DisplayName + " found in O365");
|
||||||
string O365ContactID = await GetContactIdByNickName(graphClient, destinationAccountPrimaryAddress, contact.NickName);
|
string O365ContactID = await GetContactIdByNickName(graphClient, destinationAccountPrimaryAddress, contact.NickName);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(O365ContactID))
|
if (!string.IsNullOrEmpty(O365ContactID))
|
||||||
@@ -44,23 +54,98 @@ namespace Epi2O365
|
|||||||
await graphClient.Users[destinationAccountPrimaryAddress]
|
await graphClient.Users[destinationAccountPrimaryAddress]
|
||||||
.Contacts[O365ContactID]
|
.Contacts[O365ContactID]
|
||||||
.PatchAsync(contact);
|
.PatchAsync(contact);
|
||||||
Logger.Info($"Contact with ID '{O365ContactID}' successfully updated.");
|
Logger.Info(destinationAccountPrimaryAddress + $" | Contact with ID '{O365ContactID}' successfully updated");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Warn("No valid contact ID found for update.");
|
Logger.Warn(destinationAccountPrimaryAddress + " | No valid contact ID found for update.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info("Contact " + contact.DisplayName + " NOT found in O365");
|
// --- Create-Flow mit NickName-basierter Verifikation ---
|
||||||
Logger.Info("Creating new Contact");
|
Logger.Info(destinationAccountPrimaryAddress + " | Contact " + contact.DisplayName + " NOT found in O365");
|
||||||
await graphClient.Users[destinationAccountPrimaryAddress].Contacts.PostAsync(contact);
|
Logger.Info(destinationAccountPrimaryAddress + " | Creating new Contact (Nick='" + contact.NickName + "')");
|
||||||
|
|
||||||
|
// kleiner HTTP-Retry nur für den POST (429/5xx)
|
||||||
|
int httpAttempts = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Prefer: return=representation – liefert dir direkt eine ID (nur fürs Log)
|
||||||
|
var created = await graphClient.Users[destinationAccountPrimaryAddress]
|
||||||
|
.Contacts
|
||||||
|
.PostAsync(contact, rc => rc.Headers.Add("Prefer", "return=representation"));
|
||||||
|
Logger.Info(destinationAccountPrimaryAddress + " | Created contact: Id=" + (created?.Id ?? "n/a") + " Nick='" + (created?.NickName ?? contact.NickName) + "'");
|
||||||
|
// NickName sicherstellen (einmaliges PATCH – idempotent)
|
||||||
|
if (!string.IsNullOrEmpty(created?.Id) && !string.IsNullOrWhiteSpace(contact.NickName))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await graphClient.Users[destinationAccountPrimaryAddress]
|
||||||
|
.Contacts[created.Id]
|
||||||
|
.PatchAsync(new Contact { NickName = contact.NickName });
|
||||||
|
|
||||||
|
Logger.Debug(destinationAccountPrimaryAddress + $" | Ensured nickName='{contact.NickName}' on Id={created.Id}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Warn(destinationAccountPrimaryAddress + $" | Unable to ensure nickName on Id={created.Id}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
break; // POST erfolgreich
|
||||||
|
}
|
||||||
|
catch (Microsoft.Kiota.Abstractions.ApiException apiEx) when (
|
||||||
|
(apiEx.ResponseStatusCode >= 500 || apiEx.ResponseStatusCode == 429)
|
||||||
|
&& httpAttempts < CreateHttpRetryMax)
|
||||||
|
{
|
||||||
|
httpAttempts++;
|
||||||
|
Logger.Warn(destinationAccountPrimaryAddress + $" | Create POST failed with {apiEx.ResponseStatusCode}. Retry {httpAttempts}/{CreateHttpRetryMax} in 1s …");
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifizieren mit bis zu N Wiederholungen – immer dieselbe NickName-Checkmethode
|
||||||
|
for (int attempt = 0; attempt <= CreateVerifyMaxRetries; attempt++)
|
||||||
|
{
|
||||||
|
await Task.Delay(CreateVerifyDelayMs);
|
||||||
|
Logger.Info(destinationAccountPrimaryAddress + $" | Starting to Verify: contact '{contact.NickName}'. (attempt {attempt}).");
|
||||||
|
bool existsNow = await DoesContactExist(contact, destinationAccountPrimaryAddress, graphClient);
|
||||||
|
if (existsNow)
|
||||||
|
{
|
||||||
|
Logger.Info(destinationAccountPrimaryAddress + $" | Verified: contact '{contact.NickName}' is present after create (attempt {attempt}).");
|
||||||
|
// Optional direkt noch die ID loggen:
|
||||||
|
var id = await GetContactIdByNickName(graphClient, destinationAccountPrimaryAddress, contact.NickName);
|
||||||
|
if (!string.IsNullOrEmpty(id))
|
||||||
|
Logger.Debug(destinationAccountPrimaryAddress + $" | Verified ContactId: {id}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt == CreateVerifyMaxRetries)
|
||||||
|
{
|
||||||
|
Logger.Warn(destinationAccountPrimaryAddress + $" | Contact '{contact.NickName}' still not visible after create (attempts: {CreateVerifyMaxRetries + 1}).");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info(destinationAccountPrimaryAddress + $" | Not visible yet, re-checking (attempt {attempt + 1}/{CreateVerifyMaxRetries}) …");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional: eine vorsichtige Wiederholung mit gleicher Logik (NickName-only)
|
||||||
|
if (depth < CreateRetryCount)
|
||||||
|
{
|
||||||
|
Logger.Warn(destinationAccountPrimaryAddress + $" | Retrying SyncContact once for Nick='{contact.NickName}' (depth {depth + 1}) …");
|
||||||
|
await Task.Delay(2000);
|
||||||
|
await SyncContact(contact, destinationAccountPrimaryAddress, depth + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Error("Error while synchronizing contact: " + ex.Message);
|
Logger.Error(destinationAccountPrimaryAddress + " | Error while synchronizing contact: " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,11 +153,15 @@ namespace Epi2O365
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var safe = O(nickName);
|
||||||
|
|
||||||
var contacts = await graphClient.Users[userPrimaryMail]
|
var contacts = await graphClient.Users[userPrimaryMail]
|
||||||
.Contacts
|
.Contacts
|
||||||
.GetAsync(requestConfiguration =>
|
.GetAsync(rc =>
|
||||||
{
|
{
|
||||||
requestConfiguration.QueryParameters.Filter = $"NickName eq '{nickName}'";
|
rc.QueryParameters.Filter = $"nickName eq '{safe}'";
|
||||||
|
rc.QueryParameters.Select = new[] { "id", "nickName", "displayName" };
|
||||||
|
rc.QueryParameters.Top = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
var contact = contacts?.Value?.FirstOrDefault();
|
var contact = contacts?.Value?.FirstOrDefault();
|
||||||
@@ -80,11 +169,44 @@ namespace Epi2O365
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Error($"Error retrieving contact with NickName '{nickName}': {ex.Message}");
|
Logger.Error(userPrimaryMail + $" | Error retrieving contact with NickName '{nickName}': {ex.Message}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> DoesContactExist(Contact contact, string userPrimaryMail, GraphServiceClient graphClient)
|
||||||
|
{
|
||||||
|
string NickName = contact.NickName;
|
||||||
|
if (string.IsNullOrWhiteSpace(NickName))
|
||||||
|
{
|
||||||
|
Logger.Warn(userPrimaryMail + " | No valid NickName found for the given contact.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var safe = O(NickName);
|
||||||
|
|
||||||
|
var contacts = await graphClient.Users[userPrimaryMail]
|
||||||
|
.Contacts
|
||||||
|
.GetAsync(rc =>
|
||||||
|
{
|
||||||
|
rc.QueryParameters.Filter = $"nickName eq '{safe}'";
|
||||||
|
rc.QueryParameters.Select = new[] { "id" };
|
||||||
|
rc.QueryParameters.Top = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
bool exists = contacts?.Value != null && contacts.Value.Any();
|
||||||
|
Logger.Info(userPrimaryMail + $" | Contact with NickName {NickName} exists: {exists}");
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error(userPrimaryMail + $" | Error checking contacts: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<GraphServiceClient> GetGraphClient()
|
private async Task<GraphServiceClient> GetGraphClient()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -105,35 +227,6 @@ namespace Epi2O365
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> DoesContactExist(Contact contact, string userPrimaryMail, GraphServiceClient graphClient)
|
|
||||||
{
|
|
||||||
string NickName = contact.NickName;
|
|
||||||
if (string.IsNullOrWhiteSpace(NickName))
|
|
||||||
{
|
|
||||||
Logger.Warn("No valid NickName found for the given contact.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var contacts = await graphClient.Users[userPrimaryMail]
|
|
||||||
.Contacts
|
|
||||||
.GetAsync(requestConfiguration =>
|
|
||||||
{
|
|
||||||
requestConfiguration.QueryParameters.Filter = $"NickName eq '{NickName}'";
|
|
||||||
});
|
|
||||||
|
|
||||||
bool exists = contacts?.Value != null && contacts.Value.Any();
|
|
||||||
Logger.Info($"Contact with NickName '{NickName}' exists: {exists}");
|
|
||||||
return exists;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error($"Error checking contacts: {ex.Message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TokenAcquisitionProvider : IAccessTokenProvider
|
public class TokenAcquisitionProvider : IAccessTokenProvider
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Epi2O365
|
|||||||
{
|
{
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(Program));
|
//private static readonly ILog Logger = LogManager.GetLogger(typeof(Program));
|
||||||
private static ConfigLoader Configuration;
|
private static ConfigLoader Configuration;
|
||||||
private static Model.ContactList ContactList;
|
private static Model.ContactList ContactList;
|
||||||
private static O365Connector O365;
|
private static O365Connector O365;
|
||||||
@@ -22,7 +22,7 @@ namespace Epi2O365
|
|||||||
{
|
{
|
||||||
string pathLastRuntime = "LastRun.txt";
|
string pathLastRuntime = "LastRun.txt";
|
||||||
DateTime LastRun = DateTime.Parse("01.01.1970 10:00:00");
|
DateTime LastRun = DateTime.Parse("01.01.1970 10:00:00");
|
||||||
|
Logger.Info("Started2");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (File.Exists(pathLastRuntime))
|
if (File.Exists(pathLastRuntime))
|
||||||
@@ -31,7 +31,7 @@ namespace Epi2O365
|
|||||||
string datetimeFromText = File.ReadAllText(pathLastRuntime);
|
string datetimeFromText = File.ReadAllText(pathLastRuntime);
|
||||||
Logger.Debug("Found datetime string: " + datetimeFromText);
|
Logger.Debug("Found datetime string: " + datetimeFromText);
|
||||||
|
|
||||||
LastRun = DateTime.Parse(datetimeFromText);
|
LastRun = DateTime.Parse(datetimeFromText).AddHours(-4);
|
||||||
Logger.Info("Last run timestamp: " + LastRun.ToString());
|
Logger.Info("Last run timestamp: " + LastRun.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,6 +105,8 @@ namespace Epi2O365
|
|||||||
Contact o365CompanyContact = new Contact
|
Contact o365CompanyContact = new Contact
|
||||||
{
|
{
|
||||||
DisplayName = contactDetail.Payload[0].Name,
|
DisplayName = contactDetail.Payload[0].Name,
|
||||||
|
GivenName = contactDetail.Payload[0].Name,
|
||||||
|
Surname= contactDetail.Payload[0].Name1,
|
||||||
CompanyName = contactDetail.Payload[0].Name,
|
CompanyName = contactDetail.Payload[0].Name,
|
||||||
EmailAddresses = CompanyMailAddresses,
|
EmailAddresses = CompanyMailAddresses,
|
||||||
BusinessPhones = CompanyPhoneNumbers,
|
BusinessPhones = CompanyPhoneNumbers,
|
||||||
@@ -167,7 +169,7 @@ namespace Epi2O365
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
O365.SyncContact(o365contact, addressBookHolder);
|
await O365.SyncContact(o365contact, addressBookHolder);
|
||||||
Logger.Info("Contact person synchronized for user: " + addressBookHolder);
|
Logger.Info("Contact person synchronized for user: " + addressBookHolder);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
Reference in New Issue
Block a user