diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index aa0a3f1222..4c9311a5c7 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -331,6 +331,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.GlobalFeatures", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.GlobalFeatures.Tests", "test\Volo.Abp.GlobalFeatures.Tests\Volo.Abp.GlobalFeatures.Tests.csproj", "{231F1581-AA21-44C3-BF27-51EB3AD5355C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Client.IdentityModel.WebAssembly", "src\Volo.Abp.Http.Client.IdentityModel.WebAssembly\Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj", "{3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Mvc.Client.Common", "src\Volo.Abp.AspNetCore.Mvc.Client.Common\Volo.Abp.AspNetCore.Mvc.Client.Common.csproj", "{8A22D962-016E-474A-8BB7-F831F0ABF3AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly", "src\Volo.Abp.AspNetCore.Components.WebAssembly\Volo.Abp.AspNetCore.Components.WebAssembly.csproj", "{E1A62D10-F2FB-4040-BD60-11A3934058DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlazoriseUI", "src\Volo.Abp.BlazoriseUI\Volo.Abp.BlazoriseUI.csproj", "{4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme", "src\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme\Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj", "{ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Components.WebAssembly.Theming", "src\Volo.Abp.AspNetCore.Components.WebAssembly.Theming\Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj", "{29CA7471-4E3E-4E75-8B33-001DDF682F01}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Autofac.WebAssembly", "src\Volo.Abp.Autofac.WebAssembly\Volo.Abp.Autofac.WebAssembly.csproj", "{37F89B0B-1C6B-426F-A5EE-676D1956D9E9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -985,6 +999,34 @@ Global {231F1581-AA21-44C3-BF27-51EB3AD5355C}.Debug|Any CPU.Build.0 = Debug|Any CPU {231F1581-AA21-44C3-BF27-51EB3AD5355C}.Release|Any CPU.ActiveCfg = Release|Any CPU {231F1581-AA21-44C3-BF27-51EB3AD5355C}.Release|Any CPU.Build.0 = Release|Any CPU + {3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8}.Release|Any CPU.Build.0 = Release|Any CPU + {8A22D962-016E-474A-8BB7-F831F0ABF3AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A22D962-016E-474A-8BB7-F831F0ABF3AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A22D962-016E-474A-8BB7-F831F0ABF3AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A22D962-016E-474A-8BB7-F831F0ABF3AC}.Release|Any CPU.Build.0 = Release|Any CPU + {E1A62D10-F2FB-4040-BD60-11A3934058DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1A62D10-F2FB-4040-BD60-11A3934058DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1A62D10-F2FB-4040-BD60-11A3934058DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1A62D10-F2FB-4040-BD60-11A3934058DF}.Release|Any CPU.Build.0 = Release|Any CPU + {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5}.Release|Any CPU.Build.0 = Release|Any CPU + {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695}.Release|Any CPU.Build.0 = Release|Any CPU + {29CA7471-4E3E-4E75-8B33-001DDF682F01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29CA7471-4E3E-4E75-8B33-001DDF682F01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29CA7471-4E3E-4E75-8B33-001DDF682F01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29CA7471-4E3E-4E75-8B33-001DDF682F01}.Release|Any CPU.Build.0 = Release|Any CPU + {37F89B0B-1C6B-426F-A5EE-676D1956D9E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37F89B0B-1C6B-426F-A5EE-676D1956D9E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37F89B0B-1C6B-426F-A5EE-676D1956D9E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37F89B0B-1C6B-426F-A5EE-676D1956D9E9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1152,6 +1194,13 @@ Global {C1D891B0-AE83-42CB-987D-425A2787DE78} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {04F44063-C952-403A-815F-EFB778BDA125} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {231F1581-AA21-44C3-BF27-51EB3AD5355C} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {8A22D962-016E-474A-8BB7-F831F0ABF3AC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {E1A62D10-F2FB-4040-BD60-11A3934058DF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {4EBDDB1B-D6C5-4FAE-B5A7-2171B18CDFA5} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {ABC27C10-C0FF-44CB-B4FF-A09C0B79F695} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {29CA7471-4E3E-4E75-8B33-001DDF682F01} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {37F89B0B-1C6B-426F-A5EE-676D1956D9E9} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicTheme.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicTheme.cs new file mode 100644 index 0000000000..74c81ed72c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/AbpAspNetCoreComponentsWebAssemblyBasicTheme.cs @@ -0,0 +1,20 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme +{ + [DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingModule) + )] + public class AbpAspNetCoreComponentsWebAssemblyBasicThemeModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule).Assembly); + }); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/FodyWeavers.xml new file mode 100644 index 0000000000..bc5a74a236 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Pages/Authentication.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Pages/Authentication.razor new file mode 100644 index 0000000000..e237cea555 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Pages/Authentication.razor @@ -0,0 +1,7 @@ +@page "/authentication/{action}" +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication + + +@code{ + [Parameter] public string Action { get; set; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/App.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/App.razor new file mode 100644 index 0000000000..b6bba50bab --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/App.razor @@ -0,0 +1,27 @@ +@using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing +@using Microsoft.Extensions.Options +@inject IOptions RouterOptions + + + + + + @if (!context.User.Identity.IsAuthenticated) + { + + } + else + { +

You are not authorized to access this resource.

+ } +
+
+
+ + +

Sorry, there's nothing at this address.

