I am implementing a webjob in .NET
that will have to take care of importing about 25.000 users from our database to a new one managed with IdentityServer
not handled by us.
This operation will be very expensive in terms of http calls, because for each user I have to make these calls:
- user creation;
- assigning any roles to the created user (which could be more than one so there would be yet another iteration);
- assignment of (I know for sure) at least two claims (another iteration);
- change user password.
I can't handle it any other way because the owners of the instance I'm importing users on have defined these as the steps to take for each user.
This is what I thought: this is my "entry" point:
internal async static Task<WorkerResponse> SendUsersAsync(string token, IDictionary<int, UserToSend> usersToSend, ICollection<Roles> roles, IMapper mapper, TextWriter logger = null)
{
string userName = string.Empty;
try
{
foreach (KeyValuePair<int, UserToSend> userToSend in usersToSend)
{
int externalId = userToSend.Key;
userName = userToSend.Value.UserName;
int fK_Soggetto = userToSend.Value.FK_Soggetto;
logger?.Write($"Sending user {userName} with (External)Id {externalId}");
logger?.WriteLine(string.Empty);
UserToPost userToPost = mapper.Map<UserToPost>(userToSend.Value);
(bool isSuccess, string messageOrUserId) = await SendUserToAuthorityAsync(token, userToPost);
if (!isSuccess)
return new WorkerResponse(isSuccess: false, message: messageOrUserId);
logger?.Write($"User {userName} sent.");
logger?.WriteLine(string.Empty);
if (userToSend.Value.ConsulenzaRoles?.Count > 0)
{
logger?.Write($"Appending roles for user {userName} with id {messageOrUserId}");
logger?.WriteLine(string.Empty);
(bool isSuccessRoles, string messageRoles) = await SendUserRolesToAuthorityAsync(
token,
SendingUserHelper.GetUserRolesToPost(userToSend.Value.ConsulenzaRoles, roles, messageOrUserId),
userName,
logger);
if (!isSuccessRoles)
return new WorkerResponse(isSuccess: false, message: messageRoles);
}
logger?.Write($"Appending claims for user {userName} with id {messageOrUserId}");
logger?.WriteLine(string.Empty);
ICollection<UserClaimToPost> userClaimsToPost = SendingUserHelper.GetUserClaimsToPost(messageOrUserId, externalId, fK_Soggetto);
(bool isSuccessClaims, string msg) = await SendUserClaimsToAuthorityAsync(token, userClaimsToPost, userName, logger);
if (!isSuccessClaims)
return new WorkerResponse(isSuccess: false, message: msg);
}
}
catch (BusinessLogicException ex)
{
return new WorkerResponse(isSuccess: false, message: $"{ex.Message} {ex.InnerException?.Message}");
}
return new WorkerResponse(isSuccess: true, message: $"user {userName} successfully added");
}
Where inside each method (send user, send role etc)
the structure is more or less this for all methods (using (HttpClient httpClient = new HttpClient())
mostly):
private async static Task<(bool, string)> SendUserToAuthorityAsync(string token, UserToPost userToPost, TextWriter logger = null)
{
try
{
logger?.WriteLine($"Attempting to request auth to send User {userToPost.UserName}...");
logger?.WriteLine(string.Empty);
IdentityServerUser userResponse;
using (HttpClient httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(AdminAuthority);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
string bodyString = JsonConvert.SerializeObject(userToPost);
byte[] buffer = Encoding.UTF8.GetBytes(bodyString);
ByteArrayContent byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (HttpResponseMessage responseMessage = await httpClient.PostAsync(IdentityServerEndpoint.PostUsers, byteContent))
{
responseMessage.EnsureSuccessStatusCode();
userResponse = JsonConvert.DeserializeObject<IdentityServerUser>(await responseMessage.Content.ReadAsStringAsync());
if (userResponse?.IsOk == false)
return (false, $"Error deserializing user {userToPost.UserName} from content string to abstracted model");
}
}
if (userResponse?.Id == default)
return (false, $"Error deserializing user {userToPost.UserName} from content string to abstracted model");
return (true, $"{userResponse.Id}");
}
catch (Exception ex)
{
return (false, $"Error sending user '{userToPost.UserName}'.\n{ex.Message}{ex.InnerException?.Message}");
}
}
I was wondering if there are smarter ways to make this calls within the foreach
.
For example I am not sure if the using
of HttpClient
are safe, and also if it was better to think also of a "lazy" system that sends users a little to the time without making thousands of calls in no time. Thank you