Oh, I guess it has something to do with synchronization in the cache. When I checked again after a while, the value I had entered in the DB was there.
What is the default frequency for DB and cache synchronization? Can this be forced?
Hmm... but I tried to achieve this on a experimental way by creating a corresponding database entry in the AbpFeatureValues table for this tenant. That didn't work, and the old value was still displayed in the UI.
Hi,
I already know that, unfortunately, I cannot configure any features on the tenant other than those in the assigned edition. This has already been communicated in the following post: #6579
Now I will describe our situation and I am interested to hear how you would resolve it.
We have various subscriptions (PayPlans, which are assigned to the editions). Depending on the subscription, the tenant gets a different number of maximum users. Identity.MaxUserCount is therefore set on the edition. Now, as an exception, we need to allow a higher number of users for one tenant.
What can we do? Unfortunately, the features cannot be overridden on the tenant. We don't really want to allow a higher number of users for the edition in general.
Could we create a new edition and assign it to the tenant? That would be a good solution. But since a subscription already exists, I cannot change the edition on the tenant (disabled in the UI). What would happen if I changed this in the database? And what effect would this have on subscription renewals, etc.?
Thanks, Adrian
You can call the
await InvokeAsync(StateHasChanged)after changing theSelectedTenantId. Ensure the input has set the correct value.
The problem was that the DOM had not yet been updated at that moment. I solved it as follows:
<form method="post" action="Account/ImpersonateTenant" id="impersonateTenantForm">
<input type="hidden" name="TenantId" value="@SelectedTenantId" />
<input type="hidden" name="TenantUserName" value="admin" />
</form>
private Guid SelectedTenantId { get; set; }
private bool ShouldSubmitImpersonateTenantForm { get; set; } = false;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Submit the impersonateTenantForm after setting the tenant ID
if (ShouldSubmitImpersonateTenantForm)
{
ShouldSubmitImpersonateTenantForm = false;
await JS.InvokeVoidAsync("submitForm", "impersonateTenantForm");
}
}
private async Task OnImpersonateTenantClick(Guid tenantId)
{
SelectedTenantId = tenantId;
ShouldSubmitImpersonateTenantForm = true;
await InvokeAsync(StateHasChanged);
}
function submitForm(formName) {
var form = document.getElementById(formName);
form.submit();
}
You can call the
await InvokeAsync(StateHasChanged)after changing theSelectedTenantId. Ensure the input has set the correct value.
I tried that too, but it didn't work. I thought it should work...
What can you tell me about the ReturnUrl?
I found another solution by creating a form around each button.
One more question: what is the purpose of the ReturnUrl? Is it to go to a specific page on the tenant or to return to a specific page when returning to the host?
Neither seems to work.
I found the problem. It works for me with hard-coded values. The problem is that SelectedTenantId and SelectedTenantUserName are set too late and are not included when the form is submitted.
I guess I'll have to write it in via JavaScript, or do you have a better solution?
I just sent you the log file to your email.
Unfortunately, I haven't been able to get it to work yet. I tried three different variations. With the first two, I get the error message: “There is no user with username: admin!” But this user does exist (it also works when I use impersonation in the other way).
I don't see any error messages or other indications in the log file.
The third option is the one suggested above by the AI bot. But there I get a 404 error.
This is the code:
<form method="post" action="Account/ImpersonateTenant" id="ImpersonationForm1">
<input type="hidden" name="TenantId" value="@SelectedTenantId" />
<input type="hidden" name="TenantUserName" value="@SelectedTenantUserName" />
</form>
<form method="post" data-ajaxForm="false" action="Account/ImpersonateTenant" id="ImpersonationForm2">
<AntiforgeryToken />
<input type="hidden" name="ReturntId" value="@SelectedTenantId" />
<input type="hidden" name="TenantUserName" value="@SelectedTenantUserName" />
<input type="hidden" name="ReturnUrl" value="" />
</form>
// Impersonation variant 1
private async Task SubmitImpersonationForm1(Guid tenantId)
{
Logger.Log(LogLevel.Information, "Start impersonation variant 1");
SelectedTenantId = tenantId.ToString();
SelectedTenantUserName = "admin";
await JSRuntime.InvokeVoidAsync("submitForm", "ImpersonationForm1");
}
// Impersonation variant 2
private async Task SubmitImpersonationForm2(Guid tenantId)
{
Logger.Log(LogLevel.Information, "Start impersonation variant 2");
SelectedTenantId = tenantId.ToString();
SelectedTenantUserName = "admin";
var tokens = Antiforgery.GetAndStoreTokens(HttpContextAccessor.HttpContext);
var requestToken = tokens.RequestToken;
await JSRuntime.InvokeVoidAsync("submitFormWithAntiforgeryToken", "ImpersonationForm2", $"/Account/ImpersonateTenant", requestToken);
}
// Impersonation variant 3
private async Task SubmitImpersonationForm3(Guid tenantId)
{
Logger.Log(LogLevel.Information, "Start impersonation variant 3");
//var response = await Http.PostAsJsonAsync("/api/account/impersonation/tenant", new { tenantId });
var response = await Http.PostAsJsonAsync("https://localhost:44367/api/account/impersonation/tenant", new { tenantId });
if (response.IsSuccessStatusCode)
{
//var result = await response.Content.ReadFromJsonAsync<ImpersonateResultDto>();
//NavigationManager.NavigateTo(result.ImpersonationUrl, forceLoad: true);
}
}
function submitForm(formName) {
var form = document.getElementById(formName);
form.submit();
}
function submitFormWithAntiforgeryToken(formName, uri, requestToken) {
var form = document.getElementById(formName);
// Check if the antiforgery token is present in the form, if the token is not present, create a new hidden input field for the token
var tokenInput = form.querySelector('input[name=__RequestVerificationToken]');
if (!tokenInput) {
{
tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = '__RequestVerificationToken';
form.appendChild(tokenInput);
}
}
// Set the value of the token
tokenInput.value = requestToken;
// Update the form's action with the new URI and submit the form
form.action = uri;
form.submit();
}
Any ideas?
I mean something like this:
await AccountAppService.ImpersonateTenantAsync()
Is this possible?