+
+
+
+
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LanguageSwitch.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LanguageSwitch.razor new file mode 100644 index 0000000000..8c7b348f7d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LanguageSwitch.razor @@ -0,0 +1,65 @@ +@using Volo.Abp.Localization +@using System.Globalization +@using System.Collections.Immutable +@inject ILanguageProvider LanguageProvider +@inject IJSRuntime JsRuntime +@if (_otherLanguages != null && _otherLanguages.Any()) +{ + + + @_currentLanguage.DisplayName + + + @foreach (var language in _otherLanguages) + { + @language.DisplayName + } + + +} +@code { + private IReadOnlyList _otherLanguages; + private LanguageInfo _currentLanguage; + + protected override async Task OnInitializedAsync() + { + var selectedLanguageName = await JsRuntime.InvokeAsync( + "localStorage.getItem", + "Abp.SelectedLanguage" + ); + + _otherLanguages = await LanguageProvider.GetLanguagesAsync(); + + if (!_otherLanguages.Any()) + { + return; + } + + if (!selectedLanguageName.IsNullOrWhiteSpace()) + { + _currentLanguage = _otherLanguages.FirstOrDefault(l => l.UiCultureName == selectedLanguageName); + } + + if (_currentLanguage == null) + { + _currentLanguage = _otherLanguages.FirstOrDefault(l => l.UiCultureName == CultureInfo.CurrentUICulture.Name); + } + + if (_currentLanguage == null) + { + _currentLanguage = _otherLanguages.FirstOrDefault(); + } + + _otherLanguages = _otherLanguages.Where(l => l != _currentLanguage).ToImmutableList(); + } + + private async Task ChangeLanguageAsync(LanguageInfo language) + { + await JsRuntime.InvokeVoidAsync( + "localStorage.setItem", + "Abp.SelectedLanguage", language.UiCultureName + ); + + await JsRuntime.InvokeVoidAsync("location.reload"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor new file mode 100644 index 0000000000..925783e16e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor @@ -0,0 +1,27 @@ +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@using Volo.Abp.Users +@inject ICurrentUser CurrentUser +@inject NavigationManager Navigation +@inject SignOutSessionStateManager SignOutManager + + + + + @CurrentUser.UserName + + + Logout + + + + + Log in + + +@code{ + private async Task BeginSignOut() + { + await SignOutManager.SetSignOutState(); + Navigation.NavigateTo("authentication/logout"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor new file mode 100644 index 0000000000..af88212f0a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor @@ -0,0 +1,29 @@ +@inherits LayoutComponentBase +@using Volo.Abp.Ui.Branding +@inject IBrandingProvider BrandingProvider + +
+ @Body +
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor new file mode 100644 index 0000000000..e1ede1c0fd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor @@ -0,0 +1,7 @@ +@if (Menu != null) +{ + foreach (var menuItem in Menu.Items) + { + + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor.cs new file mode 100644 index 0000000000..e47603e163 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenu.razor.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic +{ + public partial class NavMenu + { + [Inject] protected IMenuManager MenuManager { get; set; } + + protected ApplicationMenu Menu { get; set; } + + private bool collapseNavMenu = true; + + private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; + + protected override async Task OnInitializedAsync() + { + Menu = await MenuManager.GetAsync(StandardMenus.Main); + } + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenuItem.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenuItem.razor new file mode 100644 index 0000000000..8194896f61 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/NavMenuItem.razor @@ -0,0 +1,52 @@ +@using Volo.Abp.UI.Navigation +@{ + var elementId = MenuItem.ElementId ?? "MenuItem_" + MenuItem.Name.Replace(".", "_"); + var cssClass = string.IsNullOrEmpty(MenuItem.CssClass) ? string.Empty : MenuItem.CssClass; + var url = string.IsNullOrEmpty(MenuItem.Url) ? "#" : MenuItem.Url; +} +@if (MenuItem.IsLeaf) +{ + if (MenuItem.Url != null) + { + + } +} +else +{ + + +} +@code { + [Parameter] + public ApplicationMenuItem MenuItem { get; set; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/RedirectToLogin.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/RedirectToLogin.razor new file mode 100644 index 0000000000..b535ae4216 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/RedirectToLogin.razor @@ -0,0 +1,8 @@ +@inject NavigationManager Navigation +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@code { + protected override void OnInitialized() + { + Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/_Imports.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/_Imports.razor new file mode 100644 index 0000000000..f1ebc76453 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using Volo.Abp.AspNetCore.Components.WebAssembly +@using Blazorise +@using Blazorise.DataGrid diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj new file mode 100644 index 0000000000..783af0b202 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.csproj @@ -0,0 +1,16 @@ + + + + + + + netstandard2.1 + 3.0 + Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/wwwroot/theme.css b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/wwwroot/theme.css new file mode 100644 index 0000000000..fc47596044 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/wwwroot/theme.css @@ -0,0 +1,105 @@ + +#main-navbar-tools a.dropdown-toggle { + text-decoration: none; + color: #fff; +} + +.navbar .dropdown-submenu { + position: relative; +} +.navbar .dropdown-menu { + margin: 0; + padding: 0; +} + .navbar .dropdown-menu a { + font-size: .9em; + padding: 10px 15px; + display: block; + min-width: 210px; + text-align: left; + border-radius: 0.25rem; + min-height: 44px; + } +.navbar .dropdown-submenu a::after { + transform: rotate(-90deg); + position: absolute; + right: 16px; + top: 18px; +} +.navbar .dropdown-submenu .dropdown-menu { + top: 0; + left: 100%; +} + +.card-header .btn { + padding: 2px 6px; +} +.card-header h5 { + margin: 0; +} +.container > .card { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; +} + +@media screen and (min-width: 768px) { + .navbar .dropdown:hover > .dropdown-menu { + display: block; + } + + .navbar .dropdown-submenu:hover > .dropdown-menu { + display: block; + } +} +.input-validation-error { + border-color: #dc3545; +} +.field-validation-error { + font-size: 0.8em; +} + +.dataTables_scrollBody { + min-height: 248px; +} + +div.dataTables_wrapper div.dataTables_info { + padding-top: 11px; + white-space: nowrap; +} + +div.dataTables_wrapper div.dataTables_length label { + padding-top: 10px; + margin-bottom: 0; +} + +.rtl .dropdown-menu-right { + right: auto; + left: 0; +} + + .rtl .dropdown-menu-right a { + text-align: right; + } + +.rtl .navbar .dropdown-menu a { + text-align: right; +} +.rtl .navbar .dropdown-submenu .dropdown-menu { + top: 0; + left: auto; + right: 100%; +} + +/* TEMP */ + +.navbar-dark .navbar-nav .nav-link { + color: #000 !important; +} + +.navbar-nav > .nav-item > .nav-link, +.navbar-nav > .nav-item > .dropdown > .nav-link { + color: #fff !important; +} + +.navbar-nav>.nav-item>div>button{ + color:#fff; +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/wwwroot/theme.js b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/wwwroot/theme.js new file mode 100644 index 0000000000..8a5b94c7c6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/wwwroot/theme.js @@ -0,0 +1,16 @@ +$(function () { + $('.dropdown-menu a.dropdown-toggle').on('click', function (e) { + if (!$(this).next().hasClass('show')) { + $(this).parents('.dropdown-menu').first().find('.show').removeClass("show"); + } + + var $subMenu = $(this).next(".dropdown-menu"); + $subMenu.toggleClass('show'); + + $(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function (e) { + $('.dropdown-submenu .show').removeClass("show"); + }); + + return false; + }); +}); \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpAspNetCoreComponentsWebAssemblyThemingModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpAspNetCoreComponentsWebAssemblyThemingModule.cs new file mode 100644 index 0000000000..867e01467f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpAspNetCoreComponentsWebAssemblyThemingModule.cs @@ -0,0 +1,17 @@ +using Volo.Abp.BlazoriseUI; +using Volo.Abp.Http.Client.IdentityModel.WebAssembly; +using Volo.Abp.Modularity; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming +{ + [DependsOn( + typeof(AbpBlazoriseUIModule), + typeof(AbpHttpClientIdentityModelWebAssemblyModule), + typeof(AbpUiNavigationModule) + )] + public class AbpAspNetCoreComponentsWebAssemblyThemingModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Routing/AbpBlazorRouterOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Routing/AbpBlazorRouterOptions.cs new file mode 100644 index 0000000000..107fc9b078 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Routing/AbpBlazorRouterOptions.cs @@ -0,0 +1,16 @@ +using System.Reflection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing +{ + public class AbpRouterOptions + { + public Assembly AppAssembly { get; set; } + + public RouterAssemblyList AdditionalAssemblies { get; } + + public AbpRouterOptions() + { + AdditionalAssemblies = new RouterAssemblyList(); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Routing/RouterAssemblyList.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Routing/RouterAssemblyList.cs new file mode 100644 index 0000000000..e7a4a49343 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Routing/RouterAssemblyList.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing +{ + public class RouterAssemblyList : List + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj new file mode 100644 index 0000000000..86902f7038 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.csproj @@ -0,0 +1,17 @@ + + + + + + + netstandard2.1 + 3.0 + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/FodyWeavers.xml new file mode 100644 index 0000000000..bc5a74a236 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs new file mode 100644 index 0000000000..865a6f0edc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyHostBuilderExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.AspNetCore.Components.WebAssembly; +using Volo.Abp.AspNetCore.Mvc.Client; +using Volo.Abp.Modularity; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + public static class AbpWebAssemblyHostBuilderExtensions + { + public static IAbpApplicationWithExternalServiceProvider AddApplication( + [NotNull] this WebAssemblyHostBuilder builder, + Action options) + where TStartupModule : IAbpModule + { + Check.NotNull(builder, nameof(builder)); + + builder.Services.AddSingleton(builder.Configuration); + builder.Services.AddSingleton(builder); + + var application = builder.Services.AddApplication(opts => + { + options?.Invoke(new AbpWebAssemblyApplicationCreationOptions(builder, opts)); + }); + + return application; + } + + public static async Task InitializeAsync( + [NotNull] this IAbpApplicationWithExternalServiceProvider application, + [NotNull] IServiceProvider serviceProvider) + { + Check.NotNull(application, nameof(application)); + Check.NotNull(serviceProvider, nameof(serviceProvider)); + + application.Initialize(serviceProvider); + + using (var scope = serviceProvider.CreateScope()) + { + await scope.ServiceProvider + .GetRequiredService() + .InitializeAsync(); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj new file mode 100644 index 0000000000..89f2d08b49 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo.Abp.AspNetCore.Components.WebAssembly.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.1 + Volo.Abp.AspNetCore.Components.WebAssembly + Volo.Abp.AspNetCore.Components.WebAssembly + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs new file mode 100644 index 0000000000..db6109e55a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.Client; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; +using Volo.Abp.UI; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + [DependsOn( + typeof(AbpAspNetCoreMvcClientCommonModule), + typeof(AbpUiModule) + )] + public class AbpAspNetCoreComponentsWebAssemblyModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.ProxyClientBuildActions.Add((_, builder) => + { + builder.AddHttpMessageHandler(); + }); + }); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpBlazorClientHttpMessageHandler.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpBlazorClientHttpMessageHandler.cs new file mode 100644 index 0000000000..7d59042806 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpBlazorClientHttpMessageHandler.cs @@ -0,0 +1,42 @@ +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.JSInterop; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class AbpBlazorClientHttpMessageHandler : DelegatingHandler, ITransientDependency + { + private readonly IJSRuntime _jsRuntime; + + public AbpBlazorClientHttpMessageHandler(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + await SetLanguageAsync(request, cancellationToken); + + return await base.SendAsync(request, cancellationToken); + } + + private async Task SetLanguageAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var selectedLanguage = await _jsRuntime.InvokeAsync( + "localStorage.getItem", + cancellationToken, + "Abp.SelectedLanguage" + ); + + if (!selectedLanguage.IsNullOrWhiteSpace()) + { + request.Headers.AcceptLanguage.Clear(); + request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(selectedLanguage)); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpWebAssemblyApplicationCreationOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpWebAssemblyApplicationCreationOptions.cs new file mode 100644 index 0000000000..54fdf7edb4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpWebAssemblyApplicationCreationOptions.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class AbpWebAssemblyApplicationCreationOptions + { + public WebAssemblyHostBuilder HostBuilder { get; } + + public AbpApplicationCreationOptions ApplicationCreationOptions { get; } + + public AbpWebAssemblyApplicationCreationOptions( + WebAssemblyHostBuilder hostBuilder, + AbpApplicationCreationOptions applicationCreationOptions) + { + HostBuilder = hostBuilder; + ApplicationCreationOptions = applicationCreationOptions; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ApplicationConfigurationCache.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ApplicationConfigurationCache.cs new file mode 100644 index 0000000000..df41c996b6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/ApplicationConfigurationCache.cs @@ -0,0 +1,20 @@ +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class ApplicationConfigurationCache : ISingletonDependency + { + protected ApplicationConfigurationDto Configuration { get; set; } + + public virtual ApplicationConfigurationDto Get() + { + return Configuration; + } + + internal void Set(ApplicationConfigurationDto configuration) + { + Configuration = configuration; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiMessageService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiMessageService.cs new file mode 100644 index 0000000000..bad1bab7fc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiMessageService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public interface IUiMessageService + { + Task ConfirmAsync(string message, string title = null); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageService.cs new file mode 100644 index 0000000000..2e353b5e54 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageService.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Microsoft.JSInterop; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class UiMessageService : IUiMessageService, ITransientDependency + { + protected IJSRuntime JsRuntime { get; } + + public UiMessageService(IJSRuntime jsRuntime) + { + JsRuntime = jsRuntime; + } + + public async Task ConfirmAsync(string message, string title = null) + { + //TODO: Implement with sweetalert in a new package + return await JsRuntime.InvokeAsync("confirm", message); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs new file mode 100644 index 0000000000..e403a8cfe9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.AspNetCore.Mvc.Client; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.DynamicProxying; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency + { + protected IHttpClientProxy Proxy { get; } + + protected ApplicationConfigurationCache Cache { get; } + + public WebAssemblyCachedApplicationConfigurationClient( + IHttpClientProxy proxy, + ApplicationConfigurationCache cache) + { + Proxy = proxy; + Cache = cache; + } + + public virtual async Task InitializeAsync() + { + Cache.Set(await Proxy.Service.GetAsync()); + } + + public virtual Task GetAsync() + { + return Task.FromResult(GetConfigurationByChecking()); + } + + public virtual ApplicationConfigurationDto Get() + { + return GetConfigurationByChecking(); + } + + private ApplicationConfigurationDto GetConfigurationByChecking() + { + var configuration = Cache.Get(); + if (configuration == null) + { + throw new AbpException( + $"{nameof(WebAssemblyCachedApplicationConfigurationClient)} should be initialized before using it."); + } + + return configuration; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentPrincipalAccessor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentPrincipalAccessor.cs new file mode 100644 index 0000000000..3c252ebe5e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentPrincipalAccessor.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using Volo.Abp.AspNetCore.Mvc.Client; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class WebAssemblyCurrentPrincipalAccessor : CurrentPrincipalAccessorBase, ITransientDependency + { + protected ICachedApplicationConfigurationClient ConfigurationClient { get; } + + public WebAssemblyCurrentPrincipalAccessor( + ICachedApplicationConfigurationClient configurationClient) + { + ConfigurationClient = configurationClient; + } + + protected override ClaimsPrincipal GetClaimsPrincipal() + { + var configuration = ConfigurationClient.Get(); + + var claims = new List(); + + if (!configuration.CurrentUser.UserName.IsNullOrWhiteSpace()) + { + claims.Add(new Claim(AbpClaimTypes.UserName,configuration.CurrentUser.UserName)); + } + + if (!configuration.CurrentUser.Email.IsNullOrWhiteSpace()) + { + claims.Add(new Claim(AbpClaimTypes.Email,configuration.CurrentUser.Email)); + } + + if (configuration.CurrentUser.Id != null) + { + claims.Add(new Claim(AbpClaimTypes.UserId,configuration.CurrentUser.Id.ToString())); + } + + if (configuration.CurrentUser.TenantId != null) + { + claims.Add(new Claim(AbpClaimTypes.TenantId,configuration.CurrentUser.TenantId.ToString())); + } + else if (configuration.CurrentTenant.Id != null) + { + claims.Add(new Claim(AbpClaimTypes.TenantId,configuration.CurrentTenant.Id.ToString())); + } + + foreach (var role in configuration.CurrentUser.Roles) + { + claims.Add(new Claim(AbpClaimTypes.Role, role)); + } + + return new ClaimsPrincipal(new ClaimsIdentity(claims)); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyRemoteTenantStore.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyRemoteTenantStore.cs new file mode 100644 index 0000000000..ebe170cfa8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyRemoteTenantStore.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.MultiTenancy; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.DynamicProxying; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class WebAssemblyRemoteTenantStore : ITenantStore, ITransientDependency + { + protected IHttpClientProxy Proxy { get; } + + public WebAssemblyRemoteTenantStore( + IHttpClientProxy proxy) + { + Proxy = proxy; + } + + public async Task FindAsync(string name) + { + //TODO: Cache + + return CreateTenantConfiguration(await Proxy.Service.FindTenantByNameAsync(name)); + } + + public async Task FindAsync(Guid id) + { + //TODO: Cache + + return CreateTenantConfiguration(await Proxy.Service.FindTenantByIdAsync(id)); + } + + public TenantConfiguration Find(string name) + { + //TODO: Cache + + return AsyncHelper.RunSync(async () => CreateTenantConfiguration(await Proxy.Service.FindTenantByNameAsync(name))); + } + + public TenantConfiguration Find(Guid id) + { + //TODO: Cache + + return AsyncHelper.RunSync(async () => CreateTenantConfiguration(await Proxy.Service.FindTenantByIdAsync(id))); + } + + protected virtual TenantConfiguration CreateTenantConfiguration(FindTenantResultDto tenantResultDto) + { + if (!tenantResultDto.Success || tenantResultDto.TenantId == null) + { + return null; + } + + return new TenantConfiguration(tenantResultDto.TenantId.Value, tenantResultDto.Name); + } + + protected virtual string CreateCacheKey(string tenantName) + { + return $"RemoteTenantStore_Name_{tenantName}"; + } + + protected virtual string CreateCacheKey(Guid tenantId) + { + return $"RemoteTenantStore_Id_{tenantId:N}"; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/FodyWeavers.xml new file mode 100644 index 0000000000..bc5a74a236 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo.Abp.AspNetCore.Mvc.Client.Common.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo.Abp.AspNetCore.Mvc.Client.Common.csproj new file mode 100644 index 0000000000..dc672d842f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo.Abp.AspNetCore.Mvc.Client.Common.csproj @@ -0,0 +1,24 @@ + + + + + + + netstandard2.0 + Volo.Abp.AspNetCore.Mvc.Client.Common + Volo.Abp.AspNetCore.Mvc.Client.Common + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs new file mode 100644 index 0000000000..de5e80ff64 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientCommonModule.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Caching; +using Volo.Abp.Http.Client; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.Client +{ + [DependsOn( + typeof(AbpHttpClientModule), + typeof(AbpAspNetCoreMvcContractsModule), + typeof(AbpCachingModule), + typeof(AbpLocalizationModule) + )] + public class AbpAspNetCoreMvcClientCommonModule : AbpModule + { + public const string RemoteServiceName = "AbpMvcClient"; + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddHttpClientProxies( + typeof(AbpAspNetCoreMvcContractsModule).Assembly, + RemoteServiceName, + asDefaultServices: false + ); + + Configure(options => + { + options.GlobalContributors.Add(); + }); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/ICachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/ICachedApplicationConfigurationClient.cs similarity index 76% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/ICachedApplicationConfigurationClient.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/ICachedApplicationConfigurationClient.cs index 71d9d8cddf..d08ca7013e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/ICachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/ICachedApplicationConfigurationClient.cs @@ -5,6 +5,10 @@ namespace Volo.Abp.AspNetCore.Mvc.Client { public interface ICachedApplicationConfigurationClient { + Task InitializeAsync(); + Task GetAsync(); + + ApplicationConfigurationDto Get(); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteFeatureChecker.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteFeatureChecker.cs similarity index 100% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteFeatureChecker.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteFeatureChecker.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLanguageProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLanguageProvider.cs similarity index 100% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLanguageProvider.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLanguageProvider.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs similarity index 94% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs index d60256a501..f8502fce78 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteLocalizationContributor.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Localization; -using Volo.Abp.Threading; namespace Volo.Abp.AspNetCore.Mvc.Client { @@ -55,7 +54,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Client private Dictionary GetResourceOrNull() { - var applicationConfigurationDto = AsyncHelper.RunSync(() => _applicationConfigurationClient.GetAsync()); + var applicationConfigurationDto = _applicationConfigurationClient.Get(); var resource = applicationConfigurationDto .Localization.Values @@ -69,4 +68,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Client return resource; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs similarity index 100% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemotePermissionChecker.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteSettingProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteSettingProvider.cs similarity index 100% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteSettingProvider.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteSettingProvider.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo.Abp.AspNetCore.Mvc.Client.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo.Abp.AspNetCore.Mvc.Client.csproj index 87641365b2..aa11e602a1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo.Abp.AspNetCore.Mvc.Client.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo.Abp.AspNetCore.Mvc.Client.csproj @@ -17,10 +17,7 @@ - - - - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs index 6c306073c5..1186b48ae7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientModule.cs @@ -1,33 +1,11 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Caching; -using Volo.Abp.Http.Client; -using Volo.Abp.Localization; -using Volo.Abp.Modularity; +using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.Client { [DependsOn( - typeof(AbpHttpClientModule), - typeof(AbpAspNetCoreMvcContractsModule), - typeof(AbpCachingModule), - typeof(AbpLocalizationModule) + typeof(AbpAspNetCoreMvcClientCommonModule) )] public class AbpAspNetCoreMvcClientModule : AbpModule { - public const string RemoteServiceName = "AbpMvcClient"; - - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddHttpClientProxies( - typeof(AbpAspNetCoreMvcContractsModule).Assembly, - RemoteServiceName, - asDefaultServices: false - ); - - Configure(options => - { - options.GlobalContributors.Add(); - }); - } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs similarity index 76% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClient.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs index 1fbb72bb3e..a357d959b2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/CachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs @@ -12,14 +12,14 @@ using Volo.Abp.Users; namespace Volo.Abp.AspNetCore.Mvc.Client { - public class CachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency + public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency { protected IHttpContextAccessor HttpContextAccessor { get; } protected IHttpClientProxy Proxy { get; } protected ICurrentUser CurrentUser { get; } protected IDistributedCache Cache { get; } - public CachedApplicationConfigurationClient( + public MvcCachedApplicationConfigurationClient( IDistributedCache cache, IHttpClientProxy proxy, ICurrentUser currentUser, @@ -31,6 +31,11 @@ namespace Volo.Abp.AspNetCore.Mvc.Client Cache = cache; } + public async Task InitializeAsync() + { + await GetAsync(); + } + public async Task GetAsync() { var cacheKey = CreateCacheKey(); @@ -58,9 +63,22 @@ namespace Volo.Abp.AspNetCore.Mvc.Client return configuration; } + public ApplicationConfigurationDto Get() + { + var cacheKey = CreateCacheKey(); + var httpContext = HttpContextAccessor?.HttpContext; + + if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) + { + return configuration; + } + + return AsyncHelper.RunSync(GetAsync); + } + protected virtual string CreateCacheKey() { return $"ApplicationConfiguration_{CurrentUser.Id?.ToString("N") ?? "Anonymous"}_{CultureInfo.CurrentUICulture.Name}"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteTenantStore.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcRemoteTenantStore.cs similarity index 97% rename from framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteTenantStore.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcRemoteTenantStore.cs index 236387823c..2b6a272d64 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/RemoteTenantStore.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcRemoteTenantStore.cs @@ -11,13 +11,13 @@ using Volo.Abp.Threading; namespace Volo.Abp.AspNetCore.Mvc.Client { - public class RemoteTenantStore : ITenantStore, ITransientDependency + public class MvcRemoteTenantStore : ITenantStore, ITransientDependency { protected IHttpClientProxy Proxy { get; } protected IHttpContextAccessor HttpContextAccessor { get; } protected IDistributedCache Cache { get; } - public RemoteTenantStore( + public MvcRemoteTenantStore( IHttpClientProxy proxy, IHttpContextAccessor httpContextAccessor, IDistributedCache cache) @@ -120,7 +120,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Client { return tenantConfiguration; } - + tenantConfiguration = Cache.GetOrAdd( cacheKey, () => AsyncHelper.RunSync(async () => CreateTenantConfiguration(await Proxy.Service.FindTenantByIdAsync(id))), diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo.Abp.AspNetCore.Mvc.Contracts.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo.Abp.AspNetCore.Mvc.Contracts.csproj index a979cfa637..04b2e7ec59 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo.Abp.AspNetCore.Mvc.Contracts.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo.Abp.AspNetCore.Mvc.Contracts.csproj @@ -4,7 +4,7 @@ - netcoreapp3.1 + netstandard2.0 Volo.Abp.AspNetCore.Mvc.Contracts Volo.Abp.AspNetCore.Mvc.Contracts $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; diff --git a/framework/src/Volo.Abp.Autofac.WebAssembly/FodyWeavers.xml b/framework/src/Volo.Abp.Autofac.WebAssembly/FodyWeavers.xml new file mode 100644 index 0000000000..bc5a74a236 --- /dev/null +++ b/framework/src/Volo.Abp.Autofac.WebAssembly/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.Autofac.WebAssembly/FodyWeavers.xsd b/framework/src/Volo.Abp.Autofac.WebAssembly/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.Autofac.WebAssembly/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Autofac.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyApplicationCreationOptionsAutofacExtensions.cs b/framework/src/Volo.Abp.Autofac.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyApplicationCreationOptionsAutofacExtensions.cs new file mode 100644 index 0000000000..427ae3ddf6 --- /dev/null +++ b/framework/src/Volo.Abp.Autofac.WebAssembly/Microsoft/AspNetCore/Components/WebAssembly/Hosting/AbpWebAssemblyApplicationCreationOptionsAutofacExtensions.cs @@ -0,0 +1,23 @@ +using System; +using Autofac; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.AspNetCore.Components.WebAssembly; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + public static class AbpWebAssemblyApplicationCreationOptionsAutofacExtensions + { + public static void UseAutofac( + [NotNull] this AbpWebAssemblyApplicationCreationOptions options, + [CanBeNull] Action configure = null) + { + options.HostBuilder.Services.AddAutofacServiceProviderFactory(); + options.HostBuilder.ConfigureContainer( + options.HostBuilder.Services.GetSingletonInstance>(), + configure + ); + } + } +} diff --git a/framework/src/Volo.Abp.Autofac.WebAssembly/Volo.Abp.Autofac.WebAssembly.csproj b/framework/src/Volo.Abp.Autofac.WebAssembly/Volo.Abp.Autofac.WebAssembly.csproj new file mode 100644 index 0000000000..6d03439a85 --- /dev/null +++ b/framework/src/Volo.Abp.Autofac.WebAssembly/Volo.Abp.Autofac.WebAssembly.csproj @@ -0,0 +1,16 @@ + + + + + + + netstandard2.1 + + + + + + + + + diff --git a/framework/src/Volo.Abp.Autofac.WebAssembly/Volo/Abp/Autofac/WebAssembly/AbpAutofacWebAssemblyModule.cs b/framework/src/Volo.Abp.Autofac.WebAssembly/Volo/Abp/Autofac/WebAssembly/AbpAutofacWebAssemblyModule.cs new file mode 100644 index 0000000000..361d3daf85 --- /dev/null +++ b/framework/src/Volo.Abp.Autofac.WebAssembly/Volo/Abp/Autofac/WebAssembly/AbpAutofacWebAssemblyModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Autofac.WebAssembly +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpAspNetCoreComponentsWebAssemblyModule) + )] + public class AbpAutofacWebAssemblyModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.Autofac/Volo/Abp/AbpAutofacAbpApplicationCreationOptionsExtensions.cs b/framework/src/Volo.Abp.Autofac/Volo/Abp/AbpAutofacAbpApplicationCreationOptionsExtensions.cs index f632c11495..56b91dea2d 100644 --- a/framework/src/Volo.Abp.Autofac/Volo/Abp/AbpAutofacAbpApplicationCreationOptionsExtensions.cs +++ b/framework/src/Volo.Abp.Autofac/Volo/Abp/AbpAutofacAbpApplicationCreationOptionsExtensions.cs @@ -8,9 +8,22 @@ namespace Volo.Abp { public static void UseAutofac(this AbpApplicationCreationOptions options) { - var builder = new ContainerBuilder(); - options.Services.AddObjectAccessor(builder); - options.Services.AddSingleton((IServiceProviderFactory) new AbpAutofacServiceProviderFactory(builder)); + options.Services.AddAutofacServiceProviderFactory(); + } + + public static AbpAutofacServiceProviderFactory AddAutofacServiceProviderFactory(this IServiceCollection services) + { + return services.AddAutofacServiceProviderFactory(new ContainerBuilder()); + } + + public static AbpAutofacServiceProviderFactory AddAutofacServiceProviderFactory(this IServiceCollection services, ContainerBuilder containerBuilder) + { + var factory = new AbpAutofacServiceProviderFactory(containerBuilder); + + services.AddObjectAccessor(containerBuilder); + services.AddSingleton((IServiceProviderFactory) factory); + + return factory; } } } diff --git a/framework/src/Volo.Abp.BlazoriseUI/FodyWeavers.xml b/framework/src/Volo.Abp.BlazoriseUI/FodyWeavers.xml new file mode 100644 index 0000000000..bc5a74a236 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.BlazoriseUI/FodyWeavers.xsd b/framework/src/Volo.Abp.BlazoriseUI/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj new file mode 100644 index 0000000000..f7875a0f4a --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -0,0 +1,24 @@ + + + + + + + netstandard2.1 + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo/Abp/BlazoriseUI/AbpBlazoriseModule.cs b/framework/src/Volo.Abp.BlazoriseUI/Volo/Abp/BlazoriseUI/AbpBlazoriseModule.cs new file mode 100644 index 0000000000..716f36b1ea --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo/Abp/BlazoriseUI/AbpBlazoriseModule.cs @@ -0,0 +1,23 @@ +using Blazorise; +using Volo.Abp.AspNetCore.Components.WebAssembly; +using Volo.Abp.Modularity; + +namespace Volo.Abp.BlazoriseUI +{ + [DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyModule) + )] + public class AbpBlazoriseUIModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + ConfigureBlazorise(context); + } + + private void ConfigureBlazorise(ServiceConfigurationContext context) + { + context.Services + .AddBlazorise(); + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo/Abp/BlazoriseUI/BlazoriseCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/Volo/Abp/BlazoriseUI/BlazoriseCrudPageBase.cs new file mode 100644 index 0000000000..5889737745 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo/Abp/BlazoriseUI/BlazoriseCrudPageBase.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazorise; +using Blazorise.DataGrid; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Components.WebAssembly; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.BlazoriseUI +{ + public abstract class BlazoriseCrudPageBase + : BlazoriseCrudPageBase + where TAppService : ICrudAppService + where TEntityDto : IEntityDto, new() + { + + } + + public abstract class BlazoriseCrudPageBase + : BlazoriseCrudPageBase + where TAppService : ICrudAppService + where TEntityDto : IEntityDto, new() + where TGetListInput : new() + { + + } + + public abstract class BlazoriseCrudPageBase + : BlazoriseCrudPageBase + where TAppService : ICrudAppService + where TEntityDto : IEntityDto + where TCreateInput : new() + where TGetListInput : new() + { + + } + + public abstract class BlazoriseCrudPageBase + : BlazoriseCrudPageBase + where TAppService : ICrudAppService + where TEntityDto : IEntityDto + where TCreateInput : new() + where TUpdateInput : new() + where TGetListInput : new() + { + + } + + public abstract class BlazoriseCrudPageBase + : OwningComponentBase + where TAppService : ICrudAppService + where TGetOutputDto : IEntityDto + where TGetListOutputDto : IEntityDto + where TCreateInput : new() + where TUpdateInput : new() + where TGetListInput : new() + { + [Inject] protected TAppService AppService { get; set; } + [Inject] protected IUiMessageService UiMessageService { get; set; } + [Inject] protected IStringLocalizer UiLocalizer { get; set; } + + protected virtual int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount; + + protected int CurrentPage; + protected string CurrentSorting; + protected int? TotalCount; + protected IReadOnlyList Entities = Array.Empty(); + protected TCreateInput NewEntity; + protected TKey EditingEntityId; + protected TUpdateInput EditingEntity; + protected Modal CreateModal; + protected Modal EditModal; + + protected Type ObjectMapperContext { get; set; } + + protected IObjectMapper ObjectMapper + { + get + { + if (_objectMapper != null) + { + return _objectMapper; + } + + if (ObjectMapperContext == null) + { + return LazyGetRequiredService(ref _objectMapper); + } + + return LazyGetRequiredService( + typeof(IObjectMapper<>).MakeGenericType(ObjectMapperContext), + ref _objectMapper + ); + } + } + + private IObjectMapper _objectMapper; + + protected TService LazyGetRequiredService(ref TService reference) + => LazyGetRequiredService(typeof(TService), ref reference); + + protected TRef LazyGetRequiredService(Type serviceType, ref TRef reference) + { + if (reference == null) + { + reference = (TRef) ScopedServices.GetRequiredService(serviceType); + } + + return reference; + } + + protected BlazoriseCrudPageBase() + { + NewEntity = new TCreateInput(); + EditingEntity = new TUpdateInput(); + } + + protected override async Task OnInitializedAsync() + { + await GetEntitiesAsync(); + } + + protected virtual async Task GetEntitiesAsync() + { + var input = await CreateGetListInputAsync(); + var result = await AppService.GetListAsync(input); + Entities = result.Items; + TotalCount = (int?) result.TotalCount; + } + + protected virtual Task CreateGetListInputAsync() + { + var input = new TGetListInput(); + + if (input is ISortedResultRequest sortedResultRequestInput) + { + sortedResultRequestInput.Sorting = CurrentSorting; + } + + if (input is IPagedResultRequest pagedResultRequestInput) + { + pagedResultRequestInput.SkipCount = CurrentPage * PageSize; + } + + if (input is ILimitedResultRequest limitedResultRequestInput) + { + limitedResultRequestInput.MaxResultCount = PageSize; + } + + return Task.FromResult(input); + } + + protected virtual async Task OnDataGridReadAsync(DataGridReadDataEventArgs e) + { + CurrentSorting = e.Columns + .Where(c => c.Direction != SortDirection.None) + .Select(c => c.Field + (c.Direction == SortDirection.Descending ? " DESC" : "")) + .JoinAsString(","); + CurrentPage = e.Page - 1; + + await GetEntitiesAsync(); + + StateHasChanged(); + } + + protected virtual Task OpenCreateModalAsync() + { + NewEntity = new TCreateInput(); + CreateModal.Show(); + return Task.CompletedTask; + } + + protected virtual Task CloseCreateModalAsync() + { + CreateModal.Hide(); + return Task.CompletedTask; + } + + protected virtual async Task OpenEditModalAsync(TKey id) + { + var entityDto = await AppService.GetAsync(id); + EditingEntityId = id; + EditingEntity = MapToEditingEntity(entityDto); + EditModal.Show(); + } + + protected virtual TUpdateInput MapToEditingEntity(TGetOutputDto entityDto) + { + return ObjectMapper.Map(entityDto); + } + + protected virtual Task CloseEditModalAsync() + { + EditModal.Hide(); + return Task.CompletedTask; + } + + protected virtual async Task CreateEntityAsync() + { + await AppService.CreateAsync(NewEntity); + await GetEntitiesAsync(); + CreateModal.Hide(); + } + + protected virtual async Task UpdateEntityAsync() + { + await AppService.UpdateAsync(EditingEntityId, EditingEntity); + await GetEntitiesAsync(); + EditModal.Hide(); + } + + protected virtual async Task DeleteEntityAsync(TGetListOutputDto entity) + { + if (!await UiMessageService.ConfirmAsync(GetDeleteConfirmationMessage(entity))) + { + return; + } + + await AppService.DeleteAsync(entity.Id); + await GetEntitiesAsync(); + } + + protected virtual string GetDeleteConfirmationMessage(TGetListOutputDto entity) + { + return UiLocalizer["ItemWillBeDeletedMessage"]; + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplicationWithExternalServiceProvider.cs b/framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplicationWithExternalServiceProvider.cs index 1c522a438a..8968fdf918 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplicationWithExternalServiceProvider.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/IAbpApplicationWithExternalServiceProvider.cs @@ -7,4 +7,4 @@ namespace Volo.Abp { void Initialize([NotNull] IServiceProvider serviceProvider); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs index 45f02e08c8..46b4174bef 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs @@ -1,6 +1,4 @@ -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Modularity +namespace Volo.Abp.Modularity { public interface IAbpModule { diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs index 10f88db22a..75cbfb8e96 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs @@ -9,7 +9,7 @@ namespace Volo.Abp.Application.Services } public interface ICrudAppService - : ICrudAppService + : ICrudAppService { } diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs index db66881fb7..bf2be8e8f0 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs @@ -21,7 +21,7 @@ namespace Volo.Abp.Application.Services } public abstract class CrudAppService - : CrudAppService + : CrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/FodyWeavers.xml b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/FodyWeavers.xml new file mode 100644 index 0000000000..bc5a74a236 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/FodyWeavers.xsd b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj new file mode 100644 index 0000000000..36838f5149 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.1 + Volo.Abp.Http.Client.IdentityModel.WebAssembly + Volo.Abp.Http.Client.IdentityModel.WebAssembly + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AbpHttpClientIdentityModelWebAssemblyModule.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AbpHttpClientIdentityModelWebAssemblyModule.cs new file mode 100644 index 0000000000..3110c36e26 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AbpHttpClientIdentityModelWebAssemblyModule.cs @@ -0,0 +1,13 @@ +using System; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Http.Client.IdentityModel.WebAssembly +{ + [DependsOn( + typeof(AbpHttpClientIdentityModelModule) + )] + public class AbpHttpClientIdentityModelWebAssemblyModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator.cs new file mode 100644 index 0000000000..c86fc095c7 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using IdentityModel.Client; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.Authentication; +using Volo.Abp.IdentityModel; + +namespace Volo.Abp.Http.Client.IdentityModel.WebAssembly +{ + [Dependency(ReplaceServices = true)] + public class AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator : IdentityModelRemoteServiceHttpClientAuthenticator + { + protected IAccessTokenProvider AccessTokenProvider { get; } + + public AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator( + IIdentityModelAuthenticationService identityModelAuthenticationService, + IAccessTokenProvider accessTokenProvider) + : base(identityModelAuthenticationService) + { + AccessTokenProvider = accessTokenProvider; + } + + public override async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context) + { + if (context.RemoteService.GetUseCurrentAccessToken() != false) + { + var accessToken = await GetAccessTokenFromAccessTokenProviderOrNullAsync(); + if (accessToken != null) + { + context.Request.SetBearerToken(accessToken); + return; + } + } + + await base.Authenticate(context); + } + + protected virtual async Task GetAccessTokenFromAccessTokenProviderOrNullAsync() + { + var result = await AccessTokenProvider.RequestAccessToken(); + if (result.Status != AccessTokenResultStatus.Success) + { + return null; + } + + result.TryGetToken(out var token); + return token.Value; + } + } +} diff --git a/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs b/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs index e20b60bf54..4d79779dd1 100644 --- a/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs +++ b/framework/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Reflection; using Castle.DynamicProxy; using JetBrains.Annotations; -using Polly; using Volo.Abp; using Volo.Abp.Castle.DynamicProxy; using Volo.Abp.Http.Client; @@ -30,23 +29,15 @@ namespace Microsoft.Extensions.DependencyInjection /// /// True, to register the HTTP client proxy as the default implementation for the services. /// - /// - /// A delegate that is used to configure an . - /// public static IServiceCollection AddHttpClientProxies( [NotNull] this IServiceCollection services, [NotNull] Assembly assembly, [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName, - bool asDefaultServices = true, - Action configureHttpClientBuilder = null) + bool asDefaultServices = true) { Check.NotNull(services, nameof(assembly)); - AddHttpClientFactoryAndPolicy(services, remoteServiceConfigurationName, configureHttpClientBuilder); - - //TODO: Make a configuration option and add remoteServiceName inside it! - - var serviceTypes = assembly.GetTypes().Where(IsSuitableForDynamicClientProxying); + var serviceTypes = assembly.GetTypes().Where(IsSuitableForDynamicClientProxying).ToArray(); foreach (var serviceType in serviceTypes) { @@ -72,17 +63,11 @@ namespace Microsoft.Extensions.DependencyInjection /// /// True, to register the HTTP client proxy as the default implementation for the service . /// - /// - /// A delegate that is used to configure an . - /// public static IServiceCollection AddHttpClientProxy( [NotNull] this IServiceCollection services, [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName, - bool asDefaultService = true, - Action configureHttpClientBuilder = null) + bool asDefaultService = true) { - AddHttpClientFactoryAndPolicy(services, remoteServiceConfigurationName, configureHttpClientBuilder); - return services.AddHttpClientProxy( typeof(T), remoteServiceConfigurationName, @@ -90,37 +75,6 @@ namespace Microsoft.Extensions.DependencyInjection ); } - /// - /// Use IHttpClientFactory and polly - /// - /// Service collection - /// - /// The name of the remote service configuration to be used by the HTTP Client proxies. - /// See . - /// - /// - /// A delegate that is used to configure an . - /// - public static IServiceCollection AddHttpClientFactoryAndPolicy( - [NotNull] this IServiceCollection services, - [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName, - Action configureHttpClientBuilder = null) - { - var httpClientBuilder = services.AddHttpClient(remoteServiceConfigurationName); - if (configureHttpClientBuilder == null) - { - httpClientBuilder.AddTransientHttpErrorPolicy(builder => - // retry 3 times - builder.WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))); - } - else - { - configureHttpClientBuilder.Invoke(httpClientBuilder); - } - - return services; - } - /// /// Registers HTTP Client Proxy for given service . /// @@ -143,6 +97,8 @@ namespace Microsoft.Extensions.DependencyInjection Check.NotNull(type, nameof(type)); Check.NotNullOrWhiteSpace(remoteServiceConfigurationName, nameof(remoteServiceConfigurationName)); + AddHttpClientFactoryAndPolicy(services, remoteServiceConfigurationName); + services.Configure(options => { options.HttpClientProxies[type] = new DynamicHttpClientProxyConfig(type, remoteServiceConfigurationName); @@ -189,13 +145,36 @@ namespace Microsoft.Extensions.DependencyInjection return services; } + private static IServiceCollection AddHttpClientFactoryAndPolicy( + [NotNull] this IServiceCollection services, + [NotNull] string remoteServiceConfigurationName = RemoteServiceConfigurationDictionary.DefaultName) + { + var preOptions = services.ExecutePreConfiguredActions(); + + if (preOptions.ConfiguredProxyClients.Contains(remoteServiceConfigurationName)) + { + return services; + } + + var clientBuilder = services.AddHttpClient(remoteServiceConfigurationName); + + foreach (var clientBuildAction in preOptions.ProxyClientBuildActions) + { + clientBuildAction(remoteServiceConfigurationName, clientBuilder); + } + + preOptions.ConfiguredProxyClients.Add(remoteServiceConfigurationName); + + return services; + } + /// /// Checks wether the type is suitable to use with the dynamic proxying. /// Currently the type is checked statically against some fixed conditions. /// /// Type to check /// True, if the type is suitable for dynamic proxying. Otherwise false. - static bool IsSuitableForDynamicClientProxying(Type type) + private static bool IsSuitableForDynamicClientProxying(Type type) { //TODO: Add option to change type filter diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs new file mode 100644 index 0000000000..7f11a6be12 --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientBuilderOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; + +namespace Volo.Abp.Http.Client +{ + public class AbpHttpClientBuilderOptions + { + public List> ProxyClientBuildActions { get; } + + internal HashSet ConfiguredProxyClients { get; } + + public AbpHttpClientBuilderOptions() + { + ProxyClientBuildActions = new List>(); + ConfiguredProxyClients = new HashSet(); + } + } +} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs index e2e5b7b9fe..e4e4f3e802 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs @@ -1,11 +1,11 @@ -using System.Linq; +using System; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Http; using Volo.Abp.Castle; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; using Volo.Abp.Validation; +using Polly; namespace Volo.Abp.Http.Client { @@ -18,29 +18,26 @@ namespace Volo.Abp.Http.Client )] public class AbpHttpClientModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) - { - var configuration = context.Services.GetConfiguration(); - Configure(configuration); - } - - public override void PostConfigureServices(ServiceConfigurationContext context) + public override void PreConfigureServices(ServiceConfigurationContext context) { - Configure(options => + PreConfigure(options => { - if (options.HttpClientActions.Any()) + options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => { - var httpClientNames = options.HttpClientProxies.Select(x => x.Value.RemoteServiceName); - foreach (var httpClientName in httpClientNames) - { - foreach (var httpClientAction in options.HttpClientActions) - { - context.Services.Configure(httpClientName, - x => x.HttpClientActions.Add(httpClientAction.Invoke(httpClientName))); - } - } - } + clientBuilder.AddTransientHttpErrorPolicy(policyBuilder => + policyBuilder.WaitAndRetryAsync( + 3, + i => TimeSpan.FromSeconds(Math.Pow(2, i)) + ) + ); + }); }); } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + Configure(configuration); + } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs index 54cc4eb624..2e6cd3798d 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Net.Http; using Volo.Abp.Http.Client.DynamicProxying; namespace Volo.Abp.Http.Client @@ -9,12 +8,9 @@ namespace Volo.Abp.Http.Client { public Dictionary HttpClientProxies { get; set; } - public List>> HttpClientActions { get; } - public AbpHttpClientOptions() { HttpClientProxies = new Dictionary(); - HttpClientActions = new List>>(); } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index af9eb59b7f..5313457ba7 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -25,7 +25,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying public class DynamicHttpProxyInterceptor : AbpInterceptor, ITransientDependency { // ReSharper disable once StaticMemberInGenericType - protected static MethodInfo GenericInterceptAsyncMethod { get; } + protected static MethodInfo MakeRequestAndGetResultAsyncMethod { get; } protected ICancellationTokenProvider CancellationTokenProvider { get; } protected ICorrelationIdProvider CorrelationIdProvider { get; } @@ -42,7 +42,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying static DynamicHttpProxyInterceptor() { - GenericInterceptAsyncMethod = typeof(DynamicHttpProxyInterceptor) + MakeRequestAndGetResultAsyncMethod = typeof(DynamicHttpProxyInterceptor) .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) .First(m => m.Name == nameof(MakeRequestAndGetResultAsync) && m.IsGenericMethodDefinition); } @@ -81,7 +81,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying } else { - var result = (Task)GenericInterceptAsyncMethod + var result = (Task)MakeRequestAndGetResultAsyncMethod .MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0]) .Invoke(this, new object[] { invocation }); @@ -90,7 +90,6 @@ namespace Volo.Abp.Http.Client.DynamicProxying invocation.Method.ReturnType.GetGenericArguments()[0] ); } - } private async Task GetResultAsync(Task task, Type resultType) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/CurrentPrincipalAccessorBase.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/CurrentPrincipalAccessorBase.cs new file mode 100644 index 0000000000..0ebedc8d44 --- /dev/null +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/CurrentPrincipalAccessorBase.cs @@ -0,0 +1,30 @@ +using System; +using System.Security.Claims; +using System.Threading; + +namespace Volo.Abp.Security.Claims +{ + public abstract class CurrentPrincipalAccessorBase : ICurrentPrincipalAccessor + { + public ClaimsPrincipal Principal => _currentPrincipal.Value ?? GetClaimsPrincipal(); + + private readonly AsyncLocal _currentPrincipal = new AsyncLocal(); + + protected abstract ClaimsPrincipal GetClaimsPrincipal(); + + public virtual IDisposable Change(ClaimsPrincipal principal) + { + return SetCurrent(principal); + } + + private IDisposable SetCurrent(ClaimsPrincipal principal) + { + var parent = Principal; + _currentPrincipal.Value = principal; + return new DisposeAction(() => + { + _currentPrincipal.Value = parent; + }); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ThreadCurrentPrincipalAccessor.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ThreadCurrentPrincipalAccessor.cs index ad496ba916..ea7ed7106f 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ThreadCurrentPrincipalAccessor.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/ThreadCurrentPrincipalAccessor.cs @@ -1,34 +1,14 @@ -using System; -using System.Security.Claims; +using System.Security.Claims; using System.Threading; using Volo.Abp.DependencyInjection; namespace Volo.Abp.Security.Claims { - public class ThreadCurrentPrincipalAccessor : ICurrentPrincipalAccessor, ISingletonDependency + public class ThreadCurrentPrincipalAccessor : CurrentPrincipalAccessorBase, ISingletonDependency { - public ClaimsPrincipal Principal => _currentPrincipal.Value ?? GetClaimsPrincipal(); - - private readonly AsyncLocal _currentPrincipal = new AsyncLocal(); - - protected virtual ClaimsPrincipal GetClaimsPrincipal() + protected override ClaimsPrincipal GetClaimsPrincipal() { return Thread.CurrentPrincipal as ClaimsPrincipal; } - - public virtual IDisposable Change(ClaimsPrincipal principal) - { - return SetCurrent(principal); - } - - private IDisposable SetCurrent(ClaimsPrincipal principal) - { - var parent = Principal; - _currentPrincipal.Value = principal; - return new DisposeAction(() => - { - _currentPrincipal.Value = parent; - }); - } } } diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs index ffa31e66a2..912621c424 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Urls/AppUrlProvider.cs @@ -87,13 +87,16 @@ namespace Volo.Abp.UI.Navigation.Urls tenantNamePlaceHolder = TenantNamePlaceHolder + '.'; } - if (CurrentTenant.Id.HasValue) + if (url.Contains(tenantNamePlaceHolder)) { - url = url.Replace(tenantNamePlaceHolder, await GetCurrentTenantNameAsync()); - } - else - { - url = url.Replace(tenantNamePlaceHolder, ""); + if (CurrentTenant.Id.HasValue) + { + url = url.Replace(tenantNamePlaceHolder, await GetCurrentTenantNameAsync()); + } + else + { + url = url.Replace(tenantNamePlaceHolder, ""); + } } return url; @@ -110,4 +113,4 @@ namespace Volo.Abp.UI.Navigation.Urls return CurrentTenant.Name; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json index d6f24833a9..e0cdee07af 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json @@ -62,6 +62,8 @@ "500Message": "Internal Server Error", "GoHomePage": "Go to the homepage", "GoBack": "Go back", - "Search": "Search" + "Search": "Search", + "ItemWillBeDeletedMessageWithFormat": "{0} will be deleted!", + "ItemWillBeDeletedMessage": "This item will be deleted!" } } diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json index f4fffd3f7f..db803e01e2 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json @@ -62,6 +62,8 @@ "500Message": "Sunucu tarafında hata", "GoHomePage": "Ana sayfaya git", "GoBack": "Geri dön", - "Search": "Arama" + "Search": "Arama", + "ItemWillBeDeletedMessageWithFormat": "{0} silinecektir!", + "ItemWillBeDeletedMessage": "Bu nesne silinecektir!" } } diff --git a/framework/src/Volo.Abp.UI/Volo/Abp/Ui/Branding/DefaultBrandingProvider.cs b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/Branding/DefaultBrandingProvider.cs new file mode 100644 index 0000000000..d6a09e1c19 --- /dev/null +++ b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/Branding/DefaultBrandingProvider.cs @@ -0,0 +1,13 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Ui.Branding +{ + public class DefaultBrandingProvider : IBrandingProvider, ITransientDependency + { + public virtual string AppName => "MyApplication"; + + public virtual string LogoUrl => null; + + public virtual string LogoReverseUrl => null; + } +} diff --git a/framework/src/Volo.Abp.UI/Volo/Abp/Ui/Branding/IBrandingProvider.cs b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/Branding/IBrandingProvider.cs new file mode 100644 index 0000000000..93e7fd957b --- /dev/null +++ b/framework/src/Volo.Abp.UI/Volo/Abp/Ui/Branding/IBrandingProvider.cs @@ -0,0 +1,17 @@ +namespace Volo.Abp.Ui.Branding +{ + public interface IBrandingProvider + { + string AppName { get; } + + /// + /// Logo on white background + /// + string LogoUrl { get; } + + /// + /// Logo on dark background + /// + string LogoReverseUrl { get; } + } +} diff --git a/modules/feature-management/Volo.Abp.FeatureManagement.sln b/modules/feature-management/Volo.Abp.FeatureManagement.sln index 34f0c84aa5..7f1c6ae1e0 100644 --- a/modules/feature-management/Volo.Abp.FeatureManagement.sln +++ b/modules/feature-management/Volo.Abp.FeatureManagement.sln @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.FeatureManagement. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.FeatureManagement.Application.Tests", "test\Volo.Abp.FeatureManagement.Application.Tests\Volo.Abp.FeatureManagement.Application.Tests.csproj", "{13A9EAD6-F3A4-4357-BA4A-A7E8FEB4A264}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.FeatureManagement.Blazor", "src\Volo.Abp.FeatureManagement.Blazor\Volo.Abp.FeatureManagement.Blazor.csproj", "{0F34FFD5-E98F-4F77-AE0B-A790BD5810D5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -97,6 +99,10 @@ Global {13A9EAD6-F3A4-4357-BA4A-A7E8FEB4A264}.Debug|Any CPU.Build.0 = Debug|Any CPU {13A9EAD6-F3A4-4357-BA4A-A7E8FEB4A264}.Release|Any CPU.ActiveCfg = Release|Any CPU {13A9EAD6-F3A4-4357-BA4A-A7E8FEB4A264}.Release|Any CPU.Build.0 = Release|Any CPU + {0F34FFD5-E98F-4F77-AE0B-A790BD5810D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F34FFD5-E98F-4F77-AE0B-A790BD5810D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F34FFD5-E98F-4F77-AE0B-A790BD5810D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F34FFD5-E98F-4F77-AE0B-A790BD5810D5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -116,6 +122,7 @@ Global {E5906DE1-B2F5-472E-BE1B-1D96A68B834D} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} {AA783A34-86E4-41A5-AE21-5D9FBD98D858} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} {13A9EAD6-F3A4-4357-BA4A-A7E8FEB4A264} = {CCD2960C-23CC-4AB4-B84D-60C7AAA52F4D} + {0F34FFD5-E98F-4F77-AE0B-A790BD5810D5} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/AbpFeatureManagementBlazorModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/AbpFeatureManagementBlazorModule.cs new file mode 100644 index 0000000000..497d7553da --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/AbpFeatureManagementBlazorModule.cs @@ -0,0 +1,16 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; + +namespace Volo.Abp.FeatureManagement.Blazor +{ + [DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingModule), + typeof(AbpAutoMapperModule), + typeof(AbpFeatureManagementHttpApiClientModule) + )] + public class AbpFeatureManagementBlazorModule : AbpModule + { + + } +} \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor new file mode 100644 index 0000000000..f59eea6d57 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor @@ -0,0 +1,85 @@ +@using Microsoft.Extensions.Localization +@using Volo.Abp.FeatureManagement.Localization +@using Volo.Abp.Validation.StringValues +@inject IStringLocalizer L + + + + + + @L["Features"] + + + + @if (_groups == null) + { + @L["NoFeatureFoundMessage"] + } + else + { + + + @foreach (var group in _groups) + { + + @group.DisplayName + + } + + + @foreach (var group in _groups) + { + +

@group.DisplayName

+ + @foreach (var feature in group.Features) + { + var disabled = IsDisabled(feature.Provider.Name); + + if (feature.ValueType is FreeTextStringValueType) + { + + @feature.DisplayName + + @if (feature.Description != null) + { + @feature.Description + } + + } + + if (feature.ValueType is SelectionStringValueType) + { + var items = ((SelectionStringValueType) feature.ValueType).ItemSource.Items; + + + @feature.DisplayName + + + } + + if (feature.ValueType is ToggleStringValueType) + { + + @feature.DisplayName + + } + } + +
+ } +
+
+ } +
+ + + + +
+
\ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs new file mode 100644 index 0000000000..40aba610eb --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazorise; +using Microsoft.AspNetCore.Components; +using Volo.Abp.Features; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.FeatureManagement.Blazor.Components +{ + public partial class FeatureManagementModal + { + [Inject] private IFeatureAppService FeatureAppService { get; set; } + + private Modal _modal; + + private string _providerName; + private string _providerKey; + + private List _groups { get; set; } + + private Dictionary ToggleValues; + + public async Task OpenAsync(string providerName, string providerKey) + { + _providerName = providerName; + _providerKey = providerKey; + + _groups = (await FeatureAppService.GetAsync(_providerName, _providerKey)).Groups; + + ToggleValues = _groups + .SelectMany(x => x.Features) + .Where(x => x.ValueType is ToggleStringValueType) + .ToDictionary(x => x.Name, x => bool.Parse(x.Value)); + + _modal.Show(); + } + + private void CloseModal() + { + _modal.Hide(); + } + + private async Task SaveAsync() + { + var features = new UpdateFeaturesDto + { + Features = _groups.SelectMany(g => g.Features).Select(f => new UpdateFeatureDto + { + Name = f.Name, + Value = f.ValueType is ToggleStringValueType ? ToggleValues[f.Name].ToString() : f.Value + }).ToList() + }; + + await FeatureAppService.UpdateAsync(_providerName, _providerKey, features); + + _modal.Hide(); + } + + public string GetNormalizedGroupName(string name) + { + return "FeatureGroup_" + name.Replace(".", "_"); + } + + public virtual bool IsDisabled(string providerName) + { + return providerName != _providerName && providerName != DefaultValueFeatureValueProvider.ProviderName; + } + } +} \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/FodyWeavers.xml b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/FodyWeavers.xsd b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Volo.Abp.FeatureManagement.Blazor.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Volo.Abp.FeatureManagement.Blazor.csproj new file mode 100644 index 0000000000..8769a86e79 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Volo.Abp.FeatureManagement.Blazor.csproj @@ -0,0 +1,29 @@ + + + + + + + netstandard2.1 + 3.0 + + + + + + + + + + + + + + true + + + true + + + + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/_Imports.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/_Imports.razor new file mode 100644 index 0000000000..b52766daad --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/_Imports.razor @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.WebAssembly +@using Volo.Abp.BlazoriseUI +@using Blazorise +@using Blazorise.DataGrid \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml index 5e6e3a0c54..deb24accc8 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml @@ -1,13 +1,23 @@ -@page +@page @using Microsoft.AspNetCore.Mvc.Localization +@using Microsoft.Extensions.Options @using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal @using Volo.Abp.FeatureManagement.Localization @using Volo.Abp.Validation.StringValues @using Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement +@using Volo.Abp.Localization @model FeatureManagementModal @inject IHtmlLocalizer L +@inject IHtmlLocalizerFactory HtmlLocalizerFactory +@inject IOptions LocalizationOptions @{ Layout = null; + + IHtmlLocalizer CreateHtmlLocalizer(string resourceName) + { + var resource = LocalizationOptions.Value.Resources.Values.FirstOrDefault(x => x.ResourceName == resourceName); + return HtmlLocalizerFactory.Create(resource != null ? resource.ResourceType : LocalizationOptions.Value.DefaultResourceType); + } }
@@ -48,11 +58,11 @@ { if (item.Value == feature.Value) { - + } else { - + } } diff --git a/modules/identity/.gitignore b/modules/identity/.gitignore new file mode 100644 index 0000000000..dcd876a3c7 --- /dev/null +++ b/modules/identity/.gitignore @@ -0,0 +1,4 @@ +src/Volo.Abp.Identity.HttpApi/Properties/launchSettings.json +src/Volo.Abp.Identity.Web/Properties/launchSettings.json +test/Volo.Abp.Identity.AspNetCore.Tests/Properties/launchSettings.json +src/Volo.Abp.Identity.AspNetCore/Properties/launchSettings.json diff --git a/modules/identity/Volo.Abp.Identity.sln b/modules/identity/Volo.Abp.Identity.sln index 388cd0c555..23336bdc02 100644 --- a/modules/identity/Volo.Abp.Identity.sln +++ b/modules/identity/Volo.Abp.Identity.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27428.1 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30413.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AADC5A0A-F100-4511-87DE-B74E55F5B69B}" EndProject @@ -37,9 +37,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Domain.Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.AspNetCore", "src\Volo.Abp.Identity.AspNetCore\Volo.Abp.Identity.AspNetCore.csproj", "{D5EFC912-75A0-4856-9B8D-DFDD4CD66BAB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.PermissionManagement.Domain.Identity", "src\Volo.Abp.PermissionManagement.Domain.Identity\Volo.Abp.PermissionManagement.Domain.Identity.csproj", "{736F91E7-8A70-441B-89DE-0E29A348E718}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.PermissionManagement.Domain.Identity", "src\Volo.Abp.PermissionManagement.Domain.Identity\Volo.Abp.PermissionManagement.Domain.Identity.csproj", "{736F91E7-8A70-441B-89DE-0E29A348E718}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Identity.AspNetCore.Tests", "test\Volo.Abp.Identity.AspNetCore.Tests\Volo.Abp.Identity.AspNetCore.Tests.csproj", "{89C094EB-D80A-4976-9C10-7CE3EBEEE877}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.AspNetCore.Tests", "test\Volo.Abp.Identity.AspNetCore.Tests\Volo.Abp.Identity.AspNetCore.Tests.csproj", "{89C094EB-D80A-4976-9C10-7CE3EBEEE877}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Identity.Blazor", "src\Volo.Abp.Identity.Blazor\Volo.Abp.Identity.Blazor.csproj", "{3F7BB653-3F3A-4889-B73C-E463F239099A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -115,6 +117,10 @@ Global {89C094EB-D80A-4976-9C10-7CE3EBEEE877}.Debug|Any CPU.Build.0 = Debug|Any CPU {89C094EB-D80A-4976-9C10-7CE3EBEEE877}.Release|Any CPU.ActiveCfg = Release|Any CPU {89C094EB-D80A-4976-9C10-7CE3EBEEE877}.Release|Any CPU.Build.0 = Release|Any CPU + {3F7BB653-3F3A-4889-B73C-E463F239099A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F7BB653-3F3A-4889-B73C-E463F239099A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F7BB653-3F3A-4889-B73C-E463F239099A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F7BB653-3F3A-4889-B73C-E463F239099A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -137,6 +143,7 @@ Global {D5EFC912-75A0-4856-9B8D-DFDD4CD66BAB} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} {736F91E7-8A70-441B-89DE-0E29A348E718} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} {89C094EB-D80A-4976-9C10-7CE3EBEEE877} = {9FACAF96-A681-4B36-A938-A37DCA0B7EC1} + {3F7BB653-3F3A-4889-B73C-E463F239099A} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {05740D37-83CF-4041-9C2A-D89F1B3DB5A4} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs index 36f483d0e6..24969c1ed5 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs @@ -5,18 +5,14 @@ using Volo.Abp.Application.Services; namespace Volo.Abp.Identity { - public interface IIdentityRoleAppService : IApplicationService + public interface IIdentityRoleAppService + : ICrudAppService< + IdentityRoleDto, + Guid, + PagedAndSortedResultRequestDto, + IdentityRoleCreateDto, + IdentityRoleUpdateDto> { Task> GetAllListAsync(); - - Task> GetListAsync(PagedAndSortedResultRequestDto input); - - Task CreateAsync(IdentityRoleCreateDto input); - - Task GetAsync(Guid id); - - Task UpdateAsync(Guid id, IdentityRoleUpdateDto input); - - Task DeleteAsync(Guid id); } } diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs index a184226117..97f443f3b6 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs @@ -5,7 +5,13 @@ using Volo.Abp.Application.Services; namespace Volo.Abp.Identity { - public interface IIdentityUserAppService : ICrudAppService + public interface IIdentityUserAppService + : ICrudAppService< + IdentityUserDto, + Guid, + GetIdentityUsersInput, + IdentityUserCreateDto, + IdentityUserUpdateDto> { Task> GetRolesAsync(Guid id); diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs new file mode 100644 index 0000000000..bbe57e91fd --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorAutoMapperProfile.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using Volo.Abp.AutoMapper; + +namespace Volo.Abp.Identity.Blazor +{ + public class AbpIdentityBlazorAutoMapperProfile : Profile + { + public AbpIdentityBlazorAutoMapperProfile() + { + CreateMap() + .MapExtraProperties() + .Ignore(x => x.Password) + .Ignore(x => x.RoleNames); + + CreateMap() + .MapExtraProperties(); + + CreateMap() + .Ignore(x => x.Password) + .Ignore(x => x.RoleNames) + .MapExtraProperties(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs new file mode 100644 index 0000000000..cebde068c3 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.Blazor; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Identity.Blazor +{ + [DependsOn( + typeof(AbpIdentityHttpApiClientModule), + typeof(AbpAutoMapperModule), + typeof(AbpPermissionManagementBlazorModule) + )] + public class AbpIdentityBlazorModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAutoMapperObjectMapper(); + + Configure(options => + { + options.AddProfile(validate: true); + }); + + Configure(options => + { + options.MenuContributors.Add(new AbpIdentityWebMainMenuContributor()); + }); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpIdentityBlazorModule).Assembly); + }); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityWebMainMenuContributor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityWebMainMenuContributor.cs new file mode 100644 index 0000000000..17c956f0c7 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityWebMainMenuContributor.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using Volo.Abp.Identity.Localization; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Identity.Blazor +{ + public class AbpIdentityWebMainMenuContributor : IMenuContributor + { + public virtual async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.Main) + { + return; + } + + var hasRolePermission = await context.IsGrantedAsync(IdentityPermissions.Roles.Default); + var hasUserPermission = await context.IsGrantedAsync(IdentityPermissions.Users.Default); + + if (hasRolePermission || hasUserPermission) + { + var administrationMenu = context.Menu.GetAdministration(); + + var l = context.GetLocalizer(); + + var identityMenuItem = new ApplicationMenuItem(IdentityMenuNames.GroupName, l["Menu:IdentityManagement"], icon: "fa fa-id-card-o"); + administrationMenu.AddItem(identityMenuItem); + + if (hasRolePermission) + { + identityMenuItem.AddItem(new ApplicationMenuItem(IdentityMenuNames.Roles, l["Roles"], url: "/identity/roles")); + } + + if (hasUserPermission) + { + identityMenuItem.AddItem(new ApplicationMenuItem(IdentityMenuNames.Users, l["Users"], url: "/identity/users")); + } + } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/FodyWeavers.xml b/modules/identity/src/Volo.Abp.Identity.Blazor/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/FodyWeavers.xsd b/modules/identity/src/Volo.Abp.Identity.Blazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/IdentityMenuNames.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/IdentityMenuNames.cs new file mode 100644 index 0000000000..9cd5b1dc37 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/IdentityMenuNames.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.Identity.Blazor +{ + public class IdentityMenuNames + { + public const string GroupName = "AbpIdentity"; + + public const string Roles = GroupName + ".Roles"; + public const string Users = GroupName + ".Users"; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor new file mode 100644 index 0000000000..c0298aabcc --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -0,0 +1,112 @@ +@page "/identity/roles" +@attribute [Authorize] +@using Volo.Abp.Identity +@using Microsoft.AspNetCore.Authorization +@using Microsoft.Extensions.Localization +@using Volo.Abp.Identity.Localization +@using Volo.Abp.PermissionManagement.Blazor.Components +@inherits RoleManagementBase +@inject IStringLocalizer L +@* ************************* PAGE HEADER ************************* *@ + + +

@L["Roles"]

+
+ + + + + +
+ +@* ************************* DATA GRID ************************* *@ + + + + + + + @L["Actions"] + + + @L["Edit"] + @L["Permissions"] + + @L["Delete"] + + + + + + + @(context.As().Name) + @if (context.As().IsDefault) + { + @L["DisplayName:IsDefault"] + } + @if (context.As().IsPublic) + { + @L["DisplayName:IsPublic"] + } + + + + + +@* ************************* CREATE MODAL ************************* *@ + + + + + @L["NewRole"] + + + + + @L["DisplayName:RoleName"] + + + + @L["DisplayName:IsDefault"] + @L["DisplayName:IsPublic"] + + + + + + + + + +@* ************************* EDIT MODAL ************************* *@ + + + + + Edit role + + + + + + @L["DisplayName:RoleName"] + + + + @L["DisplayName:IsDefault"] + @L["DisplayName:IsPublic"] + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs new file mode 100644 index 0000000000..ddbf9164b8 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs @@ -0,0 +1,19 @@ +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.BlazoriseUI; +using Volo.Abp.PermissionManagement.Blazor.Components; + +namespace Volo.Abp.Identity.Blazor.Pages.Identity +{ + public class RoleManagementBase : BlazoriseCrudPageBase + { + protected const string PermissionProviderName = "R"; + + protected PermissionManagementModal PermissionManagementModal; + + public RoleManagementBase() + { + ObjectMapperContext = typeof(AbpIdentityBlazorModule); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor new file mode 100644 index 0000000000..dacc975bb0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -0,0 +1,204 @@ +@page "/identity/users" +@attribute [Authorize] +@using Microsoft.AspNetCore.Authorization +@using Microsoft.Extensions.Localization +@using Volo.Abp.Application.Dtos +@using Volo.Abp.Identity.Localization +@using Volo.Abp.PermissionManagement.Blazor.Components +@inherits UserManagementBase +@inject IStringLocalizer L + +@* ************************* PAGE HEADER ************************* *@ + + +

@L["Users"]

+
+ + + + + +
+ +@* ************************* DATA GRID ************************* *@ + + + + + + + @L["Actions"] + + + @L["Edit"] + @L["Permissions"] + + @L["Delete"] + + + + + + + @(context.As().UserName) + + + + + @(context.As().Email) + + + + + @(context.As().PhoneNumber) + + + + + +@* ************************* CREATE MODAL ************************* *@ + + + + + @L["NewUser"] + + + + + + @L["UserInformations"] + @L["Roles"] + + + + + @L["DisplayName:UserName"] + + + + @L["DisplayName:Name"] + + + + @L["DisplayName:Surname"] + + + + @L["DisplayName:Password"] + + + + @L["DisplayName:Email"] + + + + @L["DisplayName:PhoneNumber"] + + + + @L["DisplayName:LockoutEnabled"] + + + @L["DisplayName:TwoFactorEnabled"] + + + + @if (NewUserRoles != null) + { + @foreach (var role in NewUserRoles) + { + + + @role.Name + + } + } + + + + + + + + + + + +@* ************************* EDIT MODAL ************************* *@ + + + + + role + + + + + + + + @L["UserInformations"] + @L["Roles"] + + + + + @L["DisplayName:UserName"] + + + + @L["DisplayName:Name"] + + + + @L["DisplayName:Surname"] + + + + @L["DisplayName:Password"] + + + + @L["DisplayName:Email"] + + + + @L["DisplayName:PhoneNumber"] + + + + @L["DisplayName:LockoutEnabled"] + + + @L["DisplayName:TwoFactorEnabled"] + + + + @if (EditUserRoles != null) + { + @foreach (var role in EditUserRoles) + { + + + @role.Name + + } + } + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs new file mode 100644 index 0000000000..e39c9fe567 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.BlazoriseUI; +using Volo.Abp.PermissionManagement.Blazor.Components; +using Volo.Abp.Threading; + +namespace Volo.Abp.Identity.Blazor.Pages.Identity +{ + public class UserManagementBase : BlazoriseCrudPageBase + { + protected const string PermissionProviderName = "U"; + + protected const string DefaultSelectedTab = "UserInformations"; + + protected PermissionManagementModal PermissionManagementModal; + + protected IReadOnlyList Roles; + + protected AssignedRoleViewModel[] NewUserRoles; + + protected AssignedRoleViewModel[] EditUserRoles; + + protected string _createModalSelectedTab = DefaultSelectedTab; + + protected string _editModalSelectedTab = DefaultSelectedTab; + + public UserManagementBase() + { + ObjectMapperContext = typeof(AbpIdentityBlazorModule); + } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + Roles = (await AppService.GetAssignableRolesAsync()).Items; + } + + protected override Task OpenCreateModalAsync() + { + _createModalSelectedTab = DefaultSelectedTab; + + NewUserRoles = Roles.Select(x => new AssignedRoleViewModel + { + Name = x.Name, + IsAssigned = x.IsDefault + }).ToArray(); + + return base.OpenCreateModalAsync(); + } + + protected override Task CreateEntityAsync() + { + NewEntity.RoleNames = NewUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); + + return base.CreateEntityAsync(); + } + + protected override async Task OpenEditModalAsync(Guid id) + { + _editModalSelectedTab = DefaultSelectedTab; + + var userRoleNames = (await AppService.GetRolesAsync(id)).Items.Select(r => r.Name).ToList(); + + EditUserRoles = Roles.Select(x => new AssignedRoleViewModel + { + Name = x.Name, + IsAssigned = userRoleNames.Contains(x.Name) + }).ToArray(); + + await base.OpenEditModalAsync(id); + } + + protected override Task UpdateEntityAsync() + { + EditingEntity.RoleNames = EditUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); + + return base.UpdateEntityAsync(); + } + } + + public class AssignedRoleViewModel + { + public string Name { get; set; } + + public bool IsAssigned { get; set; } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj b/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj new file mode 100644 index 0000000000..19bfb3831c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Volo.Abp.Identity.Blazor.csproj @@ -0,0 +1,20 @@ + + + + + + + netstandard2.1 + 3.0 + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/_Imports.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/_Imports.razor new file mode 100644 index 0000000000..4685ac9893 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/_Imports.razor @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.WebAssembly +@using Volo.Abp.BlazoriseUI +@using Blazorise +@using Blazorise.DataGrid \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj index 06cef29fc3..e1df61cbce 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj @@ -23,6 +23,7 @@ + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs index 4bff33d58a..e14ce5e3f4 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Identity.Localization; +using Volo.Abp.Features; +using Volo.Abp.Identity.Localization; using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; @@ -11,7 +12,8 @@ namespace Volo.Abp.Identity { [DependsOn( typeof(AbpUsersDomainSharedModule), - typeof(AbpValidationModule) + typeof(AbpValidationModule), + typeof(AbpFeaturesModule) )] public class AbpIdentityDomainSharedModule : AbpModule { diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeature.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeature.cs new file mode 100644 index 0000000000..a51a3c11f7 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeature.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Identity.Features +{ + public class IdentityFeature + { + public const string GroupName = "Identity"; + + public const string TwoFactor = GroupName + ".TwoFactor"; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeatureCheckerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeatureCheckerExtensions.cs new file mode 100644 index 0000000000..988b381643 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeatureCheckerExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Features; + +namespace Volo.Abp.Identity.Features +{ + public static class IdentityFeatureCheckerExtensions + { + public static async Task GetIdentityTwoFactorBehaviour([NotNull] this IFeatureChecker featureChecker) + { + Check.NotNull(featureChecker, nameof(featureChecker)); + + var value = await featureChecker.GetOrNullAsync(IdentityFeature.TwoFactor); + if (value.IsNullOrWhiteSpace() || !Enum.TryParse(value, out var behaviour)) + { + throw new AbpException($"{IdentityFeature.TwoFactor} feature value is invalid"); + } + + return behaviour; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeatureDefinitionProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeatureDefinitionProvider.cs new file mode 100644 index 0000000000..d26cc17b92 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityFeatureDefinitionProvider.cs @@ -0,0 +1,51 @@ +using System; +using Volo.Abp.Features; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.Identity.Features +{ + public class IdentityFeatureDefinitionProvider : FeatureDefinitionProvider + { + public override void Define(IFeatureDefinitionContext context) + { + var group = context.AddGroup(IdentityFeature.GroupName, L("Feature:IdentityGroup")); + + group.AddFeature(IdentityFeature.TwoFactor, + IdentityTwoFactorBehaviour.Optional.ToString(), + L("Feature:TwoFactor"), + L("Feature:TwoFactorDescription"), + new SelectionStringValueType + { + ItemSource = new StaticSelectionStringValueItemSource( + new LocalizableSelectionStringValueItem + { + Value = IdentityTwoFactorBehaviour.Optional.ToString(), + DisplayText = GetTwoFactorBehaviourLocalizableStringInfo("Feature:TwoFactor.Optional") + }, + new LocalizableSelectionStringValueItem + { + Value = IdentityTwoFactorBehaviour.Disabled.ToString(), + DisplayText = GetTwoFactorBehaviourLocalizableStringInfo("Feature:TwoFactor.Disabled") + }, + new LocalizableSelectionStringValueItem + { + Value = IdentityTwoFactorBehaviour.Forced.ToString(), + DisplayText = GetTwoFactorBehaviourLocalizableStringInfo("Feature:TwoFactor.Forced") + } + ) + }); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + + private static LocalizableStringInfo GetTwoFactorBehaviourLocalizableStringInfo(string key) + { + return new LocalizableStringInfo(LocalizationResourceNameAttribute.GetName(typeof(IdentityResource)), key); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityTwoFactorBehaviour.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityTwoFactorBehaviour.cs new file mode 100644 index 0000000000..4e415a7cfb --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityTwoFactorBehaviour.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Identity.Features +{ + public enum IdentityTwoFactorBehaviour + { + Optional, + + Disabled, + + Forced + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index 9fe464ae96..166433c5eb 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -76,6 +76,12 @@ "Permission:ChangePermissions": "Change permissions", "Permission:UserManagement": "User management", "Permission:UserLookup": "User lookup", + "Feature:IdentityGroup": "Identity", + "Feature:TwoFactor": "Two Factor", + "Feature:TwoFactorDescription": "Two Factor", + "Feature:TwoFactor.Optional": "Optional", + "Feature:TwoFactor.Disabled": "Disabled", + "Feature:TwoFactor.Forced": "Forced", "DisplayName:Abp.Identity.Password.RequiredLength": "Required length", "DisplayName:Abp.Identity.Password.RequiredUniqueChars": "Required unique characters number", "DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "Required non-alphanumeric character", @@ -103,6 +109,10 @@ "Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Whether the phoneNumber can be confirmed by the user.", "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Whether a confirmed telephone number is required to sign in.", "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Whether the username can be updated by the user.", - "Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user." + "Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user.", + "DisplayName:Abp.Identity.TwoFactorBehaviour": "Two Factor behaviour", + "Description:Abp.Identity.TwoFactorBehaviour": "Two Factor behaviour", + "DisplayName:Abp.Identity.UsersCanChange": "Allow users to change their Two Factor.", + "Description:Abp.Identity.UsersCanChange": "Allow users to change their Two Factor." } -} +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json index 2ad19c80f4..97dc56b417 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json @@ -76,6 +76,12 @@ "Permission:ChangePermissions": "İzinleri değiştirme", "Permission:UserManagement": "Kullanıcı yönetimi", "Permission:UserLookup": "Kullanıcı sorgulama", + "Feature:IdentityGroup": "Kimlik", + "Feature:TwoFactor": "İki faktörlü kimlik doğrulama", + "Feature:TwoFactorDescription": "İki faktörlü kimlik doğrulama", + "Feature:TwoFactor.Optional": "İsteğe bağlı", + "Feature:TwoFactor.Disabled": "Devre dışı", + "Feature:TwoFactor.Forced": "Zorla etkinleştirildi", "DisplayName:Abp.Identity.Password.RequiredLength": "Uzunluk gerekli", "DisplayName:Abp.Identity.Password.RequiredUniqueChars": "Tekil karakter gerekli", "DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "Alfasayısal olmayan karakter gerekli", @@ -103,6 +109,10 @@ "Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Oturum açmak için telefon numarası gerekli", "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Oturum açmak için onaylanmış bir telefon numarasının gerekli olup olmadığı.", "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Kullanıcı adının, kullanıcının kendisi tarafından güncellenebilirliği.", - "Description:Abp.Identity.User.IsEmailUpdateEnabled": "E-posta alanının, kullanıcının kendisi tarafından güncellenebilirliği" + "Description:Abp.Identity.User.IsEmailUpdateEnabled": "E-posta alanının, kullanıcının kendisi tarafından güncellenebilirliği", + "DisplayName:Abp.Identity.TwoFactorBehaviour": "İki faktörlü kimlik doğrulama davranışı", + "Description:Abp.Identity.TwoFactorBehaviour": "İki faktörlü kimlik doğrulama davranışı", + "DisplayName:Abp.Identity.UsersCanChange": "Kullanıcıların faktör kimlik doğrulamasını değiştirmesine izin verin.", + "Description:Abp.Identity.UsersCanChange": "Kullanıcıların faktör kimlik doğrulamasını değiştirmesine izin verin." } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json index 5494581c6a..8e8c8cbccf 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json @@ -73,6 +73,12 @@ "Permission:ChangePermissions": "更改权限", "Permission:UserManagement": "用户管理", "Permission:UserLookup": "用户查询", + "Feature:IdentityGroup": "身份标识", + "Feature:TwoFactor": "双因素身份验证", + "Feature:TwoFactorDescription": "双因素身份验证", + "Feature:TwoFactor.Optional": "可选", + "Feature:TwoFactor.Disabled": "禁用", + "Feature:TwoFactor.Forced": "强制启用", "DisplayName:Abp.Identity.Password.RequiredLength": "要求长度", "DisplayName:Abp.Identity.Password.RequiredUniqueChars": "要求唯一字符数量", "DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "要求非字母数字", @@ -98,6 +104,10 @@ "Description:Abp.Identity.SignIn.RequireConfirmedEmail": "登录时是否需要验证的电子邮箱.", "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "登录时是否需要验证的手机号码.", "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "是否允许用户更新用户名.", - "Description:Abp.Identity.User.IsEmailUpdateEnabled": "是否允许用户更新电子邮箱." + "Description:Abp.Identity.User.IsEmailUpdateEnabled": "是否允许用户更新电子邮箱.", + "DisplayName:Abp.Identity.TwoFactorBehaviour": "双因素身份验证行为", + "Description:Abp.Identity.TwoFactorBehaviour": "双因素身份验证行为", + "DisplayName:Abp.Identity.UsersCanChange": "允许用户更改其因素身份验证.", + "Description:Abp.Identity.UsersCanChange": "允许用户更改其因素身份验证." } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs index eb7a88407f..879c3ffd4c 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs @@ -48,5 +48,14 @@ public const string MaxUserMembershipCount = OrganizationUnitPrefix + ".MaxUserMembershipCount"; } + + public static class TwoFactor + { + private const string TwoFactorPrefix = Prefix + ".TwoFactor"; + + public const string Behaviour = TwoFactorPrefix + ".Behaviour"; + + public const string UsersCanChange = TwoFactorPrefix + ".UsersCanChange"; + } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs index 1fdd2dd451..cff38810c9 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Identity.Localization; +using Volo.Abp.Identity.Features; +using Volo.Abp.Identity.Localization; using Volo.Abp.Identity.Settings; using Volo.Abp.Localization; using Volo.Abp.Settings; @@ -33,85 +34,97 @@ namespace Volo.Abp.Identity new SettingDefinition( IdentitySettingNames.Password.RequireLowercase, - true.ToString(), + true.ToString(), L("DisplayName:Abp.Identity.Password.RequireLowercase"), L("Description:Abp.Identity.Password.RequireLowercase"), true), new SettingDefinition( IdentitySettingNames.Password.RequireUppercase, - true.ToString(), + true.ToString(), L("DisplayName:Abp.Identity.Password.RequireUppercase"), L("Description:Abp.Identity.Password.RequireUppercase"), true), new SettingDefinition( IdentitySettingNames.Password.RequireDigit, - true.ToString(), + true.ToString(), L("DisplayName:Abp.Identity.Password.RequireDigit"), L("Description:Abp.Identity.Password.RequireDigit"), true), new SettingDefinition( IdentitySettingNames.Lockout.AllowedForNewUsers, - true.ToString(), + true.ToString(), L("DisplayName:Abp.Identity.Lockout.AllowedForNewUsers"), L("Description:Abp.Identity.Lockout.AllowedForNewUsers"), true), new SettingDefinition( IdentitySettingNames.Lockout.LockoutDuration, - (5 * 60).ToString(), + (5 * 60).ToString(), L("DisplayName:Abp.Identity.Lockout.LockoutDuration"), L("Description:Abp.Identity.Lockout.LockoutDuration"), true), new SettingDefinition( IdentitySettingNames.Lockout.MaxFailedAccessAttempts, - 5.ToString(), + 5.ToString(), L("DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts"), L("Description:Abp.Identity.Lockout.MaxFailedAccessAttempts"), true), new SettingDefinition( IdentitySettingNames.SignIn.RequireConfirmedEmail, - false.ToString(), + false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail"), L("Description:Abp.Identity.SignIn.RequireConfirmedEmail"), true), new SettingDefinition( - IdentitySettingNames.SignIn.EnablePhoneNumberConfirmation, - true.ToString(), - L("DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"), - L("Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"), + IdentitySettingNames.SignIn.EnablePhoneNumberConfirmation, + true.ToString(), + L("DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"), + L("Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"), true), new SettingDefinition( - IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber, - false.ToString(), - L("DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), - L("Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), + IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber, + false.ToString(), + L("DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), + L("Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), true), new SettingDefinition( IdentitySettingNames.User.IsUserNameUpdateEnabled, - true.ToString(), + true.ToString(), L("DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled"), L("Description:Abp.Identity.User.IsUserNameUpdateEnabled"), true), new SettingDefinition( IdentitySettingNames.User.IsEmailUpdateEnabled, - true.ToString(), + true.ToString(), L("DisplayName:Abp.Identity.User.IsEmailUpdateEnabled"), L("Description:Abp.Identity.User.IsEmailUpdateEnabled"), true), - new SettingDefinition( + new SettingDefinition( IdentitySettingNames.OrganizationUnit.MaxUserMembershipCount, - int.MaxValue.ToString(), + int.MaxValue.ToString(), L("Identity.OrganizationUnit.MaxUserMembershipCount"), L("Identity.OrganizationUnit.MaxUserMembershipCount"), - true) + true), + + new SettingDefinition(IdentitySettingNames.TwoFactor.Behaviour, + IdentityTwoFactorBehaviour.Optional.ToString(), + L("DisplayName:Abp.Identity.TwoFactorBehaviour"), + L("Description:Abp.Identity.TwoFactorBehaviour"), + isVisibleToClients: true), + + new SettingDefinition(IdentitySettingNames.TwoFactor.UsersCanChange, + true.ToString(), + L("DisplayName:Abp.Identity.UsersCanChange"), + L("Description:Abp.Identity.UsersCanChange"), + isVisibleToClients: true) ); } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySettingProviderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySettingProviderExtensions.cs new file mode 100644 index 0000000000..6ffe035c80 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySettingProviderExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Identity.Features; +using Volo.Abp.Identity.Settings; +using Volo.Abp.Settings; + +namespace Volo.Abp.Identity +{ + public static class IdentitySettingProviderExtensions + { + public static async Task GetIdentityTwoFactorBehaviour([NotNull] this ISettingProvider settingProvider) + { + Check.NotNull(settingProvider, nameof(settingProvider)); + + var value = await settingProvider.GetOrNullAsync(IdentitySettingNames.TwoFactor.Behaviour); + if (value.IsNullOrWhiteSpace() || !Enum.TryParse(value, out var behaviour)) + { + throw new AbpException($"{IdentitySettingNames.TwoFactor.Behaviour} setting value is invalid"); + } + + return behaviour; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityTwoFactorManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityTwoFactorManager.cs new file mode 100644 index 0000000000..5838ee20b8 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityTwoFactorManager.cs @@ -0,0 +1,69 @@ +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; +using Volo.Abp.Features; +using Volo.Abp.Identity.Features; +using Volo.Abp.Settings; + +namespace Volo.Abp.Identity +{ + public class IdentityTwoFactorManager : IDomainService + { + protected IFeatureChecker FeatureChecker { get; } + + protected ISettingProvider SettingProvider { get; } + + public IdentityTwoFactorManager(IFeatureChecker featureChecker, ISettingProvider settingProvider) + { + FeatureChecker = featureChecker; + SettingProvider = settingProvider; + } + + public virtual async Task IsOptionalAsync() + { + var feature = await FeatureChecker.GetIdentityTwoFactorBehaviour(); + if (feature == IdentityTwoFactorBehaviour.Optional) + { + var setting = await SettingProvider.GetIdentityTwoFactorBehaviour(); + if (setting == IdentityTwoFactorBehaviour.Optional) + { + return true; + } + } + + return false; + } + + public virtual async Task IsForcedEnableAsync() + { + var feature = await FeatureChecker.GetIdentityTwoFactorBehaviour(); + if (feature == IdentityTwoFactorBehaviour.Forced) + { + return true; + } + + var setting = await SettingProvider.GetIdentityTwoFactorBehaviour(); + if (setting == IdentityTwoFactorBehaviour.Forced) + { + return true; + } + + return false; + } + + public virtual async Task IsForcedDisableAsync() + { + var feature = await FeatureChecker.GetIdentityTwoFactorBehaviour(); + if (feature == IdentityTwoFactorBehaviour.Disabled) + { + return true; + } + + var setting = await SettingProvider.GetIdentityTwoFactorBehaviour(); + if (setting == IdentityTwoFactorBehaviour.Disabled) + { + return true; + } + return false; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs index 17a0fbd39d..2712e41607 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs @@ -11,7 +11,10 @@ using Microsoft.Extensions.Logging; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; +using Volo.Abp.Features; using Volo.Abp.Guids; +using Volo.Abp.Identity.Features; +using Volo.Abp.Settings; namespace Volo.Abp.Identity { @@ -56,12 +59,17 @@ namespace Volo.Abp.Identity protected ILookupNormalizer LookupNormalizer { get; } protected IIdentityUserRepository UserRepository { get; } + protected IFeatureChecker FeatureChecker { get; } + protected ISettingProvider SettingProvider { get; } + public IdentityUserStore( IIdentityUserRepository userRepository, IIdentityRoleRepository roleRepository, IGuidGenerator guidGenerator, ILogger logger, ILookupNormalizer lookupNormalizer, + IFeatureChecker featureChecker, + ISettingProvider settingProvider, IdentityErrorDescriber describer = null) { UserRepository = userRepository; @@ -69,6 +77,8 @@ namespace Volo.Abp.Identity GuidGenerator = guidGenerator; Logger = logger; LookupNormalizer = lookupNormalizer; + FeatureChecker = featureChecker; + SettingProvider = settingProvider; ErrorDescriber = describer ?? new IdentityErrorDescriber(); } @@ -931,13 +941,33 @@ namespace Volo.Abp.Identity /// The that represents the asynchronous operation, containing a flag indicating whether the specified /// has two factor authentication enabled or not. /// - public virtual Task GetTwoFactorEnabledAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + public virtual async Task GetTwoFactorEnabledAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); Check.NotNull(user, nameof(user)); - return Task.FromResult(user.TwoFactorEnabled); + var feature = await FeatureChecker.GetIdentityTwoFactorBehaviour(); + if (feature == IdentityTwoFactorBehaviour.Disabled) + { + return false; + } + if (feature == IdentityTwoFactorBehaviour.Forced) + { + return true; + } + + var setting = await SettingProvider.GetIdentityTwoFactorBehaviour(); + if (setting == IdentityTwoFactorBehaviour.Disabled) + { + return false; + } + if (setting == IdentityTwoFactorBehaviour.Forced) + { + return true; + } + + return user.TwoFactorEnabled; } /// diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs index 23a4529502..5362969011 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; -using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.AutoMapper; using Volo.Abp.Identity.Localization; @@ -16,7 +15,6 @@ using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Identity.Web { [DependsOn(typeof(AbpIdentityHttpApiModule))] - [DependsOn(typeof(AbpAspNetCoreMvcUiBootstrapModule))] [DependsOn(typeof(AbpAutoMapperModule))] [DependsOn(typeof(AbpPermissionManagementWebModule))] [DependsOn(typeof(AbpAspNetCoreMvcUiThemeSharedModule))] diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj index 89bb07688a..8a0b8ebd5c 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -38,7 +38,6 @@ - diff --git a/modules/permission-management/Volo.Abp.PermissionManagement.sln b/modules/permission-management/Volo.Abp.PermissionManagement.sln index 7206382e8e..535a31fb2f 100644 --- a/modules/permission-management/Volo.Abp.PermissionManagement.sln +++ b/modules/permission-management/Volo.Abp.PermissionManagement.sln @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.PermissionManageme EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.PermissionManagement.Application.Tests", "test\Volo.Abp.PermissionManagement.Application.Tests\Volo.Abp.PermissionManagement.Application.Tests.csproj", "{A0F72F5F-3713-4E06-ADB7-15ADFDCB79B1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.PermissionManagement.Blazor", "src\Volo.Abp.PermissionManagement.Blazor\Volo.Abp.PermissionManagement.Blazor.csproj", "{6F899C50-83BB-43C4-983A-DCCD8FBBF066}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -97,6 +99,10 @@ Global {A0F72F5F-3713-4E06-ADB7-15ADFDCB79B1}.Debug|Any CPU.Build.0 = Debug|Any CPU {A0F72F5F-3713-4E06-ADB7-15ADFDCB79B1}.Release|Any CPU.ActiveCfg = Release|Any CPU {A0F72F5F-3713-4E06-ADB7-15ADFDCB79B1}.Release|Any CPU.Build.0 = Release|Any CPU + {6F899C50-83BB-43C4-983A-DCCD8FBBF066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F899C50-83BB-43C4-983A-DCCD8FBBF066}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F899C50-83BB-43C4-983A-DCCD8FBBF066}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F899C50-83BB-43C4-983A-DCCD8FBBF066}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -116,6 +122,7 @@ Global {9E0B517E-F02E-436F-9695-7CF12795D34C} = {B559B878-38F7-49CC-BC06-43A32D68C1A7} {1CD80519-9431-48DB-B0EA-291A73FF9F49} = {B559B878-38F7-49CC-BC06-43A32D68C1A7} {A0F72F5F-3713-4E06-ADB7-15ADFDCB79B1} = {63DA4A89-5908-4F37-B7E6-525AEEF20C77} + {6F899C50-83BB-43C4-983A-DCCD8FBBF066} = {B559B878-38F7-49CC-BC06-43A32D68C1A7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8FC7DF78-5E2D-489F-9D43-147D2ABAA112} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/AbpPermissionManagementBlazorModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/AbpPermissionManagementBlazorModule.cs new file mode 100644 index 0000000000..26381615f3 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/AbpPermissionManagementBlazorModule.cs @@ -0,0 +1,16 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; + +namespace Volo.Abp.PermissionManagement.Blazor +{ + [DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingModule), + typeof(AbpAutoMapperModule), + typeof(AbpPermissionManagementHttpApiClientModule) + )] + public class AbpPermissionManagementBlazorModule : AbpModule + { + + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor new file mode 100644 index 0000000000..e94319c9ff --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor @@ -0,0 +1,79 @@ +@using Microsoft.Extensions.Localization +@using Volo.Abp.PermissionManagement.Localization +@inject IStringLocalizer L + + + + + @L["Permissions"] - @_entityDisplayName + + + + + + @L["SelectAllInAllTabs"] + + + + + @if (_groups != null) + { + + + @foreach (var group in _groups) + { + + @if (group.Permissions.Any(x => x.IsGranted)) + { + + @group.DisplayName ( @(group.Permissions.Count(x => x.IsGranted)) ) + + } + else + { + + @group.DisplayName ( @(group.Permissions.Count(x => x.IsGranted)) ) + + } + + } + + + @foreach (var group in _groups) + { + +

@group.DisplayName

+ + + + + @L["SelectAllInThisTab"] + + + + + @foreach (var permission in group.Permissions) + { + + + @permission.DisplayName + + + } + +
+ } +
+
+ } +
+ + + + +
+
\ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs new file mode 100644 index 0000000000..40333fdc80 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Blazorise; +using Microsoft.AspNetCore.Components; + +namespace Volo.Abp.PermissionManagement.Blazor.Components +{ + public partial class PermissionManagementModal + { + [Inject] private IPermissionAppService PermissionAppService { get; set; } + + private Modal _modal; + + private string _providerName; + private string _providerKey; + + private string _entityDisplayName; + private List _groups; + + private List _disabledPermissions; + + private string _selectedTabName; + + private bool GrantAll + { + get + { + return _groups != null && _groups.All(x => x.Permissions.All(y => y.IsGranted)); + } + set + { + if (_groups == null) + { + return; + } + + foreach (var permissionGroupDto in _groups) + { + foreach (var permission in permissionGroupDto.Permissions) + { + if (!IsDisabledPermission(permission)) + { + permission.IsGranted = value; + } + } + } + } + } + + public async Task OpenAsync(string providerName, string providerKey) + { + _providerName = providerName; + _providerKey = providerKey; + + var result = await PermissionAppService.GetAsync(_providerName, _providerKey); + + _entityDisplayName = result.EntityDisplayName; + _groups = result.Groups; + + _disabledPermissions = + _groups.SelectMany(x => x.Permissions) + .Where( + x => x.IsGranted && + x.GrantedProviders.All(y => y.ProviderName != _providerName) + ).ToList(); + + _selectedTabName = GetNormalizedGroupName(_groups.First().Name); + + _modal.Show(); + } + + private void CloseModal() + { + _modal.Hide(); + } + + private async Task SaveAsync() + { + var updateDto = new UpdatePermissionsDto + { + Permissions = _groups + .SelectMany(g => g.Permissions) + .Select(p => new UpdatePermissionDto {IsGranted = p.IsGranted, Name = p.Name}) + .ToArray() + }; + + await PermissionAppService.UpdateAsync(_providerName, _providerKey, updateDto); + + _modal.Hide(); + } + + private string GetNormalizedGroupName(string name) + { + return "PermissionGroup_" + name.Replace(".", "_"); + } + + private void GrantAllChanged(bool value) + { + GrantAll = value; + } + + private void GroupGrantAllChanged(bool value, string groupName) + { + foreach (var permission in _groups.First(x => x.Name == groupName).Permissions) + { + if (!IsDisabledPermission(permission)) + { + permission.IsGranted = value; + } + } + } + + private bool IsDisabledPermission(PermissionGrantInfoDto permissionGrantInfo) + { + return _disabledPermissions.Any(x => x == permissionGrantInfo); + } + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/FodyWeavers.xml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/FodyWeavers.xsd b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Volo.Abp.PermissionManagement.Blazor.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Volo.Abp.PermissionManagement.Blazor.csproj new file mode 100644 index 0000000000..0663ca49a5 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Volo.Abp.PermissionManagement.Blazor.csproj @@ -0,0 +1,20 @@ + + + + + + + netstandard2.1 + 3.0 + + + + + + + + + + + + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/_Imports.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/_Imports.razor new file mode 100644 index 0000000000..4685ac9893 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/_Imports.razor @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.WebAssembly +@using Volo.Abp.BlazoriseUI +@using Blazorise +@using Blazorise.DataGrid \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Properties/launchSettings.json b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Properties/launchSettings.json new file mode 100644 index 0000000000..5e8bf1d171 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:62454/", + "sslPort": 44344 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.PermissionManagement.Web": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts index 027c284f52..dd7abf929e 100644 --- a/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/config-state.service.ts @@ -49,6 +49,10 @@ export class ConfigStateService { return this.store.selectSnapshot(ConfigState.getLocalization(...args)); } + getLocalizationResource(...args: Parameters) { + return this.store.selectSnapshot(ConfigState.getLocalizationResource(...args)); + } + dispatchGetAppConfiguration() { return this.store.dispatch(new GetAppConfiguration()); } diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index 5b98afe0c3..dd64bd27ec 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -70,6 +70,10 @@ export class LocalizationService { return this.store.select(ConfigState.getLocalization(key, ...interpolateParams)); } + getResource(resourceName: string) { + return this.store.select(ConfigState.getLocalizationResource(resourceName)); + } + /** * Returns localized text with the given interpolation parameters in current language. * @param key Localization key to replace with localized text diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index ec9ccd6749..8639981456 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -128,6 +128,16 @@ export class ConfigState { return selector; } + static getLocalizationResource(resourceName: string) { + const selector = createSelector([ConfigState], (state: Config.State): { + [key: string]: string; + } => { + return state.localization.values[resourceName]; + }); + + return selector; + } + static getLocalization( key: string | Config.LocalizationWithDefault, ...interpolateParams: string[] diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index b45b4a47da..2685011c13 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -30,8 +30,12 @@ $projects = ( "framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer", "framework/src/Volo.Abp.AspNetCore.Authentication.OAuth", "framework/src/Volo.Abp.AspNetCore", + "framework/src/Volo.Abp.AspNetCore.Components.WebAssembly", + "framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming", + "framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme", "framework/src/Volo.Abp.AspNetCore.MultiTenancy", "framework/src/Volo.Abp.AspNetCore.Mvc.Client", + "framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common", "framework/src/Volo.Abp.AspNetCore.Mvc.Contracts", "framework/src/Volo.Abp.AspNetCore.Mvc", "framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap", @@ -48,6 +52,7 @@ $projects = ( "framework/src/Volo.Abp.Auditing", "framework/src/Volo.Abp.Authorization", "framework/src/Volo.Abp.Autofac", + "framework/src/Volo.Abp.Autofac.WebAssembly", "framework/src/Volo.Abp.AutoMapper", "framework/src/Volo.Abp.BackgroundJobs.Abstractions", "framework/src/Volo.Abp.BackgroundJobs", @@ -56,6 +61,7 @@ $projects = ( "framework/src/Volo.Abp.BackgroundJobs.Quartz", "framework/src/Volo.Abp.BackgroundWorkers", "framework/src/Volo.Abp.BackgroundWorkers.Quartz", + "framework/src/Volo.Abp.BlazoriseUI", "framework/src/Volo.Abp.BlobStoring", "framework/src/Volo.Abp.BlobStoring.FileSystem", "framework/src/Volo.Abp.BlobStoring.Aliyun", @@ -94,6 +100,7 @@ $projects = ( "framework/src/Volo.Abp.Http.Client", "framework/src/Volo.Abp.Http.Client.IdentityModel", "framework/src/Volo.Abp.Http.Client.IdentityModel.Web", + "framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly", "framework/src/Volo.Abp.Http", "framework/src/Volo.Abp.IdentityModel", "framework/src/Volo.Abp.Json", @@ -193,6 +200,7 @@ $projects = ( "modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi", "modules/feature-management/src/Volo.Abp.FeatureManagement.MongoDB", "modules/feature-management/src/Volo.Abp.FeatureManagement.Web", + "modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor", # modules/identity "modules/identity/src/Volo.Abp.Identity.Application.Contracts", @@ -205,6 +213,7 @@ $projects = ( "modules/identity/src/Volo.Abp.Identity.HttpApi", "modules/identity/src/Volo.Abp.Identity.MongoDB", "modules/identity/src/Volo.Abp.Identity.Web", + "modules/identity/src/Volo.Abp.Identity.Blazor", "modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity", # modules/identityserver @@ -224,6 +233,7 @@ $projects = ( "modules/permission-management/src/Volo.Abp.PermissionManagement.HttpApi", "modules/permission-management/src/Volo.Abp.PermissionManagement.MongoDB", "modules/permission-management/src/Volo.Abp.PermissionManagement.Web", + "modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor", # modules/setting-management "modules/setting-management/src/Volo.Abp.SettingManagement.Domain", diff --git a/templates/app/aspnet-core/MyCompanyName.MyProjectName.sln b/templates/app/aspnet-core/MyCompanyName.MyProjectName.sln index bc8ab2ff39..d8b8d51d8a 100644 --- a/templates/app/aspnet-core/MyCompanyName.MyProjectName.sln +++ b/templates/app/aspnet-core/MyCompanyName.MyProjectName.sln @@ -49,7 +49,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCompanyName.MyProjectName EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCompanyName.MyProjectName.DbMigrator", "src\MyCompanyName.MyProjectName.DbMigrator\MyCompanyName.MyProjectName.DbMigrator.csproj", "{AA94D832-1CCC-4715-95A9-A483F23A1A5D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyCompanyName.MyProjectName.HttpApi.HostWithIds", "src\MyCompanyName.MyProjectName.HttpApi.HostWithIds\MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj", "{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCompanyName.MyProjectName.HttpApi.HostWithIds", "src\MyCompanyName.MyProjectName.HttpApi.HostWithIds\MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj", "{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyCompanyName.MyProjectName.Blazor", "src\MyCompanyName.MyProjectName.Blazor\MyCompanyName.MyProjectName.Blazor.csproj", "{27B2DDC7-8B75-4322-A312-25419C15D9D8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -145,6 +147,10 @@ Global {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.Build.0 = Debug|Any CPU {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.ActiveCfg = Release|Any CPU {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.Build.0 = Release|Any CPU + {27B2DDC7-8B75-4322-A312-25419C15D9D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27B2DDC7-8B75-4322-A312-25419C15D9D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27B2DDC7-8B75-4322-A312-25419C15D9D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27B2DDC7-8B75-4322-A312-25419C15D9D8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -172,6 +178,7 @@ Global {EF480016-9127-4916-8735-D2466BDBC582} = {04DBDB01-70F4-4E06-B468-8F87850B22BE} {AA94D832-1CCC-4715-95A9-A483F23A1A5D} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} + {27B2DDC7-8B75-4322-A312-25419C15D9D8} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj new file mode 100644 index 0000000000..8ae3302d2c --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.1 + 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs new file mode 100644 index 0000000000..c6ee19d53c --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs @@ -0,0 +1,94 @@ +using System; +using System.Net.Http; +using Blazorise; +using Blazorise.Bootstrap; +using Blazorise.Icons.FontAwesome; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp; +using Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme; +using Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing; +using Volo.Abp.Autofac.WebAssembly; +using Volo.Abp.Modularity; +using Volo.Abp.UI.Navigation; +using Volo.Abp.Identity.Blazor; + +namespace MyCompanyName.MyProjectName.Blazor +{ + [DependsOn( + typeof(AbpAutofacWebAssemblyModule), + typeof(MyProjectNameHttpApiClientModule), + typeof(AbpIdentityBlazorModule), + typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule) + )] + public class MyProjectNameBlazorModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var environment = context.Services.GetSingletonInstance(); + var builder = context.Services.GetSingletonInstance(); + + ConfigureAuthentication(builder); + ConfigureHttpClient(context, environment); + ConfigureBlazorise(context); + ConfigureRouter(context); + ConfigureUI(builder); + ConfigureMenu(context); + } + + private void ConfigureRouter(ServiceConfigurationContext context) + { + Configure(options => + { + options.AppAssembly = typeof(MyProjectNameBlazorModule).Assembly; + }); + } + + private void ConfigureMenu(ServiceConfigurationContext context) + { + Configure(options => + { + options.MenuContributors.Add(new MyProjectNameMenuContributor()); + }); + } + + private void ConfigureBlazorise(ServiceConfigurationContext context) + { + context.Services + .AddBlazorise() + .AddBootstrapProviders() + .AddFontAwesomeIcons(); + } + + private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) + { + builder.Services.AddOidcAuthentication(options => + { + builder.Configuration.Bind("AuthServer", options.ProviderOptions); + options.ProviderOptions.DefaultScopes.Add("MyProjectName"); + }); + } + + private static void ConfigureUI(WebAssemblyHostBuilder builder) + { + builder.RootComponents.Add("app"); + } + + private static void ConfigureHttpClient(ServiceConfigurationContext context, IWebAssemblyHostEnvironment environment) + { + context.Services.AddTransient(sp => new HttpClient + { + BaseAddress = new Uri(environment.BaseAddress) + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + context.ServiceProvider + .UseBootstrapProviders() + .UseFontAwesomeIcons(); + } + } +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBrandingProvider.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBrandingProvider.cs new file mode 100644 index 0000000000..cada0bba19 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBrandingProvider.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Ui.Branding; + +namespace MyCompanyName.MyProjectName.Blazor +{ + public class MyProjectNameBrandingProvider : DefaultBrandingProvider + { + public override string AppName => "MyProjectName"; + } +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameMenuContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameMenuContributor.cs new file mode 100644 index 0000000000..4fa8ef9095 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameMenuContributor.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using MyCompanyName.MyProjectName.Localization; +using Volo.Abp.UI.Navigation; + +namespace MyCompanyName.MyProjectName.Blazor +{ + public class MyProjectNameMenuContributor : IMenuContributor + { + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + var l = context.GetLocalizer(); + + context.Menu.Items.Insert( + 0, + new ApplicationMenuItem( + "MyProjectName.Home", + l["Menu:Home"], + "/", + icon: "fas fa-home" + ) + ); + + return Task.CompletedTask; + } + } +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Pages/Index.razor new file mode 100644 index 0000000000..66c4949ac9 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Pages/Index.razor @@ -0,0 +1,60 @@ +@page "/" +@using Volo.Abp.Users +@using Volo.Abp.MultiTenancy +@using System.Security.Claims +@inject ICurrentUser CurrentUser +@inject ICurrentTenant CurrentTenant +@inject AuthenticationStateProvider AuthenticationStateProvider + +

Welcome to MyProjectName!

+ +@if (CurrentUser.IsAuthenticated) +{ +

Current User

+
    +
  • Id: @CurrentUser.Id
  • +
  • TenantId: @CurrentUser.TenantId
  • +
  • UserName: @CurrentUser.UserName
  • +
  • Name: @CurrentUser.Name
  • +
  • SurName: @CurrentUser.SurName
  • +
  • Email: @CurrentUser.Email
  • +
  • EmailVerified: @CurrentUser.EmailVerified
  • +
  • PhoneNumber: @CurrentUser.PhoneNumber
  • +
  • PhoneNumberVerified: @CurrentUser.PhoneNumberVerified
  • +
  • Roles: @CurrentUser.Roles.JoinAsString(", ")
  • +
+} + +@if (_claims != null) +{ +

Current Claims

+
    + @foreach (var claim in _claims) + { +
  • @claim.Type: @claim.Value
  • + } +
+} + +@if (CurrentTenant.IsAvailable) +{ +

Current Tenant

+
    +
  • Id: @CurrentTenant.Id
  • +
  • Name: @CurrentTenant.Name
  • +
+} + +@code +{ + private IEnumerable _claims; + + protected override async Task OnInitializedAsync() + { + var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + if (authState.User.Identity.IsAuthenticated) + { + _claims = authState.User.Claims; + } + } +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Program.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Program.cs new file mode 100644 index 0000000000..31f1c228ad --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Program.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + +namespace MyCompanyName.MyProjectName.Blazor +{ + public class Program + { + public static async Task Main(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + + var application = builder.AddApplication(options => + { + options.UseAutofac(); + }); + + var host = builder.Build(); + + await application.InitializeAsync(host.Services); + + await host.RunAsync(); + } + } +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Properties/launchSettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Properties/launchSettings.json new file mode 100644 index 0000000000..9ef766441e --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44307", + "sslPort": 44307 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "MyCompanyName.MyProjectName.Blazor": { + "commandName": "Project", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:44307", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/_Imports.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/_Imports.razor new file mode 100644 index 0000000000..b1ade9c628 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/_Imports.razor @@ -0,0 +1,11 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using Volo.Abp.AspNetCore.Components.WebAssembly +@using MyCompanyName.MyProjectName.Blazor +@using Blazorise +@using Blazorise.DataGrid diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.Development.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.Development.json new file mode 100644 index 0000000000..0db3279e44 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.Development.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.json new file mode 100644 index 0000000000..af7cddcc72 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.json @@ -0,0 +1,12 @@ +{ + "AuthServer": { + "Authority": "https://localhost:44305", + "ClientId": "MyProjectName_Blazor", + "ResponseType": "code" + }, + "RemoteServices": { + "Default": { + "BaseUrl": "https://localhost:44305" + } + } +} diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/favicon.ico b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/favicon.ico new file mode 100644 index 0000000000..a3a799985c Binary files /dev/null and b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/favicon.ico differ diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html new file mode 100644 index 0000000000..99ab025095 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html @@ -0,0 +1,39 @@ + + + + + + + MyCompanyName.MyProjectName.Blazor + + + + + + + + + + + + + Loading... + +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + + + + + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/main.css b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/main.css new file mode 100644 index 0000000000..23e0f7a249 --- /dev/null +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/main.css @@ -0,0 +1,18 @@ +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + +#blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; +} \ No newline at end of file diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json index 17ce15c462..0accabeaaa 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json @@ -13,6 +13,10 @@ "ClientId": "MyProjectName_App", "ClientSecret": "1q2w3e*", "RootUrl": "http://localhost:4200" + }, + "MyProjectName_Blazor": { + "ClientId": "MyProjectName_Blazor", + "RootUrl": "https://localhost:44307" } } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs index 805bc0e424..dcd105a4e9 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/IdentityServer/IdentityServerDataSeedContributor.cs @@ -142,13 +142,30 @@ namespace MyCompanyName.MyProjectName.IdentityServer postLogoutRedirectUri: webClientRootUrl ); } + + // Blazor Client + var blazorClientId = configurationSection["MyProjectName_Blazor:ClientId"]; + if (!blazorClientId.IsNullOrWhiteSpace()) + { + var blazorRootUrl = configurationSection["MyProjectName_Blazor:RootUrl"].TrimEnd('/'); + + await CreateClientAsync( + name: blazorClientId, + scopes: commonScopes, + grantTypes: new[] { "authorization_code" }, + secret: configurationSection["MyProjectName_Blazor:ClientSecret"]?.Sha256(), + requireClientSecret: false, + redirectUri: $"{blazorRootUrl}/authentication/login-callback", + postLogoutRedirectUri: $"{blazorRootUrl}/authentication/logout-callback" + ); + } } private async Task CreateClientAsync( string name, IEnumerable scopes, IEnumerable grantTypes, - string secret, + string secret = null, string redirectUri = null, string postLogoutRedirectUri = null, string frontChannelLogoutUri = null, @@ -199,9 +216,12 @@ namespace MyCompanyName.MyProjectName.IdentityServer } } - if (client.FindSecret(secret) == null) + if (!secret.IsNullOrEmpty()) { - client.AddSecret(secret); + if (client.FindSecret(secret) == null) + { + client.AddSecret(secret); + } } if (redirectUri != null) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs index 1cd8ea1c0b..25d1598438 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net.Http; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -12,7 +11,6 @@ using MyCompanyName.MyProjectName.EntityFrameworkCore; using MyCompanyName.MyProjectName.MultiTenancy; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Authentication.JwtBearer; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/appsettings.json index 2ac6a310cc..d71ef4ef79 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/appsettings.json @@ -1,7 +1,7 @@ { "App": { "SelfUrl": "https://localhost:44305", - "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200" + "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307" }, "ConnectionStrings": { "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true" diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json index 390efeb07d..887908f3db 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json @@ -1,7 +1,7 @@ { "App": { "SelfUrl": "https://localhost:44301", - "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200" + "CorsOrigins": "https://*.MyProjectName.com,http://localhost:4200,https://localhost:44307" }, "ConnectionStrings": { "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true"