It seems you're on dev
branch.
Localization logic is changed in v7.0 but npm packages aren't published yet. So, if you run abp install-libs
, 6.0 packages will be installed.
You can overcome this issue by replacing abp.js with following file in the dev branch: https://github.com/abpframework/abp/blob/dev/npm/packs/core/src/abp.js
how to prevent a user who has the privilege to manage the users from deleting the Admin user or Sys Role?
You can replace and customize AppService
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IdentityUserAppService))]
public class MyIdenityUserAppService : IdentityUserAppService
{
// base ctor here
public override async Task DeleteAsync(Guid id)
{
var user = await UserManager.FindByIdAsync(id.ToString());
if(user.UserName == "admin")
{
throw new Exception("can not delete admin");
}
(await UserManager.DeleteAsync(user)).CheckErrors();
}
}
Hi, We've investigated your logs and found this:
2022-11-04 10:23:17.696 +02:00 [INF] Content root path: C:\Source\Token Generation API\code\TokenGen\services\administration\src\TokenGen.AdministrationService.HttpApi.Host\
2022-11-04 10:23:17.900 +02:00 [INF] Request starting HTTP/2 GET https://localhost:44367/ - -
2022-11-04 10:23:20.506 +02:00 [INF] Request starting HTTP/1.1 GET https://localhost:44367/api/abp/application-configuration?api-version=1.0 - 0
2022-11-04 10:23:22.153 +02:00 [ERR] An unhandled exception has occurred while executing the request.
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
TokenGen.AdministrationService.Controllers.HomeController.Index (TokenGen.AdministrationService.HttpApi.Host)
TokenGen.StsApiService.Controllers.HomeController.Index (TokenGen.StsApiService.HttpApi.Host)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.Select(HttpContext httpContext, CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
at Volo.Abp.AspNetCore.Security.AbpSecurityHeadersMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.RequestLocalization.AbpRequestLocalizationMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
at Volo.Abp.AspNetCore.Tracing.AbpCorrelationIdMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location ---
It seems TokenGen.AdministrationService.Controllers.HomeController
and TokenGen.StsApiService.Controllers.HomeController
conflicts for the same route (/
).
Make sure your AdministrationService doesn't have a reference to StsApiService directly. Most probably your StsApiService.HttpApi layer is included in your AdministrationService.HttpApi layer. Can you check if there is a reference?
Yeah sure,
You can use following files to override:
/Pages/Identity/Users/EditModal.cshtml
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Abp.Data
@using Volo.Abp.Identity.Localization
@using Volo.Abp.Identity.Web.Pages.Identity.Users
@using Volo.Abp.Localization
@using Volo.Abp.ObjectExtending
@model EditModalModel
@inject IHtmlLocalizer<IdentityResource> L
@inject IStringLocalizerFactory StringLocalizerFactory
@{
Layout = null;
}
<form method="post" asp-page="/Identity/Users/EditModal">
<abp-modal>
<abp-modal-header title="@L["Edit"].Value"></abp-modal-header>
<abp-modal-body>
<abp-tabs name="create-user-modal-tabs">
<abp-tab title="@L["UserInformation"].Value">
<input asp-for="UserInfo.Id" />
<input asp-for="UserInfo.ConcurrencyStamp" />
<abp-input asp-for="UserInfo.UserName" />
<abp-input asp-for="UserInfo.Name" />
<abp-input asp-for="UserInfo.Surname" />
<abp-input asp-for="UserInfo.Email" label="@(L["EmailAddress"].Value + " * ")" />
@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<EditModalModel.UserInfoViewModel>())
{
if (!propertyInfo.Name.EndsWith("_Text"))
{
if (propertyInfo.Type.IsEnum || !propertyInfo.Lookup.Url.IsNullOrEmpty())
{
if (propertyInfo.Type.IsEnum)
{
Model.UserInfo.ExtraProperties.ToEnum(propertyInfo.Name, propertyInfo.Type);
}
<abp-select asp-for="UserInfo.ExtraProperties[propertyInfo.Name]"
label="@propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)"
autocomplete-api-url="@propertyInfo.Lookup.Url"
autocomplete-selected-item-name="@Model.UserInfo.GetProperty(propertyInfo.Name+"_Text")"
autocomplete-selected-item-value="@Model.UserInfo.GetProperty(propertyInfo.Name)"
autocomplete-filter-param-name="@propertyInfo.Lookup.FilterParamName"
autocomplete-items-property-name="@propertyInfo.Lookup.ResultListPropertyName"
autocomplete-display-property-name="@propertyInfo.Lookup.DisplayPropertyName"
autocomplete-value-property-name="@propertyInfo.Lookup.ValuePropertyName"></abp-select>
}
else
{
<abp-input type="@propertyInfo.GetInputType()"
asp-for="UserInfo.ExtraProperties[propertyInfo.Name]"
label="@propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)"
asp-format="@propertyInfo.GetInputFormatOrNull()"
value="@propertyInfo.GetInputValueOrNull(Model.UserInfo.GetProperty(propertyInfo.Name))" />
}
}
}
<abp-input asp-for="UserInfo.PhoneNumber" />
<abp-input asp-for="UserInfo.IsActive" />
<abp-input asp-for="UserInfo.LockoutEnabled" />
</abp-tab>
@if (Model.Roles.Any())
{
<abp-tab name="Roles" title="@L.GetString("Roles{0}", Model.Roles.Count(r => r.IsAssigned))">
@for (var i = 0; i < Model.Roles.Length; i++)
{
var role = Model.Roles[i];
<abp-input asp-for="@role.IsAssigned"
abp-id-name="@Model.Roles[i].IsAssigned"
label="@role.GetShownName(HtmlEncoder, L.GetString("OU"))"
disabled="@role.IsInheritedFromOu" />
<input asp-for="@role.Name" abp-id-name="@Model.Roles[i].Name" />
}
</abp-tab>
}
@if (Model.OrganizationUnits.Any())
{
<abp-tab name="OrganizationUnits" title="@L.GetString("OrganizationUnits{0}", Model.OrganizationUnits.Count(r => r.IsAssigned))">
<div id="JsTreeCheckable" class="tree jstree jstree-2 jstree-default jstree-checkbox-no-clicked jstree-checkbox-selection" role="tree" aria-multiselectable="true" tabindex="0" aria-activedescendant="j2_1" aria-busy="false" aria-selected="false">
<ul class="jstree-container-ul jstree-children" role="group">
@for (int i = 0; i < Model.OrganizationUnitTreeRootNode.Children.Count; i++)
{
@await Html.PartialAsync("OrganizationUnitTreeNode", new OrganizationUnitTreeNodeModel
{
Depth = 0,
Node = Model.OrganizationUnitTreeRootNode.Children[i],
OrganizationUnits = Model.OrganizationUnits
})
}
</ul>
</div>
@for (var i = 0; i < Model.OrganizationUnits.Length; i++)
{
var organizationUnits = Model.OrganizationUnits[i];
<input asp-for="@organizationUnits.IsAssigned" abp-id-name="@Model.OrganizationUnits[i].IsAssigned" />
<input asp-for="@organizationUnits.DisplayName" abp-id-name="@Model.OrganizationUnits[i].DisplayName" />
<input asp-for="@organizationUnits.Id" abp-id-name="@Model.OrganizationUnits[i].Id" />
}
</abp-tab>
}
</abp-tabs>
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel | AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</form>
<input hidden id="RolesCount" value="@Model.Roles.Count(r => r.IsAssigned)" />
For the models, you can just inherit from IdentityUserModalPageModel
, IdentityUserModalPageModel
and add your custom logic instead of writing all the model again.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Web.Pages.Identity.Users;
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(CreateModalModel))]
public class MyCreateModalModel : CreateModalModel
{
public MyCreateModalModel(IIdentityUserAppService identityUserAppService) : base(identityUserAppService)
{
}
public override async Task OnGetAsync()
{
await base.OnGetAsync(); // Keep base operations.
// your own logic here
}
public override async Task<NoContentResult> OnPostAsync()
{
var result = await base.OnPostAsync();
// your own logic here
return result;
}
}
We solved that problem and it'll be released in the next version of LeptonX.
You can use the following footer override in your application to fix it until the next release.
Create a razor file in your blazor project. Let's say it's MyFooter.razor
Content of this file should be as following:
@using Volo.Abp.DependencyInjection
@using Microsoft.Extensions.DependencyInjection
@using Volo.Abp.AspNetCore.Components.Web.LeptonXTheme.Components.ApplicationLayout.TopMenu
@inherits Footer
@attribute [ExposeServices(typeof(Footer))]
@attribute [Dependency(ReplaceServices = true)]
<div class="lpx-footer">
<div class="d-flex justify-content-between px-4 py-3">
<div>
<span>2022©</span>
<a href="#"> LeptonX Theme</a>
</div>
<div>
<a href="#">About</a>
<a href="#">Contact</a>
</div>
</div>
</div>
You can override the existing cshtml file of the modal via following this documentation: https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface#overriding-a-razor-page-cshtml
To achieve that, create a file in this path:
/Pages/Identity/Users/CreateModal.cshtml
And content of this newly created file should be:
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Abp.Data
@using Volo.Abp.Identity
@using Volo.Abp.Identity.Localization
@using Volo.Abp.Identity.Settings
@using Volo.Abp.Identity.Web.Pages.Identity.Users
@using Volo.Abp.Localization
@using Volo.Abp.ObjectExtending
@using Volo.Abp.Settings
@model Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel
@inject ISettingProvider SettingProvider
@inject IHtmlLocalizer<IdentityResource> L
@inject IStringLocalizerFactory StringLocalizerFactory
@{
Layout = null;
}
<form method="post" asp-page="/Identity/Users/CreateModal">
<abp-modal>
<abp-modal-header title="@L["NewUser"].Value"></abp-modal-header>
<abp-modal-body>
<abp-tabs name="create-user-modal-tabs">
<abp-tab title="@L["UserInformations"].Value">
<abp-input asp-for="UserInfo.UserName" />
<abp-input asp-for="UserInfo.Name" />
<abp-input asp-for="UserInfo.Surname" />
<div class="mb-3">
<label asp-for="UserInfo.Password" class="form-label">@L["Password"] *</label>
<div class="input-group">
<input type="password" class="form-control" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="UserInfo.Password" />
<button class="btn btn-secondary" type="button" id="PasswordVisibilityButton"><i class="fa fa-eye-slash" aria-hidden="true"></i></button>
</div>
<span asp-validation-for="UserInfo.Password"></span>
</div>
<abp-input asp-for="UserInfo.Email" label="@(L["EmailAddress"].Value + " * ")" />
@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<CreateModalModel.UserInfoViewModel>())
{
if (!propertyInfo.Name.EndsWith("_Text"))
{
if (propertyInfo.Type.IsEnum || !propertyInfo.Lookup.Url.IsNullOrEmpty())
{
if (propertyInfo.Type.IsEnum)
{
Model.UserInfo.ExtraProperties.ToEnum(propertyInfo.Name, propertyInfo.Type);
}
<abp-select asp-for="UserInfo.ExtraProperties[propertyInfo.Name]"
label="@propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)"
autocomplete-api-url="@propertyInfo.Lookup.Url"
autocomplete-selected-item-name="@Model.UserInfo.GetProperty(propertyInfo.Name+"_Text")"
autocomplete-selected-item-value="@Model.UserInfo.GetProperty(propertyInfo.Name)"
autocomplete-filter-param-name="@propertyInfo.Lookup.FilterParamName"
autocomplete-items-property-name="@propertyInfo.Lookup.ResultListPropertyName"
autocomplete-display-property-name="@propertyInfo.Lookup.DisplayPropertyName"
autocomplete-value-property-name="@propertyInfo.Lookup.ValuePropertyName"></abp-select>
}
else
{
<abp-input type="@propertyInfo.GetInputType()"
asp-for="UserInfo.ExtraProperties[propertyInfo.Name]"
label="@propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)"
asp-format="@propertyInfo.GetInputFormatOrNull()"
value="@propertyInfo.GetInputValueOrNull(Model.UserInfo.GetProperty(propertyInfo.Name))" />
}
}
}
<abp-input asp-for="UserInfo.PhoneNumber" />
<abp-input asp-for="UserInfo.IsActive" />
<abp-input asp-for="UserInfo.LockoutEnabled" />
@if (await SettingProvider.IsTrueAsync(IdentitySettingNames.SignIn.RequireConfirmedEmail))
{
<abp-input asp-for="UserInfo.SendConfirmationEmail" />
}
</abp-tab>
<abp-tab name="Roles" title="@L.GetString("Roles{0}", Model.Roles.Count(r => r.IsAssigned))">
@for (var i = 0; i < Model.Roles.Length; i++)
{
var role = Model.Roles[i];
if (role.IsDefault)
{
<abp-input asp-for="@role.IsAssigned" abp-id-name="@Model.Roles[i].IsAssigned" label="@HtmlEncoder.Encode(role.Name)" checked="checked" />
}
else
{
<abp-input asp-for="@role.IsAssigned" abp-id-name="@Model.Roles[i].IsAssigned" label="@HtmlEncoder.Encode(role.Name)" />
}
<input asp-for="@role.Name" abp-id-name="@Model.Roles[i].Name" />
}
</abp-tab>
@if (Model.OrganizationUnits.Any())
{
<abp-tab name="OrganizationUnits" title="@L.GetString("OrganizationUnits{0}", Model.OrganizationUnits.Count(r => r.IsAssigned))">
<div id="JsTreeCheckable" class="tree jstree jstree-2 jstree-default jstree-checkbox-no-clicked jstree-checkbox-selection" role="tree" aria-multiselectable="true" tabindex="0" aria-activedescendant="j2_1" aria-busy="false" aria-selected="false">
<ul class="jstree-container-ul jstree-children" role="group">
@for (int i = 0; i < Model.OrganizationUnitTreeRootNode.Children.Count; i++)
{
@await Html.PartialAsync("OrganizationUnitTreeNode", new OrganizationUnitTreeNodeModel
{
Depth = 0,
Node = Model.OrganizationUnitTreeRootNode.Children[i],
OrganizationUnits = Model.OrganizationUnits
})
}
</ul>
</div>
@for (var i = 0; i < Model.OrganizationUnits.Length; i++)
{
var organizationUnits = Model.OrganizationUnits[i];
<input asp-for="@organizationUnits.IsAssigned" abp-id-name="@Model.OrganizationUnits[i].IsAssigned" />
<input asp-for="@organizationUnits.DisplayName" abp-id-name="@Model.OrganizationUnits[i].DisplayName" />
<input asp-for="@organizationUnits.Id" abp-id-name="@Model.OrganizationUnits[i].Id" />
}
</abp-tab>
}
<abp-tab name="your-custom-tab" title="Your Custom Tab">
YOUR TAB CONTENT HERE 👈
</abp-tab>
</abp-tabs>
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel | AbpModalButtons.Save)"></abp-modal-footer>
</abp-modal>
</form>
<input hidden id="RolesCount" value="@Model.Roles.Count(r => r.IsAssigned)" />
Have you checked Tenant-Edition Subscription? Before doing that, you have to configure Payment Configuration properly.
You can use Edition Features to turn on/off tenant features according to the subscription.
Also you can check this out: https://support.abp.io/QA/Questions/3726/Questions-Regarding-Recurring-Stripe-Payment--Retrieval#answer-f874d726-d096-90e6-d993-3a0671092939
Hi christophe.baille
That search feature is written as a plain script in javascript. It shouldn't be dependent on redis. Can you provide Browser Console log if there is an error?
We're still investigating this issue. Since this is a kind of bug, your ticket has been refunded.