Please answer the following questions before submitting an issue. YOU MAY DELETE THE PREREQUISITES SECTION.
I am trying to consume a web api and below is what I have implemented :
Startup.cs
services.AddTransient<HtgAuthenticationDelegatingHandler>();
services.AddHttpClient<IMyServiceManager,MyServiceManager>(client =>
{
client.BaseAddress = new Uri(_appConfiguration["cccccccc"]);
client.DefaultRequestHeaders.Add("User-Agent", "myPortal");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.CacheControl = CacheControlHeaderValue.Parse("no-cache");
})
//.AddPolicyHandler(GetRetryPolicy())
.AddHttpMessageHandler<MyAuthenticationDelegatingHandler>(); // This handler is on the inside, closest to the request.
public class MyAuthenticationDelegatingHandler : DelegatingHandler
{
private readonly TokenManager _tokenManager;
private readonly ICacheManager _cacheManager;
public HtgAuthenticationDelegatingHandler(TokenManager tokenManager, ICacheManager cacheManager)
{
_tokenManager = tokenManager;
_cacheManager = cacheManager;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var htgtoken = await _tokenManager.GetMyToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", htgtoken);
try
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode != HttpStatusCode.Unauthorized &&
response.StatusCode != HttpStatusCode.Forbidden)
return response;
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", await _tokenManager.GetMyToken());
response = await base.SendAsync(request, cancellationToken);
return response;
}
catch (Exception e)
{
string exception = e.Message;
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest,
ReasonPhrase = e.Message
};
}
}
}
public class TokenManager : ITransientDependency
{
private readonly IAmazonSecretsManager _secretManager;
public IConfiguration Configuration { get; }
private readonly IConfigurationRoot _appConfiguration;
private readonly ICacheManager _cacheManager;
public TokenManager(IAmazonSecretsManager secretManager, IConfiguration configuration,
IAppConfigurationAccessor configurationAccessor, ICacheManager cacheManager)
{
_secretManager = secretManager;
Configuration = configuration;
_cacheManager = cacheManager;
_appConfiguration = configurationAccessor.Configuration;
}
public async Task<string> GetHTGToken()
{
return await UpdateHtgTokenCacheAsync();
}
public static bool IsValidJwtToken(string accesstoken)
{
var tokenExpiryDate = JWTTokenExpiryTime(accesstoken);
return tokenExpiryDate > DateTime.UtcNow;
}
public static DateTime JWTTokenExpiryTime(string token)
{
var handler = new JwtSecurityTokenHandler();
if (!(handler.ReadToken(token) is JwtSecurityToken tokenread)) return DateTime.MinValue;
return tokenread.ValidTo;
}
private async Task<string> UpdateMyTokenCacheAsync()
{
//Get it from the app settings
var request = new HttpRequestMessage(HttpMethod.Post, _appConfiguration["Api:My:TokenEndPoint"]);
var clientId = xxxx;
var secret = yyyy;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.CacheControl = CacheControlHeaderValue.Parse("no-cache");
request.Headers.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientId}:{secret}")));
request.Content = new StringContent($"grant_type=client_credentials&scope=manage:all",
System.Text.Encoding.UTF8, "application/x-www-form-urlencoded");
var client = new HttpClient();
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
var token = JObject.Parse(body)["access_token"]?.ToString();
var tokencache = _cacheManager.GetCache("tokencache");
try
{
await tokencache.SetAsync(new KeyValuePair<string, object>[]
{
new KeyValuePair<string, object>("mytoken", token),
}, null, JWTTokenExpiryTime(token));
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
return token;
}
}
public class MyServiceManager : MyDomainServiceBase, IMyServiceManager
{
private readonly HttpClient _httpClient;
public MyServiceManager(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<HttpResponseMessage> GetStates()
{
try
{
var response = await _httpClient.GetAsync(
"state?additionalProp1=&additionalProp2=&additionalProp3=");
return response;
}
catch (Exception ex)
{
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest,
ReasonPhrase = ex.Message
};
}
}
}
<br> I am basically getting the token from an authentication server , storing that token and getting the token again if it has expired.
Eveyrthing works fine for the first time but my http client is getting disposed on the second time.
Any idea?
Please answer the following questions before submitting an issue. YOU MAY DELETE THE PREREQUISITES SECTION.
I have been using New Relics for Application Monitoring, now I would like to use their Log feature , I would like to export the log that's being generated to New Relics.
Below is a documentation I am referring :
https://docs.newrelic.com/docs/logs/enable-log-management-new-relic/logs-context-net/net-configure-log4net https://github.com/newrelic/newrelic-logenricher-dotnet/tree/master/src/Log4Net/NewRelic.LogEnrichers.Log4Net
The idea is to move the logs from the server to New Relics for further analysis.
I need some help and guidance to configure Abp Log4Net config file to make it work.
Secondly I would like to export the Audit Log information to a AuditLog.txt using Log4Net and send it to New Relics to store in their Logs.
Is there a way I can port the code easily without rewriting everything from the ground up.
I was trying to add the Volo.Abp.BlobStoring to by Aspnetzero project speciffically Application project and I am encountering this error :
Severity Code Description Project File Line Suppression State Error CS0121 The call is ambiguous between the following methods or properties: 'System.AbpStringExtensions.IsNullOrWhiteSpace(string)' and 'Abp.Extensions.StringExtensions.IsNullOrWhiteSpace(string)' XXXX.YYYY.Application \XXXX.YYYY.Application\Configuration\Host\HostSettingsAppService.cs 308 Active.
Also can you guide me how to integrate it for multi tenancy so that each tenant can configure their own provider, something like the way you did for External providers.
Any updates on this ?
I am using ASPNET Zero .net core and angular to build an admin tool to control more than 2000 databases. All databases are in MySQL and I am basically creating one database per tenant. The host database and the tenant databases are in different servers.
My issue is I create multiple background jobs to handle certain type of things.
E.g. Let's say user clean up, so basically I have a job which looks for an email address in host databases and comb through each of the 2000 databases and delete the user from every database. The problem is while doing so I am ending up creating 2000 threads across the MySQL server and most of them goes to sleep. MYSql server then timesout and the whole database is crashing
namespace ABC.BackgroundJobs
{
public class CreateUserToAllShowsJob : BackgroundJob<CreateUserInShowsJobArgs>, ITransientDependency
{
private readonly UserManager _userManager;
private readonly IUserJobStateNotifier _userJobStateNotifier;
private readonly IBackgroundJobEmailer _backgroundJobEmailer;
private readonly MySQLUserManager _mySQLUserManager;
private readonly IRepository<Tenant> _tenantRepository;
private readonly IRepository<UserGroupOrganization> _usergrouporganizationsRepository;
private readonly IRepository<UserAccount, long> _userAccountRepository;
private readonly RoleManager _roleManager;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IRepository<UserGroup> _usergroupsRepository;
private readonly IRepository<Role> _roleRepository;
private readonly IIocResolver _iIocResolver;
private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;
public CreateUserToAllShowsJob(IUserJobStateNotifier userJobStateNotifier,
IBackgroundJobEmailer backgroundJobEmailer,
MySQLUserManager mySQLUserManager,
UserManager userManager,
IRepository<Tenant> tenantRepository,
IRepository<UserGroupOrganization> usergrouporganizationsRepository,
IRepository<UserAccount, long> userAccountRepository,
RoleManager roleManager,
IUnitOfWorkManager unitOfWorkManager,
IRepository<UserGroup> usergroupsRepository,
IRepository<Role> roleRepository,
IIocResolver iIocResolver,
IRepository<OrganizationUnit, long> organizationUnitRepository
)
{
_userManager = userManager;
_userJobStateNotifier = userJobStateNotifier;
_backgroundJobEmailer = backgroundJobEmailer;
_mySQLUserManager = mySQLUserManager;
_tenantRepository = tenantRepository;
_usergrouporganizationsRepository = usergrouporganizationsRepository;
_userAccountRepository = userAccountRepository;
_roleManager = roleManager;
_unitOfWorkManager = unitOfWorkManager;
_usergroupsRepository = usergroupsRepository;
_roleRepository = roleRepository;
_iIocResolver = iIocResolver;
_organizationUnitRepository = organizationUnitRepository;
}
[UnitOfWork]
[Audited]
public override void Execute(CreateUserInShowsJobArgs args)
{
var hostUser = _userManager.FindByIdAsync(args.ActionRequestedBy).Result;
var newUser = _userManager.FindByIdAsync(args.ÚserId).Result;
try
{
AsyncHelper.RunSync(() => AddUserToAllShows(newUser, args.UserGroupIds, args.AssignedRoleNames, args.OrganizationIds, hostUser));
}
catch (Exception exception)
{
// sends notification
_userJobStateNotifier.AddUserToAllShowsAsync(new BackgroundJobStatusDto()
{
Message = exception.Message,
JobName = LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "CreateUserToAllShowsJob"),
}, Abp.Notifications.NotificationSeverity.Error).Wait();
// sends email
if (!ReferenceEquals(hostUser, null) && !hostUser.EmailAddress.IsNullOrEmpty())
{
// sends email
_backgroundJobEmailer.SendBackgroundJobFailInfo(new BackgroundJobEmailInput()
{
EmailAddress = hostUser.EmailAddress,
Message = exception.Message,
JobName = LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "CreateUserToAllShowsJob"),
JobFailedDateTime = DateTime.UtcNow
});
}
// logs the exception
Logger.Error(exception.Message, exception);
throw exception;
}
}
private async Task AddUserToAllShows(User input, List<int> UserGroups, List<string> AssignedRoleNames, List<long> OrganizationUnits, User hostUser)
{
if (!ReferenceEquals(UserGroups, null) && UserGroups.Any())
{
var hostUserRoles = AssignedRoleNames.ToList();
var tenants = await (from tenant in _tenantRepository.GetAll().Where(p => OrganizationUnits.Contains(p.OrganizationUnitId.Value))
join usergrporg in _usergrouporganizationsRepository.GetAll().Where(p => UserGroups.Contains(p.UserGroupId)) on
tenant.OrganizationUnitId equals usergrporg.OrganizationId
join useracc in _userAccountRepository.GetAll().Where(p => p.UserName == input.UserName) on
tenant.Id equals useracc.TenantId
into userAccount
from userAccounts in userAccount.DefaultIfEmpty()
select new { tenantId = tenant.Id, userAccounts.UserName, tenant.OrganizationUnitId, tenant.TenancyName }).ToListAsync();
var userGroupRoleIds = await _usergroupsRepository.GetAll().Where(p => UserGroups.Contains(p.Id))
.Select(p => p.RoleId.Value).Distinct().ToListAsync();
var roleDisplayNames = new List<string>();
if (userGroupRoleIds.Any())
{
var roles = await _roleRepository.GetAll().Where(p => userGroupRoleIds.Contains(p.Id)).ToListAsync();
hostUserRoles = roles.Select(p => p.DisplayName).ToList();
}
else
{
//from UI Role NAmes will passs, By using role name we are getting Displaynames
var roles = await _roleRepository.GetAll().Where(p => hostUserRoles.Contains(p.Name)).Select(p => p.DisplayName).ToListAsync();
hostUserRoles = roles;
}
foreach (var role in hostUserRoles)
{
roleDisplayNames.Add(_roleManager.GetTrimmedRoleName(role));
}
var user = await _userManager.FindByNameAsync(input.UserName) ??
await _userManager.FindByEmailAsync(input.EmailAddress);
var orgIds = tenants.Where(p => p.UserName == null).Select(p => p.OrganizationUnitId);
if (orgIds.Any())
{
bool isException = false;
var orgNames = await _organizationUnitRepository.GetAll().Where(p => orgIds.Contains(p.Id)).Select(p => p.DisplayName).ToListAsync();
await _userJobStateNotifier.AddUserToAllShowsAsync(new BackgroundJobStatusDto()
{
Message = $"{ LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "CreateUserToAllShowsJob")}:" +
$"{string.Format(LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "Addtoshowsjobstarted"), input.UserName, string.Join(',', orgNames))}",
}, Abp.Notifications.NotificationSeverity.Info);
foreach (var tenant in tenants.Where(p => p.UserName == null).Distinct())
{
using (var uowForAdduser = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
{
try
{
using (_unitOfWorkManager.Current.SetTenantId(tenant.tenantId))
{
//Copy the details
var userinputforTenant = new UserEditDto();
userinputforTenant.Name = user.Name;
userinputforTenant.Surname = user.Surname;
userinputforTenant.UserName = user.UserName;
userinputforTenant.EmailAddress = user.EmailAddress;
userinputforTenant.PhoneNumber = user.PhoneNumber;
userinputforTenant.Password = user.Password;
var createUserinput = new CreateOrUpdateUserInput
{
User = userinputforTenant,
SetRandomPassword = true,
AssignedRoleNames = assignedRoleNames.ToArray(),
OrganizationUnits = OrganizationUnits,
SendActivationEmail = false,
DbTemplates = new List<int>() { }
};
using (var _userAppService = _iIocResolver.ResolveAsDisposable<UserAppService>())
{
await _userAppService.Object.CreateInternalUserAsync(createUserinput);
}
//await CreateInternalUserAsync(createUserinput);
}
await _unitOfWorkManager.Current.SaveChangesAsync();
await _mySQLUserManager.GrantPrivilegeToUserForShow(new MySqlUserPrivilegeInput()
{
TenantId = tenant.tenantId,
UserName = input.UserName,
Password = SimpleStringCipher.Instance.Decrypt(user.PasswordForMysqlUser)
});
}
catch (Exception exception)
{
isException = true;
var msg = $"{string.Format(LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "CreateUserToShowExceptionMessage"), tenant.TenancyName, orgNames)} {exception.Message}";
// sends notification
await _userJobStateNotifier.AddUserToAllShowsAsync(new BackgroundJobStatusDto()
{
Message = msg,
}, Abp.Notifications.NotificationSeverity.Error);
// sends email
if (!hostUser.EmailAddress.IsNullOrEmpty())
{
await _backgroundJobEmailer.SendBackgroundJobFailInfo(new BackgroundJobEmailInput()
{
EmailAddress = hostUser.EmailAddress,
Message = msg,
JobName = LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "CreateUserToAllShowsJob"),
JobFailedDateTime = DateTime.UtcNow
});
}
Logger.Error(msg, exception);
}
finally
{
await uowForAdduser.CompleteAsync();
}
}
}
if (isException)
{
throw new UserFriendlyException(string.Format(LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "CreateUserToAllShowsJobFailedReinitiateMesage"),
user.Name, string.Join(',', orgNames)));
}
await _userJobStateNotifier.AddUserToAllShowsAsync(new BackgroundJobStatusDto()
{
Message = $"{ LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "CreateUserToAllShowsJob")}:" +
$"{string.Format(LocalizationHelper.GetString(ABCConsts.LocalizationSourceName, "AddtoshowsjobCompletd"), input.UserName, string.Join(',', orgNames))}",
}, Abp.Notifications.NotificationSeverity.Info);
}
}
}
}
}
The code more or less looks like the above. Can you help me understand how to execute job which can safely go through each of the database and do something. Any example should help.
i am looking for a solution to this exact problem. I am dedicating a sub domain called api and the rest all tenant name.
My Angular app is hosted on a S3 bucet and Cloud Front on top of it.
I tried the above solution but of no sucess.
"Cookie “Abp.TenantId” will be soon rejected because it has the “sameSite” attribute set to “none” or an invalid value, without the “secure” attribute. To know more about the “sameSite“ attribute, read https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite"
Any other solution to set the TenantId by reading the sub domain from the angular app ?
https://github.com/aspnetzero/aspnet-zero-core/issues/3217
I believe I need more help on making the changes. My assumption was that if we register the KeyVault as mentioned above or something like below
return new WebHostBuilder()
.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("Secrets.json");
var root = config.Build();
var vault = root["KeyVault:Vault"];
if (!string.IsNullOrEmpty(vault) && !string.IsNullOrEmpty(root["KeyVault:ClientId"]) && !string.IsNullOrEmpty(root["KeyVault:ClientSecret"]))
{
config.AddAzureKeyVault(
$"https://{root["KeyVault:Vault"]}.vault.azure.net/",
root["KeyVault:ClientId"],
root["KeyVault:ClientSecret"],new DefaultKeyVaultSecretManager());
}
else if (!string.IsNullOrEmpty(vault) && !string.IsNullOrEmpty(root["KeyVault:ClientId"]))
{
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates;
Debug.WriteLine("Num of certificates in store: " + certs.Count);
var distinguishedName = new X500DistinguishedName(root["KeyVault:SubjectDistinguishedName"]);
var certFound = certs.Find(X509FindType.FindBySubjectDistinguishedName,
distinguishedName.Name, false).OfType<X509Certificate2>();
if (!certFound.Any())
{
Debug.WriteLine("Unable to find the certificate to authenticate and access key vault");
}
else
{
// found the certificate
config.AddAzureKeyVault($"https://{root["KeyVault:Vault"]}.vault.azure.net/", root["KeyVault:ClientId"], certFound.Single(),new DefaultKeyVaultSecretManager());
store.Close();
}
}
}
})
.UseKestrel(opt => opt.AddServerHeader = false)
it would automatically pick it up.
But it's clearly not happening.
So where should I change it, I tried :
public override void PreInitialize()
{
if (!SkipDbContextRegistration)
{
Configuration.Modules.AbpEfCore().AddDbContext<EDRMSDbContext>(options =>
{
if (options.ExistingConnection != null)
{
EDRMSDbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
}
else
{
EDRMSDbContextConfigurer.Configure(options.DbContextOptions, _keyvault["ConnectionStrings:Default"]);
}
});
}
It worked for host but when i started creating Tenant with a different db connection string it ended ip creating in the host db.
So please provide a clear explanation how to register the Azure Key Vault with Abp as I am sure everyone is going to get benefitted out of this.
I am trying to use the secrets stored in Azure Key Vault. I first created a connection resolver :
public class KeyValutConnectionResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver
{
/// <summary>
/// Reference to the session.
/// </summary>
public IAbpSession AbpSession { get; set; }
private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
private readonly ITenantCache _tenantCache;
private readonly IConfiguration _configuration;
/// <summary>
/// Initializes a new instance of the <see cref="DbPerTenantConnectionStringResolver"/> class.
/// </summary>
public KeyValutConnectionResolver(
IAbpStartupConfiguration configuration,
ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
ITenantCache tenantCache, IConfiguration configuration1)
: base(
configuration)
{
_currentUnitOfWorkProvider = currentUnitOfWorkProvider;
_tenantCache = tenantCache;
_configuration = configuration1;
AbpSession = NullAbpSession.Instance;
}
public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
{
return args.MultiTenancySide == MultiTenancySides.Host ? _configuration["ConnectionStrings:Default"] : GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args));
}
public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args)
{
if (args.TenantId == null)
{
//Requested for host
return base.GetNameOrConnectionString(args);
}
var tenantCacheItem = _tenantCache.Get(args.TenantId.Value);
if (tenantCacheItem.ConnectionString.IsNullOrEmpty())
{
//Tenant has not dedicated database
return base.GetNameOrConnectionString(args);
}
return tenantCacheItem.ConnectionString;
}
protected virtual int? GetCurrentTenantId()
{
return _currentUnitOfWorkProvider.Current != null
? _currentUnitOfWorkProvider.Current.GetTenantId()
: AbpSession.TenantId;
}
}
In EntityFrameworkCore Module Pre-Initialize :
Configuration.ReplaceService<IConnectionStringResolver, KeyValutConnectionResolver>(DependencyLifeStyle.Transient);
When I debug the connection string i Get is from KeyVault but then i don't know where it gets replaced from Appsettings.json.
public EDRMSDbContext(DbContextOptions<EDRMSDbContext> options)
: base(options)
{
// I get the cinnection from KeyVault
}