Compare commits
1 Commits
1f4ac29030
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 59f26c3d3d |
@@ -21,6 +21,12 @@ namespace Epi2O365
|
||||
private string tenantID;
|
||||
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)
|
||||
{
|
||||
this.clientID = clientID;
|
||||
@@ -29,14 +35,18 @@ namespace Epi2O365
|
||||
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
|
||||
{
|
||||
var graphClient = await GetGraphClient();
|
||||
|
||||
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);
|
||||
|
||||
if (!string.IsNullOrEmpty(O365ContactID))
|
||||
@@ -44,23 +54,98 @@ namespace Epi2O365
|
||||
await graphClient.Users[destinationAccountPrimaryAddress]
|
||||
.Contacts[O365ContactID]
|
||||
.PatchAsync(contact);
|
||||
Logger.Info($"Contact with ID '{O365ContactID}' successfully updated.");
|
||||
Logger.Info(destinationAccountPrimaryAddress + $" | Contact with ID '{O365ContactID}' successfully updated");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warn("No valid contact ID found for update.");
|
||||
Logger.Warn(destinationAccountPrimaryAddress + " | No valid contact ID found for update.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info("Contact " + contact.DisplayName + " NOT found in O365");
|
||||
Logger.Info("Creating new Contact");
|
||||
await graphClient.Users[destinationAccountPrimaryAddress].Contacts.PostAsync(contact);
|
||||
// --- Create-Flow mit NickName-basierter Verifikation ---
|
||||
Logger.Info(destinationAccountPrimaryAddress + " | Contact " + contact.DisplayName + " NOT found in O365");
|
||||
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)
|
||||
{
|
||||
Logger.Error("Error while synchronizing contact: " + ex.Message);
|
||||
Logger.Error(destinationAccountPrimaryAddress + " | Error while synchronizing contact: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,11 +153,15 @@ namespace Epi2O365
|
||||
{
|
||||
try
|
||||
{
|
||||
var safe = O(nickName);
|
||||
|
||||
var contacts = await graphClient.Users[userPrimaryMail]
|
||||
.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();
|
||||
@@ -80,11 +169,44 @@ namespace Epi2O365
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
try
|
||||
@@ -105,35 +227,6 @@ namespace Epi2O365
|
||||
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
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Epi2O365
|
||||
{
|
||||
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 Model.ContactList ContactList;
|
||||
private static O365Connector O365;
|
||||
@@ -22,7 +22,7 @@ namespace Epi2O365
|
||||
{
|
||||
string pathLastRuntime = "LastRun.txt";
|
||||
DateTime LastRun = DateTime.Parse("01.01.1970 10:00:00");
|
||||
|
||||
Logger.Info("Started2");
|
||||
try
|
||||
{
|
||||
if (File.Exists(pathLastRuntime))
|
||||
@@ -31,7 +31,7 @@ namespace Epi2O365
|
||||
string datetimeFromText = File.ReadAllText(pathLastRuntime);
|
||||
Logger.Debug("Found datetime string: " + datetimeFromText);
|
||||
|
||||
LastRun = DateTime.Parse(datetimeFromText);
|
||||
LastRun = DateTime.Parse(datetimeFromText).AddHours(-4);
|
||||
Logger.Info("Last run timestamp: " + LastRun.ToString());
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,8 @@ namespace Epi2O365
|
||||
Contact o365CompanyContact = new Contact
|
||||
{
|
||||
DisplayName = contactDetail.Payload[0].Name,
|
||||
GivenName = contactDetail.Payload[0].Name,
|
||||
Surname= contactDetail.Payload[0].Name1,
|
||||
CompanyName = contactDetail.Payload[0].Name,
|
||||
EmailAddresses = CompanyMailAddresses,
|
||||
BusinessPhones = CompanyPhoneNumbers,
|
||||
@@ -167,7 +169,7 @@ namespace Epi2O365
|
||||
{
|
||||
try
|
||||
{
|
||||
O365.SyncContact(o365contact, addressBookHolder);
|
||||
await O365.SyncContact(o365contact, addressBookHolder);
|
||||
Logger.Info("Contact person synchronized for user: " + addressBookHolder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user