diff --git a/.gitignore b/.gitignore index 1f14485237..5b47e32794 100644 --- a/.gitignore +++ b/.gitignore @@ -303,4 +303,4 @@ modules/virtual-file-explorer/app/Volo.Abp.VirtualFileExplorer.DemoApp/Logs/ /templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package-lock.json /templates/app/angular/package-lock.json /modules/client-simulation/demo/Volo.ClientSimulation.Demo/package-lock.json - +abp-build-config.json diff --git a/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md new file mode 100644 index 0000000000..8f09cc24e6 --- /dev/null +++ b/docs/en/Blog-Posts/2020-09-17 v3_2_Preview/POST.md @@ -0,0 +1,121 @@ +# ABP Framework 3.2 RC with the new Blazor UI + +We are extremely excited today to release the ABP Framework (and the ABP Commercial, as always) version `3.2.0-rc.1` (Release Candidate). This release includes an early preview version of the **Blazor UI** for the ABP.IO Platform. + +## The Blazor UI + +While the Blazor UI should be considered as **experimental** for now, it is possible to develop your application today. + +### Fundamental Services + +Currently implemented some important framework features; + +* **Authentication** through the MVC backend using the OpenId Connect authorization code flow. So, all the current login options (login, register, forgot password, external/social logins...) are supported. +* **Authorization**, using the ABP Framework **permissions** as well as the standard authorization system. +* **Localization** just works like the MVC UI. +* **Basic Theme** with top main menu. +* **Dynamic C# HTTP API proxies**, so you can directly consume your backend API by injecting the application service interfaces. +* Some other **fundamental services** like ISettingProvider, IFeatureChecker, ICurrentUser + +Also, the standard .net services are already available, like caching, logging, validation and much more. Since the ABP Framework is layered itself, all the non MVC UI related features are already available. + +### Pre-Built Modules + +And some modules have been implemented; + +* **Identity** module is pre-installed and provides user**, role and permission management**. +* **Profile management** page is implemented to allow to change password and personal settings. + +### About the Blazorise Library + +We've selected the [Blazorise](https://blazorise.com/) as a fundamental UI library for the Blazor UI. It already support different HTML/CSS frameworks and significantly increases the developer productivity. + +We also have a good news: **[Mladen Macanović](https://github.com/stsrki)**, the creator of the Blazorise, is **joining to the core ABP Framework team** in the next weeks. We are excited to work with him to bring the power of these two successfully projects together. + +### The Tutorial + +We are currently in progress of updating the web application development tutorial for the Blazor UI. Follow the [@abpframework](https://twitter.com/abpframework) Twitter account to get informed once it's done. + +### Get started with the Blazor UI + +If you want to try the Blazor UI today, follow the instructions below. + +First, install the the latest [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) preview version: + +````bash +dotnet tool update Volo.Abp.Cli -g --version 3.2.0-rc.1 +```` + +Then you can create a new solution using the *abp new* command: + +````bash +abp new AbpBlazorDemo -u blazor +```` + +> See the ABP CLI documentation for the additional options, like MongoDB database or separated authentication server. + +Open the generated solution using the latest Visual Studio 2019. You will see a solution structure like the picture below: + +TODO + +* Run the `.DbMigrator` project to create the database and seed the initial data. +* Run the `HttpApi.Host` project for the server side. +* Run the `.Blazor` project to start the Blazor UI. + +Use `admin` as the username as `1q2w3E*` as the password to login to the application. + +TODO + +## What's New with the ABP Framework 3.2 + +Beside the Blazor UI, there are a lot of issues have been closed with [the milestone 3.2](https://github.com/abpframework/abp/milestone/39?closed=1). I will highlight some of the major features and changes released with this version. + +### MongoDB ACID Transactions + +[MongoDB integration](https://docs.abp.io/en/abp/latest/MongoDB) now supports multi-document transactions that comes with the MongoDB 4.x. + +> Transactions are disabled for automated integration tests coming with the application startup template, since the Mongo2Go library (we use in the test projects) has a problem with the transactions. We've sent a [Pull Request](https://github.com/Mongo2Go/Mongo2Go/pull/101) to fix it and will enable the transactions again when they merge & release it. +> +> If you are upgrading an existing solution and using MongoDB, please disable transactions for the test projects by following the [Unit Of Work](https://docs.abp.io/en/abp/latest/Unit-Of-Work) documentation. + +### Kafka Integration for the Distributed Event Bus + +ABP Framework's [distributed event system](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus) has been [integrated to RabbitMQ](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus-RabbitMQ-Integration) before. By the version 3.2, it has a Kafka integration package, named [Volo.Abp.EventBus.Kafka](https://www.nuget.org/packages/Volo.Abp.EventBus.Kafka). + +See the [Kafka integration documentation](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus-Kafka-Integration) to learn how to install and configure it. + +### Host Features + +TODO + +### AbpHttpClientBuilderOptions + +TODO + +### Account Module: Profile Management Page Extensions + +TODO + +### ABP Build Command + +We are using **mono repository** approach and the [abp repository](https://github.com/abpframework/abp) has tens of solutions and hundreds of projects (the framework, modules, tooling, templates...) with all of them are referencing to each other. + +It gets a significant time to build the whole repository for every Git push. To **optimize** this process, we've created the **abp build** command in the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI): + +````bash +abp build +```` + +We will use this command to build the abp repository or a solution inside it. However it is available to everyone in case of need. + +> **Most of the people will not need it**. If you need it, see the [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document to learn all the details and options. + +### Other Features, Improvements and Changes + +* Introduced the `DynamicRangeAttribute` that can be used to determine the range values on runtime, just like the `DynamicStringLengthAttribute` was introduced before. +* Improved the feature management modal for multi-tenant applications to group features on the UI and show hierarchically. +* Added `--skip-cli-version-check` option to ABP CLI to improve the performance by bypassing the online version check. +* Angular UI now redirect to MVC UI (the authentication server side) for profile management page, if the authorization code flow is used (which is the default). +* Improvements and optimizations for the [Angular service proxy generation](https://blog.abp.io/abp/Introducing-the-Angular-Service-Proxy-Generation). + +And a lot of minor improvements and bug fixes. You can see [the milestone 3.2](https://github.com/abpframework/abp/milestone/39?closed=1) for all issues & PRs closed with this version. \ No newline at end of file diff --git a/docs/en/CLI-BuildCommand.md b/docs/en/CLI-BuildCommand.md new file mode 100644 index 0000000000..d9620aa621 --- /dev/null +++ b/docs/en/CLI-BuildCommand.md @@ -0,0 +1,67 @@ +# Build Command + +Building a .NET project is hard when the project references a project reference outside of the solution or even from a different GIT repository. This command builds a GIT repository and it's depending repositories or a single .NET solution File. In order ```build``` command to work, its **executing directory** or passed ```--working-directory``` parameter's directory must contain one of; + +* A .NET solution file (*.sln) +* abp-build-config.json + +When the executing directory (or ```--working-directory``` parameter's directory) contains a .NET solution file, ```build``` command builds all the projects in the related solution file and all project references on building project files recursively. + +When the executing directory (or ```--working-directory``` parameter's directory) contains a ```abp-build-config.json```, ```build``` command builds all changed projects form its last build and all project references on building project files recursively. + +# Build Command Config + +```abp-build-config.json``` contains properties below; + +* ```Name```: Name of the GIT repository. This can be friendly name of your GIT repository or any other unique string for the repository. +* ```RootPath```: Root path of the repository which contains ```.git``` folder. +* ```DependingRepositories```: Depending repository list of a repository. Each depending repository item contains same fields as a repository. +* ```IgnoredDirectories```: Relative directory paths to ignore while building a GIT repository. + +A sample ```abp-build-config.json``` looks like for a Windows OS; + +````json +{ + "Name": "main-repository", + "RootPath": "D:\\GitHub\\main-repository", + "DependingRepositories": [{ + "Name": "module-repository", + "RootPath": "D:\\GitHub\\module-repository" + }], + "IgnoredDirectories": [ + "utils" + ] +} +```` + +# Build Status + +ABP CLI stores a build status file for builds using the repository friendly names and current branch names in the; + +* ```%USERPROFILE%\.abp\build\``` for Windows. +* ```$HOME/.abp/build/``` for Linux/macOS. + +and uses this file when building same repository next time and only builds affected projects and decreases the total build time. A sample build status file content looks like; + +````json +{ + "RepositoryName": "main-repository", + "BranchName": "dev", + "CommitId": "84ecde8ba275aeeb14d24a87ad46a1e941adf8ba", + "SucceedProjects": [{ + "CsProjPath": "D:\\GitHub\\main-repository\\BookStore\\BookStore.Web.csproj", + "CommitId": "84ecde8ba275aeeb14d24a87ad46a1e941adf8ba" + }], + "DependingRepositories": [{ + "RepositoryName": "module-repository", + "BranchName": "dev", + "CommitId": "0598b8e45af9507fc9ba8abf304e78fc7d434e04", + "SucceedProjects": [{ + "CsProjPath": "D:\\GitHub\\module-repository\\identity-module\Identity\\Identity.Web.csproj", + "CommitId": "0598b8e45af9507fc9ba8abf304e78fc7d434e04" + }], + "DependingRepositories": [] + }] +} +```` + diff --git a/docs/en/CLI.md b/docs/en/CLI.md index ff2daad528..dd3287ce12 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -40,6 +40,7 @@ Here, the list of all available commands before explaining their details: * **`translate`**: Simplifies to translate localization files when you have multiple JSON [localization](Localization.md) files in a source control repository. * **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password. * **`logout`**: Logouts from your computer if you've authenticated before. +* **`build`**: Builds a GIT repository and depending repositories or a single .NET solution. ### help @@ -86,6 +87,8 @@ abp new Acme.BookStore * `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios. * `angular`: Angular. There are some additional options for this template: * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side. + * `blazor`: Blazor. There are some additional options for this template: + * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side. * `none`: Without UI. There are some additional options for this template: * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side. * `--mobile` or `-m`: Specifies the mobile application framework. If not specified, no mobile application will be created. Available options: @@ -358,3 +361,31 @@ Logs you out by removing the session token from your computer. abp logout ``` +### build + +This command builds a GIT repository and it's depending repositories or a single .NET solution File. In order ```build``` command to work, its **executing directory** or passed ```--working-directory``` parameter's directory must contain one of; + +* A .NET solution file (*.sln) +* abp-build-config.json (suggested to add this to .gitignore) + +Usage: + +````bash +abp build [options] +```` + +Example: + +``` +abp build --build-name "prod" --dotnet-build-arguments "\"--no-dependencies\"" +``` + +#### Options + +* ```--working-directory``` or ```-w```: Specifies the working directory. This option is useful when the command is executed outside of a GIT repository or when executing directory doesn't contain a .NET solution file. +* ```--build-name``` or ```-n```: Specifies a name for the build. This option is useful when same repository is used for more than one different builds. +* ```--dotnet-build-arguments``` or ```-a```: Arguments to pass ```dotnet build``` when building project files. This parameter must be passed like ```"\"{params}\""``` . +* ```--force``` or ```-f```: Forces to build projects even they are not changed from the last successful build. + +For more details, see [build command documentation](CLI-BuildCommand.md). + diff --git a/docs/en/Modules/Identity.md b/docs/en/Modules/Identity.md index b950658505..ac86b13564 100644 --- a/docs/en/Modules/Identity.md +++ b/docs/en/Modules/Identity.md @@ -4,3 +4,26 @@ Identity module is used to manage [organization units](Organization-Units.md), r See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon... + +## Identity Security Log + +The security log can record some important operations or changes about your account. You can save the security log if needed. + +You can inject and use `IdentitySecurityLogManager` or `ISecurityLogManager` to write security logs. It will create a log object by default and fill in some common values, such as `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`, etc. Of course, you can override them. + +```cs +await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() +{ + Identity = "IdentityServer"; + Action = "ChangePassword"; +}); +``` + +Configure `AbpSecurityLogOptions` to provide the application name for the log or disable this feature. **Enabled** by default. + +```cs +Configure(options => +{ + options.ApplicationName = "AbpSecurityTest"; +}); +``` diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 5138c9de04..c087c6b8f2 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -304,6 +304,7 @@ Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to dete * **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**. * **QueryStringTenantResolveContributor**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default. +* **FormTenantResolveContributor**: Tries to find current tenant id from form parameter. Parameter name is "__tenant" by default. * **RouteTenantResolveContributor**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route. * **HeaderTenantResolveContributor**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default. * **CookieTenantResolveContributor**: Tries to find current tenant id from cookie values. Cookie name is "__tenant" by default. diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 20afc1b50f..11f9964279 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -85,6 +85,8 @@ abp new Acme.BookStore * `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的.如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景. * `angular`: Angular. 这个模板还有一些额外的选项: * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. + * `blazor`: Blazor. 这个模板还有一些额外的选项: + * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. * `none`: 无UI. 这个模板还有一些额外的选项: * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. * `--mobile` 或者 `-m`: 指定移动应用程序框架. 如果未指定,则不会创建任何移动应用程序,其他选项: diff --git a/docs/zh-Hans/Modules/Identity.md b/docs/zh-Hans/Modules/Identity.md index f396de00c1..5589fb367e 100644 --- a/docs/zh-Hans/Modules/Identity.md +++ b/docs/zh-Hans/Modules/Identity.md @@ -1,5 +1,28 @@ # 身份管理模块 -身份模块基于Microsoft Identity 库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. +身份模块基于Microsoft Identity库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限. -参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. \ No newline at end of file +参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善. + +## Identity安全日志 + +安全日志可以记录账户的一些重要的操作或者改动, 你可以在在一些功能中保存安全日志. + +你可以注入和使用 `IdentitySecurityLogManager` 或 `ISecurityLogManager` 来保存安全日志. 默认它会创建一个安全日志对象并填充常用的值. 如 `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`等等. 当然你可以自定义这些值. + +```cs +await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() +{ + Identity = "IdentityServer"; + Action = "ChangePassword"; +}); +``` + +通过配置 `AbpSecurityLogOptions` 来提供应用程序的名称或者禁用安全日志功能. 默认是**启用**状态. + +```cs +Configure(options => +{ + options.ApplicationName = "AbpSecurityTest"; +}); +``` diff --git a/docs/zh-Hans/Multi-Tenancy.md b/docs/zh-Hans/Multi-Tenancy.md index 3e65a97cef..863e9cfb4b 100644 --- a/docs/zh-Hans/Multi-Tenancy.md +++ b/docs/zh-Hans/Multi-Tenancy.md @@ -305,6 +305,7 @@ Volo.Abp.AspNetCore.MultiTenancy 添加了下面这些租户解析器,从当前W * **CurrentUserTenantResolveContributor**: 如果当前用户已登录,从当前用户的声明中获取租户Id. **出于安全考虑,应该始终将其做为第一个Contributor**. * **QueryStringTenantResolveContributor**: 尝试从query string参数中获取当前租户,默认参数名为"__tenant". +* **FormTenantResolveContributor**: 尝试从form参数中获取当前租户,默认参数名为"__tenant". * **RouteTenantResolveContributor**:尝试从当前路由中获取(URL路径),默认是变量名是"__tenant".所以,如果你的路由中定义了这个变量,就可以从路由中确定当前租户. * **HeaderTenantResolveContributor**: 尝试从HTTP header中获取当前租户,默认的header名称是"__tenant". * **CookieTenantResolveContributor**: 尝试从当前cookie中获取当前租户.默认的Cookie名称是"__tenant". diff --git a/docs/zh-Hans/Virtual-File-System.md b/docs/zh-Hans/Virtual-File-System.md index e2e4c9e865..59c1fcf1f2 100644 --- a/docs/zh-Hans/Virtual-File-System.md +++ b/docs/zh-Hans/Virtual-File-System.md @@ -1,6 +1,6 @@ ## 虚拟文件系统 -虚拟文件系统使得管理物理上不存在于文件系统中(磁盘)的文件成为可能. 它主要用于将(js, css, image...)文件嵌入到程序集中, 并在运行时将它们象物理文件一样使用. +虚拟文件系统使得管理物理上不存在于文件系统中(磁盘)的文件成为可能. 它主要用于将(js, css, image...)文件嵌入到程序集中, 并在运行时将它们像物理文件一样使用. ## 安装 @@ -196,4 +196,4 @@ public class MyWebAppModule : AbpModule ### 物理文件 -物理文件总是覆盖虚拟文件. 这意味着如果你把一个文件放在 `/wwwroot/my-folder/my-file.css`,它将覆盖虚拟文件系统相同位置的文件.因此你需要知道在模块中定义的文件路径来覆盖它们. \ No newline at end of file +物理文件总是覆盖虚拟文件. 这意味着如果你把一个文件放在 `/wwwroot/my-folder/my-file.css`,它将覆盖虚拟文件系统相同位置的文件.因此你需要知道在模块中定义的文件路径来覆盖它们. diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index aa0a3f1222..503b42c7e1 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -331,6 +331,22 @@ 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 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Authentication.OpenIdConnect", "src\Volo.Abp.AspNetCore.Authentication.OpenIdConnect\Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj", "{DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -985,6 +1001,38 @@ 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 + {DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1152,6 +1200,14 @@ 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} + {DEFE3DB2-EA4F-4F90-87FC-B25D64427BC5} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/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.Authentication.OpenIdConnect/Microsoft/Extensions/DependencyInjection/AbpOpenIdConnectExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Microsoft/Extensions/DependencyInjection/AbpOpenIdConnectExtensions.cs new file mode 100644 index 0000000000..56886f679e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Microsoft/Extensions/DependencyInjection/AbpOpenIdConnectExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.MultiTenancy; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class AbpOpenIdConnectExtensions + { + public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder) + => builder.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, _ => { }); + + public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder, Action configureOptions) + => builder.AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, configureOptions); + + public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) + => builder.AddAbpOpenIdConnect(authenticationScheme, OpenIdConnectDefaults.DisplayName, configureOptions); + + public static AuthenticationBuilder AddAbpOpenIdConnect(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action configureOptions) + { + return builder.AddOpenIdConnect(authenticationScheme, displayName, options => + { + options.ClaimActions.MapAbpClaimTypes(); + + options.Events = new OpenIdConnectEvents + { + OnAuthorizationCodeReceived = receivedContext => + { + var tenantKey = receivedContext.HttpContext.RequestServices + .GetRequiredService>().Value.TenantKey; + + if (receivedContext.HttpContext.Request != null && + receivedContext.Request.Cookies.ContainsKey(tenantKey)) + { + receivedContext.TokenEndpointRequest.SetParameter(tenantKey, + receivedContext.Request.Cookies[tenantKey]); + } + + return Task.CompletedTask; + } + }; + + configureOptions?.Invoke(options); + }); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj new file mode 100644 index 0000000000..30f56491c5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo.Abp.AspNetCore.Authentication.OpenIdConnect.csproj @@ -0,0 +1,20 @@ + + + + + + + netcoreapp3.1 + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs new file mode 100644 index 0000000000..13a8fd5c04 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OpenIdConnect/Volo/Abp/AspNetCore/Authentication/OpenIdConnect/AbpAspNetCoreAuthenticationOpenIdConnectModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.AspNetCore.Authentication.OAuth; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.AspNetCore.Authentication.OpenIdConnect +{ + [DependsOn( + typeof(AbpMultiTenancyModule), + typeof(AbpAspNetCoreAuthenticationOAuthModule))] + public class AbpAspNetCoreAuthenticationOpenIdConnectModule : AbpModule + { + + } +} 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..88a005d7bf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor @@ -0,0 +1,41 @@ +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@using Volo.Abp.Users +@inject ICurrentUser CurrentUser +@inject NavigationManager Navigation +@inject SignOutSessionStateManager SignOutManager + + + + + @CurrentUser.UserName + + + @if (Menu != null) + { + @foreach (var menuItem in Menu.Items) + { + @menuItem.DisplayName + } + } + + Logout + + + + + Log in + + +@code{ + + private void NavigateTo(string uri) + { + Navigation.NavigateTo(uri); + } + + private async Task BeginSignOut() + { + await SignOutManager.SetSignOutState(); + NavigateTo("authentication/logout"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs new file mode 100644 index 0000000000..e0ad688513 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs @@ -0,0 +1,18 @@ +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 LoginDisplay + { + [Inject] protected IMenuManager MenuManager { get; set; } + + protected ApplicationMenu Menu { get; set; } + + protected override async Task OnInitializedAsync() + { + Menu = await MenuManager.GetAsync(StandardMenus.User); + } + } +} \ No newline at end of file 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..2c834ddc87 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiMessageService.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public interface IUiMessageService + { + Task InfoAsync(string message, string title = null); + + Task SuccessAsync(string message, string title = null); + + Task WarnAsync(string message, string title = null); + + Task ErrorAsync(string message, string title = null); + + 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..16fe041de4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiMessageService.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; +using Microsoft.JSInterop; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + //TODO: Implement with sweetalert in a new package + public class UiMessageService : IUiMessageService, ITransientDependency + { + protected IJSRuntime JsRuntime { get; } + + public UiMessageService(IJSRuntime jsRuntime) + { + JsRuntime = jsRuntime; + } + + public async Task InfoAsync(string message, string title = null) + { + await JsRuntime.InvokeVoidAsync("alert", message); + } + + public async Task SuccessAsync(string message, string title = null) + { + await JsRuntime.InvokeVoidAsync("alert", message); + } + + public async Task WarnAsync(string message, string title = null) + { + await JsRuntime.InvokeVoidAsync("alert", message); + } + + public async Task ErrorAsync(string message, string title = null) + { + await JsRuntime.InvokeVoidAsync("alert", message); + } + + public async Task ConfirmAsync(string message, string title = null) + { + 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.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs index d757b2f5f8..1d0268556f 100644 --- a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/AbpAspNetCoreMultiTenancyModule.cs @@ -5,7 +5,7 @@ using Volo.Abp.MultiTenancy; namespace Volo.Abp.AspNetCore.MultiTenancy { [DependsOn( - typeof(AbpMultiTenancyModule), + typeof(AbpMultiTenancyModule), typeof(AbpAspNetCoreModule) )] public class AbpAspNetCoreMultiTenancyModule : AbpModule @@ -15,6 +15,7 @@ namespace Volo.Abp.AspNetCore.MultiTenancy Configure(options => { options.TenantResolvers.Add(new QueryStringTenantResolveContributor()); + options.TenantResolvers.Add(new FormTenantResolveContributor()); options.TenantResolvers.Add(new RouteTenantResolveContributor()); options.TenantResolvers.Add(new HeaderTenantResolveContributor()); options.TenantResolvers.Add(new CookieTenantResolveContributor()); diff --git a/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs new file mode 100644 index 0000000000..5e70559f0c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.MultiTenancy/Volo/Abp/AspNetCore/MultiTenancy/FormTenantResolveContributor.cs @@ -0,0 +1,23 @@ +using System.Linq; +using Microsoft.AspNetCore.Http; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.AspNetCore.MultiTenancy +{ + public class FormTenantResolveContributor : HttpTenantResolveContributorBase + { + public const string ContributorName = "Form"; + + public override string Name => ContributorName; + + protected override string GetTenantIdOrNameFromHttpContextOrNull(ITenantResolveContext context, HttpContext httpContext) + { + if (httpContext.Request == null || !httpContext.Request.Form.Any()) + { + return null; + } + + return httpContext.Request.Form[context.GetAbpAspNetCoreMultiTenancyOptions().TenantKey]; + } + } +} 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.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/AlertsDemo/AlertsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/AlertsDemo/AlertsDemoViewComponent.cs index 2599c317cf..4e1bdd133f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/AlertsDemo/AlertsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/AlertsDemo/AlertsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.AlertsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class AlertsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/AlertsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BadgesDemo/BadgesDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BadgesDemo/BadgesDemoViewComponent.cs index bdc47336cd..727b613926 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BadgesDemo/BadgesDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BadgesDemo/BadgesDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.BadgesDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class BadgesDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/BadgesDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/BordersDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/BordersDemoViewComponent.cs index 2ba33feb54..b288653010 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/BordersDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/BordersDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.BordersDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class BordersDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/BordersDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/Default.cshtml index 281f6c73ed..7733f51fdf 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BordersDemo/Default.cshtml @@ -9,11 +9,11 @@ - - - - - + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BreadcrumbsDemo/BreadcrumbsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BreadcrumbsDemo/BreadcrumbsDemoViewComponent.cs index e700acb667..0853f2b195 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BreadcrumbsDemo/BreadcrumbsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/BreadcrumbsDemo/BreadcrumbsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.BreadcrumbsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class BreadcrumbsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/BreadcrumbsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonGroupsDemo/ButtonGroupsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonGroupsDemo/ButtonGroupsDemoViewComponent.cs index ff5b917d40..cb134673ac 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonGroupsDemo/ButtonGroupsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonGroupsDemo/ButtonGroupsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.ButtonGroupsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class ButtonGroupsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/ButtonGroupsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonsDemo/ButtonsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonsDemo/ButtonsDemoViewComponent.cs index 9d7a2dd3f9..5cb6cf0779 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonsDemo/ButtonsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ButtonsDemo/ButtonsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.ButtonsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class ButtonsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/ButtonsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CardsDemo/CardsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CardsDemo/CardsDemoViewComponent.cs index f192285f80..0dafa65c70 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CardsDemo/CardsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CardsDemo/CardsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.CardsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class CardsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/CardsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CarouselDemo/CarouselDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CarouselDemo/CarouselDemoViewComponent.cs index 8ef48c1da5..e57b840269 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CarouselDemo/CarouselDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CarouselDemo/CarouselDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.CarouselDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class CarouselDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/CarouselDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CollapseDemo/CollapseDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CollapseDemo/CollapseDemoViewComponent.cs index 28ccbcb447..44df03d3c4 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CollapseDemo/CollapseDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/CollapseDemo/CollapseDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.CollapseDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class CollapseDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/CollapseDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DropdownsDemo/DropdownsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DropdownsDemo/DropdownsDemoViewComponent.cs index 136ad70481..1eb9be8cf5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DropdownsDemo/DropdownsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DropdownsDemo/DropdownsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.DropdownsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class DropdownsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/DropdownsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/DynamicFormsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/DynamicFormsDemoViewComponent.cs index 3681eda627..e768536a9f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/DynamicFormsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/DynamicFormsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.DynamicFormsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class DynamicFormsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/DynamicFormsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/FormElementsDemo/FormElementsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/FormElementsDemo/FormElementsDemoViewComponent.cs index 01c477ecb1..22bcde1e52 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/FormElementsDemo/FormElementsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/FormElementsDemo/FormElementsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.FormElementsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class FormElementsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/FormElementsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/GridsDemo/GridsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/GridsDemo/GridsDemoViewComponent.cs index 99f99186cd..e5e031afc0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/GridsDemo/GridsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/GridsDemo/GridsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.GridsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class GridsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/GridsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ListGroupsDemo/ListGroupsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ListGroupsDemo/ListGroupsDemoViewComponent.cs index 9e0f89e325..3c48462650 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ListGroupsDemo/ListGroupsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ListGroupsDemo/ListGroupsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.ListGroupsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class ListGroupsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/ListGroupsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ModalsDemo/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ModalsDemo/Default.cshtml index 91ed5b8bff..8b48f579ac 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ModalsDemo/Default.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ModalsDemo/Default.cshtml @@ -5,7 +5,20 @@ Launch modal - + + + + Woohoo, you're reading this text in a modal! + + + + + + + + Launch modal + + Woohoo, you're reading this text in a modal! diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ModalsDemo/ModalsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ModalsDemo/ModalsDemoViewComponent.cs index 272ea4a480..e4c2ce2c5e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ModalsDemo/ModalsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ModalsDemo/ModalsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.ModalsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class ModalsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/ModalsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/NavbarsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/NavbarsDemoViewComponent.cs index e282f1b923..470857a845 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/NavbarsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavbarsDemo/NavbarsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.NavbarsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class NavbarsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/NavbarsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavsDemo/NavsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavsDemo/NavsDemoViewComponent.cs index 5bc0363193..7149894f46 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavsDemo/NavsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/NavsDemo/NavsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.NavsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class NavsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/NavsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PaginatorDemo/PaginatorDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PaginatorDemo/PaginatorDemoViewComponent.cs index 46b8cb2db1..92a0b19ead 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PaginatorDemo/PaginatorDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PaginatorDemo/PaginatorDemoViewComponent.cs @@ -1,10 +1,14 @@ using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.PaginatorDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class PaginatorDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/PaginatorDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PopoversDemo/PopoversDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PopoversDemo/PopoversDemoViewComponent.cs index 6114918165..408676f63d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PopoversDemo/PopoversDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/PopoversDemo/PopoversDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.PopoversDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class PopoversDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/PopoversDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ProgressBarsDemo/ProgressBarsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ProgressBarsDemo/ProgressBarsDemoViewComponent.cs index acaf9b4135..33f1e09e15 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ProgressBarsDemo/ProgressBarsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/ProgressBarsDemo/ProgressBarsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.ProgressBarsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class ProgressBarsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/ProgressBarsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TablesDemo/TablesDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TablesDemo/TablesDemoViewComponent.cs index e40ecae321..181a58f07f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TablesDemo/TablesDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TablesDemo/TablesDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.TablesDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class TablesDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/TablesDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TabsDemo/TabsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TabsDemo/TabsDemoViewComponent.cs index 00f3b5c2c4..e001b5157d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TabsDemo/TabsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TabsDemo/TabsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.TabsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class TabsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/TabsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TooltipsDemo/TooltipsDemoViewComponent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TooltipsDemo/TooltipsDemoViewComponent.cs index a23fede627..7d30f09a79 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TooltipsDemo/TooltipsDemoViewComponent.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/Demos/TooltipsDemo/TooltipsDemoViewComponent.cs @@ -1,9 +1,13 @@ using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.Shared.Demos.TooltipsDemo { - [Widget] + [Widget( + StyleTypes = new []{ typeof(PrismjsStyleBundleContributor) }, + ScriptTypes = new[]{ typeof(PrismjsScriptBundleContributor) } + )] public class TooltipsDemoViewComponent : AbpViewComponent { public const string ViewPath = "/Views/Components/Themes/Shared/Demos/TooltipsDemo/Default.cshtml"; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs index 1f1f71b066..27fc4c4d13 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo/Views/Components/Themes/Shared/TagHelpers/AbpComponentDemoSectionTagHelper.cs @@ -78,7 +78,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S output.PostContent.AppendHtml($"
"); output.PostContent.AppendHtml("
");
-            output.PostContent.AppendHtml("");
+            output.PostContent.AppendHtml("");
             output.PostContent.Append(GetRawDemoSource());
             output.PostContent.AppendHtml("");
             output.PostContent.AppendHtml("
"); @@ -86,7 +86,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Demo.Views.Components.Themes.S output.PostContent.AppendHtml($"
"); output.PostContent.AppendHtml("
");
-            output.PostContent.AppendHtml("");
+            output.PostContent.AppendHtml("");
             output.PostContent.Append(content.GetContent());
             output.PostContent.AppendHtml("");
             output.PostContent.AppendHtml("
"); 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.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs index b456d7abb6..cf8ff42973 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aliyun/Volo/Abp/BlobStoring/Aliyun/AliyunBlobNamingNormalizer.cs @@ -1,46 +1,51 @@ -using System.Text.RegularExpressions; +using System.Globalization; +using System.Text.RegularExpressions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.BlobStoring.Aliyun { public class AliyunBlobNamingNormalizer : IBlobNamingNormalizer, ITransientDependency { /// - /// Container names can contain only letters, numbers, and the dash (-) character - /// they can't start or end with the dash (-) character + /// Container names can contain only letters, numbers, and the dash (-) character + /// they can't start or end with the dash (-) character /// Container names must be from 3 through 63 characters long /// public virtual string NormalizeContainerName(string containerName) { - // All letters in a container name must be lowercase. - containerName = containerName.ToLower(); + using (CultureHelper.Use(CultureInfo.InvariantCulture)) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); - // Container names can contain only letters, numbers, and the dash (-) character. - containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); - // Every dash (-) character must be immediately preceded and followed by a letter or number; - // consecutive dashes are not permitted in container names. - // Container names must start or end with a letter or number - containerName = Regex.Replace(containerName, "-{2,}", "-"); - containerName = Regex.Replace(containerName, "^-", string.Empty); - containerName = Regex.Replace(containerName, "-$", string.Empty); + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); - // Container names must be from 3 through 63 characters long. - if (containerName.Length < 3) - { - var length = containerName.Length; - for (var i = 0; i < 3 - length; i++) + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) { - containerName += "0"; + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } } - } - if (containerName.Length > 63) - { - containerName = containerName.Substring(0, 63); - } + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } - return containerName; + return containerName; + } } public virtual string NormalizeBlobName(string blobName) diff --git a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs index cd8eca021e..3779f9ff70 100644 --- a/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs +++ b/framework/src/Volo.Abp.BlobStoring.Aws/Volo/Abp/BlobStoring/Aws/AwsBlobNamingNormalizer.cs @@ -1,5 +1,7 @@ -using System.Text.RegularExpressions; +using System.Globalization; +using System.Text.RegularExpressions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.BlobStoring.Aws { @@ -10,35 +12,38 @@ namespace Volo.Abp.BlobStoring.Aws /// public virtual string NormalizeContainerName(string containerName) { - // All letters in a container name must be lowercase. - containerName = containerName.ToLower(); + using (CultureHelper.Use(CultureInfo.InvariantCulture)) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); - // Container names can contain only letters, numbers, and the dash (-) character. - containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); - // Every dash (-) character must be immediately preceded and followed by a letter or number; - // consecutive dashes are not permitted in container names. - // Container names must start or end with a letter or number - containerName = Regex.Replace(containerName, "-{2,}", "-"); - containerName = Regex.Replace(containerName, "^-", string.Empty); - containerName = Regex.Replace(containerName, "-$", string.Empty); + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); - // Container names must be from 3 through 63 characters long. - if (containerName.Length < 3) - { - var length = containerName.Length; - for (var i = 0; i < 3 - length; i++) + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) { - containerName += "0"; + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } } - } - if (containerName.Length > 63) - { - containerName = containerName.Substring(0, 63); - } + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } - return containerName; + return containerName; + } } /// diff --git a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobNamingNormalizer.cs index 43b479e08e..767a7b6fea 100644 --- a/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobNamingNormalizer.cs +++ b/framework/src/Volo.Abp.BlobStoring.Azure/Volo/Abp/BlobStoring/Azure/AzureBlobNamingNormalizer.cs @@ -1,5 +1,7 @@ -using System.Text.RegularExpressions; +using System.Globalization; +using System.Text.RegularExpressions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.BlobStoring.Azure { @@ -10,35 +12,38 @@ namespace Volo.Abp.BlobStoring.Azure /// public virtual string NormalizeContainerName(string containerName) { - // All letters in a container name must be lowercase. - containerName = containerName.ToLower(); + using (CultureHelper.Use(CultureInfo.InvariantCulture)) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); - // Container names can contain only letters, numbers, and the dash (-) character. - containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); - // Every dash (-) character must be immediately preceded and followed by a letter or number; - // consecutive dashes are not permitted in container names. - // Container names must start or end with a letter or number - containerName = Regex.Replace(containerName, "-{2,}", "-"); - containerName = Regex.Replace(containerName, "^-", string.Empty); - containerName = Regex.Replace(containerName, "-$", string.Empty); + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); - // Container names must be from 3 through 63 characters long. - if (containerName.Length < 3) - { - var length = containerName.Length; - for (var i = 0; i < 3 - length; i++) + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) { - containerName += "0"; + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } } - } - if (containerName.Length > 63) - { - containerName = containerName.Substring(0, 63); - } + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } - return containerName; + return containerName; + } } /// diff --git a/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobNamingNormalizer.cs index f7d273dca7..94c19b07f8 100644 --- a/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobNamingNormalizer.cs +++ b/framework/src/Volo.Abp.BlobStoring.FileSystem/Volo/Abp/BlobStoring/FileSystem/FileSystemBlobNamingNormalizer.cs @@ -1,8 +1,10 @@ using System; +using System.Globalization; using System.Runtime; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.BlobStoring.FileSystem { @@ -27,15 +29,18 @@ namespace Volo.Abp.BlobStoring.FileSystem protected virtual string Normalize(string fileName) { - var os = _iosPlatformProvider.GetCurrentOSPlatform(); - if (os == OSPlatform.Windows) + using (CultureHelper.Use(CultureInfo.InvariantCulture)) { - // A filename cannot contain any of the following characters: \ / : * ? " < > | - // In order to support the directory included in the blob name, remove / and \ - fileName = Regex.Replace(fileName, "[:\\*\\?\"<>\\|]", string.Empty); - } + var os = _iosPlatformProvider.GetCurrentOSPlatform(); + if (os == OSPlatform.Windows) + { + // A filename cannot contain any of the following characters: \ / : * ? " < > | + // In order to support the directory included in the blob name, remove / and \ + fileName = Regex.Replace(fileName, "[:\\*\\?\"<>\\|]", string.Empty); + } - return fileName; + return fileName; + } } } } diff --git a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobNamingNormalizer.cs b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobNamingNormalizer.cs index d350910105..c4df24f91a 100644 --- a/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobNamingNormalizer.cs +++ b/framework/src/Volo.Abp.BlobStoring.Minio/Volo/Abp/BlobStoring/Minio/MinioBlobNamingNormalizer.cs @@ -1,5 +1,7 @@ -using System.Text.RegularExpressions; +using System.Globalization; +using System.Text.RegularExpressions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.BlobStoring.Minio { @@ -10,35 +12,38 @@ namespace Volo.Abp.BlobStoring.Minio /// public virtual string NormalizeContainerName(string containerName) { - // All letters in a container name must be lowercase. - containerName = containerName.ToLower(); + using (CultureHelper.Use(CultureInfo.InvariantCulture)) + { + // All letters in a container name must be lowercase. + containerName = containerName.ToLower(); - // Container names can contain only letters, numbers, and the dash (-) character. - containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); + // Container names can contain only letters, numbers, and the dash (-) character. + containerName = Regex.Replace(containerName, "[^a-z0-9-]", string.Empty); - // Every dash (-) character must be immediately preceded and followed by a letter or number; - // consecutive dashes are not permitted in container names. - // Container names must start or end with a letter or number - containerName = Regex.Replace(containerName, "-{2,}", "-"); - containerName = Regex.Replace(containerName, "^-", string.Empty); - containerName = Regex.Replace(containerName, "-$", string.Empty); + // Every dash (-) character must be immediately preceded and followed by a letter or number; + // consecutive dashes are not permitted in container names. + // Container names must start or end with a letter or number + containerName = Regex.Replace(containerName, "-{2,}", "-"); + containerName = Regex.Replace(containerName, "^-", string.Empty); + containerName = Regex.Replace(containerName, "-$", string.Empty); - // Container names must be from 3 through 63 characters long. - if (containerName.Length < 3) - { - var length = containerName.Length; - for (var i = 0; i < 3 - length; i++) + // Container names must be from 3 through 63 characters long. + if (containerName.Length < 3) { - containerName += "0"; + var length = containerName.Length; + for (var i = 0; i < 3 - length; i++) + { + containerName += "0"; + } } - } - if (containerName.Length > 63) - { - containerName = containerName.Substring(0, 63); - } + if (containerName.Length > 63) + { + containerName = containerName.Substring(0, 63); + } - return containerName; + return containerName; + } } /// diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj index 40eecfd06a..2dfd66f4ab 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj +++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index a79b52b4f2..29238a3e35 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -36,6 +36,7 @@ namespace Volo.Abp.Cli options.Commands["switch-to-stable"] = typeof(SwitchToStableCommand); options.Commands["switch-to-nightly"] = typeof(SwitchToNightlyCommand); options.Commands["translate"] = typeof(TranslateCommand); + options.Commands["build"] = typeof(BuildCommand); }); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultBuildProjectListSorter.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultBuildProjectListSorter.cs new file mode 100644 index 0000000000..6df892b472 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultBuildProjectListSorter.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class DefaultBuildProjectListSorter : IBuildProjectListSorter, ITransientDependency + { + public List SortByDependencies( + List source, + IEqualityComparer comparer = null) + { + /* See: http://www.codeproject.com/Articles/869059/Topological-sorting-in-Csharp + * http://en.wikipedia.org/wiki/Topological_sorting + */ + + var sorted = new List(); + var visited = new Dictionary(comparer); + + foreach (var item in source) + { + SortByDependenciesVisit(source, item, sorted, visited); + } + + return sorted; + } + + private void SortByDependenciesVisit( + List source, + DotNetProjectInfo item, + List sorted, + Dictionary visited) + { + bool inProcess; + var alreadyVisited = visited.TryGetValue(item, out inProcess); + + if (alreadyVisited) + { + if (inProcess) + { + throw new ArgumentException("Cyclic dependency found! Item: " + item); + } + } + else + { + visited[item] = true; + + var dependencies = item.Dependencies; + if (dependencies != null) + { + foreach (var dependency in dependencies) + { + var dependencyItem = source.FirstOrDefault(e => e.CsProjPath == dependency.CsProjPath); + if (dependencyItem != null) + { + SortByDependenciesVisit(source, dependencyItem, sorted, visited); + } + } + } + + visited[item] = false; + sorted.Add(item); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultBuildStatusGenerator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultBuildStatusGenerator.cs new file mode 100644 index 0000000000..e57c6837c0 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultBuildStatusGenerator.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LibGit2Sharp; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class DefaultBuildStatusGenerator : IBuildStatusGenerator, ITransientDependency + { + private readonly IGitRepositoryHelper _gitRepositoryHelper; + + public DefaultBuildStatusGenerator(IGitRepositoryHelper gitRepositoryHelper) + { + _gitRepositoryHelper = gitRepositoryHelper; + } + + public GitRepositoryBuildStatus Generate( + DotNetProjectBuildConfig buildConfig, + List changedProjects, + List buildSucceededProjects) + { + var lastCommitId = _gitRepositoryHelper.GetLastCommitId(buildConfig.GitRepository); + var repoFriendlyName = _gitRepositoryHelper.GetFriendlyName(buildConfig.GitRepository); + + var status = new GitRepositoryBuildStatus( + buildConfig.GitRepository.Name, + repoFriendlyName + ); + + if (ShouldUpdateRepositoryCommitId(buildConfig, changedProjects, buildSucceededProjects)) + { + status.CommitId = lastCommitId; + } + + status.SucceedProjects = changedProjects.Where(p => + p.RepositoryName == buildConfig.GitRepository.Name && + buildSucceededProjects.Contains(p.CsProjPath) + ) + .Select(e => new DotNetProjectBuildStatus + { + CsProjPath = e.CsProjPath, + CommitId = lastCommitId + }).ToList(); + + foreach (var dependingRepository in buildConfig.GitRepository.DependingRepositories) + { + GenerateBuildStatusInternal( + buildConfig, + dependingRepository, + changedProjects, + buildSucceededProjects, + status + ); + } + + return status; + } + + private bool ShouldUpdateRepositoryCommitId( + DotNetProjectBuildConfig buildConfig, + List changedProjects, + List buildSucceededProjects) + { + if (!buildConfig.SlFilePath.IsNullOrEmpty()) + { + return false; + } + + if (changedProjects.Count == 0 || buildSucceededProjects.Count == 0) + { + return false; + } + + return changedProjects.Count == buildSucceededProjects.Count; + } + + private void GenerateBuildStatusInternal( + DotNetProjectBuildConfig buildConfig, + GitRepository gitRepository, + List changedProjects, + List buildSucceededProjects, + GitRepositoryBuildStatus status) + { + var lastCommitId = _gitRepositoryHelper.GetLastCommitId(gitRepository); + var repoFriendlyName = _gitRepositoryHelper.GetFriendlyName(gitRepository); + + var dependingRepositoryStatus = new GitRepositoryBuildStatus( + gitRepository.Name, + repoFriendlyName + ); + + if (ShouldUpdateRepositoryCommitId(buildConfig, changedProjects, buildSucceededProjects)) + { + dependingRepositoryStatus.CommitId = lastCommitId; + } + + dependingRepositoryStatus.SucceedProjects = changedProjects.Where(p => + p.RepositoryName == gitRepository.Name && + buildSucceededProjects.Contains(p.CsProjPath) + ) + .Select(e => new DotNetProjectBuildStatus() + { + CsProjPath = e.CsProjPath, + CommitId = lastCommitId + }).ToList(); + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + GenerateBuildStatusInternal( + buildConfig, + dependingRepository, + changedProjects, + buildSucceededProjects, + dependingRepositoryStatus + ); + } + + status.DependingRepositories.Add(dependingRepositoryStatus); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultChangedProjectFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultChangedProjectFinder.cs new file mode 100644 index 0000000000..e866c425b5 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultChangedProjectFinder.cs @@ -0,0 +1,352 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using LibGit2Sharp; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class DefaultChangedProjectFinder : IChangedProjectFinder, ITransientDependency + { + private readonly IRepositoryBuildStatusStore _repositoryBuildStatusStore; + private readonly IGitRepositoryHelper _gitRepositoryHelper; + + private readonly IDotNetProjectDependencyFiller _dotNetProjectDependencyFiller; + private readonly IBuildProjectListSorter _buildProjectListSorter; + + private readonly List _changeDetectionFileExtensions = new List + { + ".cs", + ".csproj", + ".cshtml" + }; + + public DefaultChangedProjectFinder( + IRepositoryBuildStatusStore repositoryBuildStatusStore, + IGitRepositoryHelper gitRepositoryHelper, + IDotNetProjectDependencyFiller dotNetProjectDependencyFiller, + IBuildProjectListSorter buildProjectListSorter) + { + _repositoryBuildStatusStore = repositoryBuildStatusStore; + _gitRepositoryHelper = gitRepositoryHelper; + _dotNetProjectDependencyFiller = dotNetProjectDependencyFiller; + _buildProjectListSorter = buildProjectListSorter; + } + + public List Find(DotNetProjectBuildConfig buildConfig) + { + if (!buildConfig.SlFilePath.IsNullOrEmpty()) + { + return FindBySlnFile(buildConfig); + } + + return FindByRepository(buildConfig); + } + + /// + /// Returns list of projects in a repository and its depending repositories sorted by dependencies + /// + /// + /// + private List FindAllProjects(DotNetProjectBuildConfig buildConfig) + { + var projects = new List(); + + AddProjectsOfRepository(buildConfig.GitRepository, projects); + + _dotNetProjectDependencyFiller.Fill(projects); + + var allSortedProjectList = _buildProjectListSorter.SortByDependencies( + projects, + new DotNetProjectInfoEqualityComparer() + ); + + FilterIgnoredDirectories(allSortedProjectList, buildConfig.GitRepository); + + return allSortedProjectList; + } + + private void FilterIgnoredDirectories(List projects, GitRepository gitRepository) + { + foreach (var ignoredDirectory in gitRepository.IgnoredDirectories) + { + projects = projects.Where(e => + !e.CsProjPath.StartsWith(Path.Combine(gitRepository.RootPath, ignoredDirectory))) + .ToList(); + } + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + FilterIgnoredDirectories(projects, dependingRepository); + } + } + + private void AddProjectsOfRepository(GitRepository gitRepository, List projects) + { + var allCsProjFiles = Directory.GetFiles( + gitRepository.RootPath, + "*.csproj", + SearchOption.AllDirectories + ).ToList(); + + projects.AddRange( + allCsProjFiles.Select(csProjPath => new DotNetProjectInfo(gitRepository.Name, csProjPath, false)) + ); + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + AddProjectsOfRepository(dependingRepository, projects); + } + } + + private List FindByRepository(DotNetProjectBuildConfig buildConfig) + { + var gitRepositoryBuildStatus = _repositoryBuildStatusStore.Get( + buildConfig.BuildName, + buildConfig.GitRepository + ); + + var allSortedProjectList = FindAllProjects(buildConfig); + + MarkProjectsForBuild( + buildConfig.GitRepository, + gitRepositoryBuildStatus, + buildConfig.ForceBuild, + allSortedProjectList + ); + + return allSortedProjectList.Where(e => e.ShouldBuild).ToList(); + } + + private void MarkProjectsForBuild( + GitRepository repository, + GitRepositoryBuildStatus repositoryBuildStatus, + bool forceBuild, + List allProjectList) + { + if (forceBuild || repositoryBuildStatus == null || repositoryBuildStatus.CommitId.IsNullOrEmpty()) + { + // Mark all projects for build + allProjectList.ForEach(e => e.ShouldBuild = true); + } + else + { + MarkChangedProjectsForBuild( + repository, + repositoryBuildStatus, + allProjectList + ); + } + + if (!repository.DependingRepositories.Any()) + { + return; + } + + foreach (var dependingRepository in repository.DependingRepositories) + { + var dependingRepositoryBuildStatus = repositoryBuildStatus?.GetChild(dependingRepository.Name); + MarkProjectsForBuild( + dependingRepository, + dependingRepositoryBuildStatus, + forceBuild, + allProjectList + ); + } + } + + private void MarkChangedProjectsForBuild( + GitRepository repository, + GitRepositoryBuildStatus status, + List allProjectList) + { + using (var repo = new Repository(string.Concat(repository.RootPath, @"\.git"))) + { + var firstCommit = status.CommitId.IsNullOrEmpty() + ? null + : repo.Lookup(status.CommitId); + + var repoDifferences = repo.Diff.Compare(firstCommit?.Tree, repo.Head.Tip.Tree); + + var fileExtensionPredicate = PredicateBuilder.New(true); + + foreach (var changeDetectionFileExtension in _changeDetectionFileExtensions) + { + fileExtensionPredicate = fileExtensionPredicate.Or( + e => e.Path.EndsWith(changeDetectionFileExtension) + ); + } + + var files = repoDifferences + .Where(fileExtensionPredicate) + .Where(e => e.Status != ChangeKind.Deleted) + .Select(e => e) + .ToList(); + + var affectedCsProjFiles = FindAffectedCsProjFiles(repository.RootPath, files); + var lastCommitId = _gitRepositoryHelper.GetLastCommitId(repository); + + foreach (var file in affectedCsProjFiles) + { + var csProjPath = Path.Combine(repository.RootPath, file); + if (status.SucceedProjects.Any(p => p.CsProjPath == csProjPath && p.CommitId == lastCommitId)) + { + continue; + } + + // Filter ignored directories + var isIgnored = repository.IgnoredDirectories.Any(ignoredDirectory => + csProjPath.StartsWith(Path.Combine(repository.RootPath, ignoredDirectory))); + if (isIgnored) + { + continue; + } + + allProjectList.MarkForBuild(repository.Name, csProjPath); + AddDependingProjectsToList(repository.Name, csProjPath, allProjectList); + } + } + } + + private List FindBySlnFile(DotNetProjectBuildConfig buildConfig) + { + var allProjectList = FindAllProjects(buildConfig); + + var slFine = new FileInfo(buildConfig.SlFilePath); + var csProjFiles = slFine.Directory.GetFiles( + "*.csproj", + SearchOption.AllDirectories + ).Select(e => e.FullName) + .ToList(); + + foreach (var csProjFile in csProjFiles) + { + MarkDependantProjectsForBuild(buildConfig.GitRepository, csProjFile, allProjectList); + } + + return _buildProjectListSorter.SortByDependencies( + allProjectList, + new DotNetProjectInfoEqualityComparer() + ).Where(e => e.ShouldBuild).ToList(); + } + + private void MarkDependantProjectsForBuild( + GitRepository gitRepository, + string csProjFilePath, + List allProjectList) + { + var repositoryName = gitRepository.FindRepositoryOf(csProjFilePath); + var project = new DotNetProjectInfo(repositoryName, csProjFilePath, true); + + if (allProjectList.IsMarkedForBuild(repositoryName, csProjFilePath)) + { + return; + } + + allProjectList.MarkForBuild(project); + AddProjectDependencies(gitRepository, project, allProjectList); + } + + private void AddProjectDependencies( + GitRepository gitRepository, + DotNetProjectInfo project, + List allProjectList) + { + var projectInfo = allProjectList.FirstOrDefault(e => e.CsProjPath == project.CsProjPath); + if (projectInfo == null) + { + return; + } + + var dependencies = projectInfo.Dependencies; + + foreach (var dependency in dependencies) + { + if (allProjectList.IsMarkedForBuild(dependency.RepositoryName, dependency.CsProjPath)) + { + continue; + } + + allProjectList.MarkForBuild(dependency.RepositoryName, dependency.CsProjPath); + + AddProjectDependencies(gitRepository, dependency, allProjectList); + } + } + + private void AddDependingProjectsToList( + string repositoryName, + string csProjPath, + List allProjectList) + { + var dependingProjects = allProjectList.Where( + e => e.Dependencies.Any(d => d.RepositoryName == repositoryName && d.CsProjPath == csProjPath) + ).Select(e => new DotNetProjectInfo(e.RepositoryName, e.CsProjPath, true)).ToList(); + + if (!dependingProjects.Any()) + { + return; + } + + foreach (var dependingProject in dependingProjects) + { + if (allProjectList.IsMarkedForBuild(dependingProject)) + { + continue; + } + + allProjectList.MarkForBuild(dependingProject); + AddDependingProjectsToList(dependingProject.RepositoryName, dependingProject.CsProjPath, + allProjectList); + } + } + + private List FindAffectedCsProjFiles(string repositoryPath, List files) + { + var affectedProjectFiles = new List(); + foreach (var file in files) + { + var filePath = Path.Combine(repositoryPath, file.Path); + if (filePath.EndsWith(".csproj")) + { + affectedProjectFiles.Add(filePath); + } + + if (!filePath.EndsWith(".cs")) + { + continue; + } + + var classFile = new FileInfo(filePath); + var csProjPath = FindBelongingProjectPathOfClass(classFile.Directory?.FullName); + if (csProjPath.IsNullOrEmpty() || affectedProjectFiles.Contains(csProjPath)) + { + continue; + } + + affectedProjectFiles.Add(csProjPath); + } + + return affectedProjectFiles; + } + + private string FindBelongingProjectPathOfClass(string directoryPath) + { + var files = Directory.GetFiles(directoryPath, "*.csproj", SearchOption.TopDirectoryOnly); + if (files.Length == 1) + { + return files.First(); + } + + var directoryInfo = new DirectoryInfo(directoryPath); + + if (directoryInfo.Parent == null) + { + return null; + } + + return FindBelongingProjectPathOfClass(directoryInfo.Parent.FullName); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultDotNetProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultDotNetProjectBuilder.cs new file mode 100644 index 0000000000..65749fd45c --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DefaultDotNetProjectBuilder.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class DefaultDotNetProjectBuilder : IDotNetProjectBuilder, ITransientDependency + { + public List Build(List projects, string arguments) + { + var builtProjects = new ConcurrentBag(); + var totalProjectCountToBuild = projects.Count; + var buildingProjectIndex = 0; + + try + { + foreach (var project in projects) + { + if (builtProjects.Contains(project.CsProjPath)) + { + continue; + } + + buildingProjectIndex++; + + Console.WriteLine( + "Building....: " + " (" + buildingProjectIndex + "/" + + totalProjectCountToBuild + ")" + project.CsProjPath + ); + + BuildInternal(project, arguments, builtProjects); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + + return builtProjects.ToList(); + } + + private void BuildInternal(DotNetProjectInfo project, string arguments, ConcurrentBag builtProjects) + { + var buildArguments = arguments.TrimStart('"').TrimEnd('"'); + Console.WriteLine("Executing...: dotnet build " + project.CsProjPath + " " + buildArguments); + + var output = CmdHelper.RunCmdAndGetOutput( + "dotnet build " + project.CsProjPath + " " + buildArguments, + out int buildStatus + ); + + if (buildStatus == 0) + { + builtProjects.Add(project.CsProjPath); + WriteOutput(output, ConsoleColor.Green); + } + else + { + WriteOutput(output, ConsoleColor.Red); + Console.WriteLine("Build failed for :" + project.CsProjPath); + throw new Exception("Build failed!"); + } + } + + private void WriteOutput(string text, ConsoleColor color) + { + var currentConsoleColor = Console.ForegroundColor; + Console.ForegroundColor = color; + Console.WriteLine(text); + Console.ForegroundColor = currentConsoleColor; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildConfig.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildConfig.cs new file mode 100644 index 0000000000..9a9da0766a --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildConfig.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.Cli.Build +{ + public class DotNetProjectBuildConfig + { + public string BuildName { get; set; } + + public string SlFilePath { get; set; } + + public GitRepository GitRepository { get; set; } + + public bool ForceBuild { get; set; } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildStatus.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildStatus.cs new file mode 100644 index 0000000000..10092777c2 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectBuildStatus.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Cli.Build +{ + public class DotNetProjectBuildStatus + { + public string CsProjPath { get; set; } + + public string CommitId { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectDependencyFiller.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectDependencyFiller.cs new file mode 100644 index 0000000000..b9c883a45d --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectDependencyFiller.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class DotNetProjectDependencyFiller : IDotNetProjectDependencyFiller, ITransientDependency + { + public void Fill(List projects) + { + foreach (var project in projects) + { + FillProjectDependencies(project); + } + } + + private void FillProjectDependencies(DotNetProjectInfo project) + { + var projectNode = XElement.Load(project.CsProjPath); + var referenceNodes = projectNode.Descendants("ItemGroup").Descendants("ProjectReference"); + + foreach (var referenceNode in referenceNodes) + { + if (referenceNode.Attribute("Include") == null) + { + continue; + } + + var relativePath = referenceNode.Attribute("Include").Value; + var file = new FileInfo(project.CsProjPath); + var referenceProjectInfo = new FileInfo(Path.Combine(file.Directory.FullName, relativePath)); + + var referenceProject = new DotNetProjectInfo(project.RepositoryName, referenceProjectInfo.FullName, false); + project.Dependencies.Add(referenceProject); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfo.cs new file mode 100644 index 0000000000..3a8fe825ca --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfo.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + [Serializable] + public class DotNetProjectInfo + { + public string RepositoryName { get; set; } + + public string CsProjPath { get; set; } + + public bool ShouldBuild { get; set; } + + public List Dependencies { get; set; } + + public DotNetProjectInfo(string repositoryName, string csProjPath, bool shouldBuild) + { + RepositoryName = repositoryName; + CsProjPath = csProjPath; + ShouldBuild = shouldBuild; + Dependencies = new List(); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfoEqualityComparer.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfoEqualityComparer.cs new file mode 100644 index 0000000000..c6f8e5dd53 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfoEqualityComparer.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public class DotNetProjectInfoEqualityComparer : EqualityComparer + { + public override bool Equals(DotNetProjectInfo x, DotNetProjectInfo y) + { + return (x == null && y == null) || (x != null && y != null && x.CsProjPath == y.CsProjPath); + } + + public override int GetHashCode(DotNetProjectInfo obj) + { + return obj == null ? 0 : obj.CsProjPath.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfoExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfoExtensions.cs new file mode 100644 index 0000000000..ace85e5257 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/DotNetProjectInfoExtensions.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.Cli.Build +{ + public static class DotNetProjectInfoExtensions + { + public static void MarkForBuild(this List projects, string repositoryName, string csProjPath) + { + var project = projects.FirstOrDefault(e => + e.RepositoryName == repositoryName && e.CsProjPath == csProjPath + ); + + if (project == null) + { + return; + } + + project.ShouldBuild = true; + } + + public static void MarkForBuild(this List projects, DotNetProjectInfo project) + { + projects.MarkForBuild(project.RepositoryName, project.CsProjPath); + } + + public static bool IsMarkedForBuild(this List projects, string repositoryName, + string csProjPath) + { + var project = projects.FirstOrDefault(e => + e.RepositoryName == repositoryName && e.CsProjPath == csProjPath + ); + + if (project == null) + { + return false; + } + + return project.ShouldBuild; + } + + public static bool IsMarkedForBuild(this List projects, DotNetProjectInfo project) + { + return projects.IsMarkedForBuild(project.RepositoryName, project.CsProjPath); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/FileSystemDotNetProjectBuildConfigReader.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/FileSystemDotNetProjectBuildConfigReader.cs new file mode 100644 index 0000000000..5aed3d0941 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/FileSystemDotNetProjectBuildConfigReader.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace Volo.Abp.Cli.Build +{ + public class FileSystemDotNetProjectBuildConfigReader : IDotNetProjectBuildConfigReader, ITransientDependency + { + private readonly IJsonSerializer _jsonSerializer; + private string _buildConfigName = "abp-build-config.json"; + + public FileSystemDotNetProjectBuildConfigReader(IJsonSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + } + + public DotNetProjectBuildConfig Read(string directoryPath) + { + var buildConfig = new DotNetProjectBuildConfig(); + var solutionFiles = Directory.GetFiles(directoryPath, "*.sln", SearchOption.TopDirectoryOnly); + if (solutionFiles.Length == 1) + { + buildConfig.SlFilePath = solutionFiles.First(); + var configFile = GetClosestFile(directoryPath, _buildConfigName); + var configFileContent = File.ReadAllText(configFile); + buildConfig.GitRepository = _jsonSerializer.Deserialize(configFileContent); + + SetBranchNames(buildConfig.GitRepository); + + return buildConfig; + } + + var configFiles = Directory.GetFiles(directoryPath, _buildConfigName, SearchOption.TopDirectoryOnly); + if (configFiles.Length == 1) + { + var configFile = configFiles.First(); + var configFileContent = File.ReadAllText(configFile); + buildConfig.GitRepository = _jsonSerializer.Deserialize(configFileContent); + + SetBranchNames(buildConfig.GitRepository); + + return buildConfig; + } + + Console.WriteLine( + "There are more than 1 config (abp-build-config.json) file in the directory!" + ); + + throw new Exception("There is no solution file (*.sln) or " + _buildConfigName + " in the working directory !"); + } + + private void SetBranchNames(GitRepository gitRepository) + { + using (var repo = new Repository(string.Concat(gitRepository.RootPath, @"\.git"))) + { + gitRepository.BranchName = repo.Head.FriendlyName; + } + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + SetBranchNames(dependingRepository); + } + } + + private string GetClosestFile(string directoryPath, string fileName) + { + var directory = new DirectoryInfo(directoryPath); + var files = directory.GetFiles(fileName, SearchOption.TopDirectoryOnly); + if (files.Any() && files.Length == 1) + { + return files.First().FullName; + } + + do + { + directory = directory.Parent; + if (directory == null) + { + return string.Empty; + } + + files = directory.GetFiles(fileName, SearchOption.TopDirectoryOnly); + if (files.Any() && files.Length == 1) + { + return files.First().FullName; + } + } while (true); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/FileSystemRepositoryBuildStatusStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/FileSystemRepositoryBuildStatusStore.cs new file mode 100644 index 0000000000..0f6b2f19a7 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/FileSystemRepositoryBuildStatusStore.cs @@ -0,0 +1,61 @@ +using System.IO; +using Newtonsoft.Json; +using Volo.Abp.DependencyInjection; +using Volo.Abp.IO; + +namespace Volo.Abp.Cli.Build +{ + public class FileSystemRepositoryBuildStatusStore : IRepositoryBuildStatusStore, ITransientDependency + { + public GitRepositoryBuildStatus Get(string buildNamePrefix, GitRepository repository) + { + if (!Directory.Exists(CliPaths.Build)) + { + Directory.CreateDirectory(CliPaths.Build); + } + + var buildStatusFile = Path.Combine(CliPaths.Build, repository.GetUniqueName(buildNamePrefix)) + + ".json"; + + if (!File.Exists(buildStatusFile)) + { + return null; + } + + var buildStatusText = File.ReadAllText(buildStatusFile); + return JsonConvert.DeserializeObject(buildStatusText); + } + + public void Set(string buildNamePrefix, GitRepository repository, GitRepositoryBuildStatus status) + { + var existingRepositoryStatus = Get(buildNamePrefix, repository); + + var buildStatusFile = Path.Combine( + CliPaths.Build, + status.GetUniqueName(buildNamePrefix) + ) + ".json"; + + if (File.Exists(buildStatusFile)) + { + FileHelper.DeleteIfExists(buildStatusFile); + } + + if (existingRepositoryStatus != null) + { + existingRepositoryStatus.MergeWith(status); + + using (var file = File.CreateText(buildStatusFile)) + { + new JsonSerializer {Formatting = Formatting.Indented}.Serialize(file, existingRepositoryStatus); + } + } + else + { + using (var file = File.CreateText(buildStatusFile)) + { + new JsonSerializer {Formatting = Formatting.Indented}.Serialize(file, status); + } + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepository.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepository.cs new file mode 100644 index 0000000000..eb1acafd82 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepository.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + /// + /// Represents a source code repository + /// + public class GitRepository + { + /// + /// Name of the repository + /// + public string Name { get; set; } + + /// + /// Branch of the repository + /// + public string BranchName { get; set; } + + /// + /// Root path of the repository which contains .git folder + /// + public string RootPath { get; set; } + + public List DependingRepositories { get; set; } + + public List IgnoredDirectories { get; set; } + + public GitRepository(string name, string branchName, string rootPath) + { + Name = name; + BranchName = branchName; + RootPath = rootPath; + DependingRepositories = new List(); + IgnoredDirectories = new List(); + } + + public string GetUniqueName(string prefix) + { + var name = Name + "_" + BranchName; + foreach (var dependingRepository in DependingRepositories) + { + AddToUniqueName(dependingRepository, name); + } + + return (prefix.IsNullOrEmpty() ? "" : prefix + "_") + name.ToMd5(); + } + + private void AddToUniqueName(GitRepository gitRepository, string name) + { + name += "_" + gitRepository.Name + "_" + gitRepository.BranchName; + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + AddToUniqueName(dependingRepository, name); + } + } + + public string FindRepositoryOf(string csProjFilePath) + { + if (csProjFilePath.StartsWith(RootPath)) + { + return Name; + } + + foreach (var dependingRepository in DependingRepositories) + { + var name = FindRepositoryOfInternal(dependingRepository, csProjFilePath); + if (!string.IsNullOrEmpty(name)) + { + return name; + } + } + + return null; + } + + private string FindRepositoryOfInternal(GitRepository repository, string csProjFilePath) + { + if (csProjFilePath.StartsWith(repository.RootPath)) + { + return repository.Name; + } + + foreach (var dependingRepository in repository.DependingRepositories) + { + return FindRepositoryOfInternal(dependingRepository, csProjFilePath); + } + + return null; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryBuildStatus.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryBuildStatus.cs new file mode 100644 index 0000000000..1a60a04068 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryBuildStatus.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.Cli.Build +{ + public class GitRepositoryBuildStatus + { + /// + /// Name of the repository + /// + public string RepositoryName { get; set; } + + /// + /// Branch of the repository + /// + public string BranchName { get; set; } + + /// + /// Last succeeded commitId of the repository + /// + public string CommitId { get; set; } + + /// + /// Build succeeded projects of this repository + /// + public List SucceedProjects { get; set; } + + /// + /// Build status of depending repositories + /// + public List DependingRepositories { get; set; } + + public GitRepositoryBuildStatus(string repositoryName, string branchName) + { + RepositoryName = repositoryName; + BranchName = branchName; + SucceedProjects = new List(); + DependingRepositories = new List(); + } + + public GitRepositoryBuildStatus GetSelfOrChild(string repositoryName) + { + if (RepositoryName == repositoryName) + { + return this; + } + + return GetChild(repositoryName); + } + + public GitRepositoryBuildStatus GetChild(string repositoryName) + { + foreach (var dependingRepository in DependingRepositories) + { + var child = GetChildInternal(dependingRepository, repositoryName); + if (child != null) + { + return child; + } + } + + return null; + } + + public string GetUniqueName(string prefix) + { + var name = RepositoryName + "_" + BranchName; + foreach (var dependingRepository in DependingRepositories) + { + AddToUniqueName(dependingRepository, name); + } + + return (prefix.IsNullOrEmpty() ? "" : prefix + "_") + name.ToMd5(); + } + + public void AddOrUpdateProjectStatus(DotNetProjectBuildStatus status) + { + var existingProjectStatus = SucceedProjects.FirstOrDefault(p => p.CsProjPath == status.CsProjPath); + if (existingProjectStatus != null) + { + existingProjectStatus.CommitId = status.CommitId; + } + else + { + SucceedProjects.Add(status); + } + } + + public void MergeWith(GitRepositoryBuildStatus newBuildStatus) + { + if (!newBuildStatus.CommitId.IsNullOrEmpty()) + { + CommitId = newBuildStatus.CommitId; + } + + foreach (var succeedProject in newBuildStatus.SucceedProjects) + { + AddOrUpdateProjectStatus(succeedProject); + } + + foreach (var dependingRepositoryBuildStatus in newBuildStatus.DependingRepositories) + { + var existingDependingRepositoryBuildStatus = GetChild(dependingRepositoryBuildStatus.RepositoryName); + var newDependingRepositoryBuildStatus = newBuildStatus.GetChild( + dependingRepositoryBuildStatus.RepositoryName + ); + + existingDependingRepositoryBuildStatus.MergeWith(newDependingRepositoryBuildStatus); + } + } + + private GitRepositoryBuildStatus GetChildInternal(GitRepositoryBuildStatus repositoryBuildStatus, + string repositoryName) + { + if (repositoryBuildStatus.RepositoryName == repositoryName) + { + return repositoryBuildStatus; + } + + foreach (var dependingRepository in repositoryBuildStatus.DependingRepositories) + { + var child = GetChildInternal(dependingRepository, repositoryName); + if (child != null) + { + return child; + } + } + + return null; + } + + private void AddToUniqueName(GitRepositoryBuildStatus gitRepository, string name) + { + name += "_" + gitRepository.RepositoryName + "_" + gitRepository.BranchName; + + foreach (var dependingRepository in gitRepository.DependingRepositories) + { + AddToUniqueName(dependingRepository, name); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryHelper.cs new file mode 100644 index 0000000000..51c84f0f61 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/GitRepositoryHelper.cs @@ -0,0 +1,24 @@ +using LibGit2Sharp; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Build +{ + public class GitRepositoryHelper : IGitRepositoryHelper, ITransientDependency + { + public string GetLastCommitId(GitRepository repository) + { + using (var repo = new Repository(string.Concat(repository.RootPath, @"\.git"))) + { + return repo.Head.Tip.Id.ToString(); + } + } + + public string GetFriendlyName(GitRepository repository) + { + using (var repo = new Repository(string.Concat(repository.RootPath, @"\.git"))) + { + return repo.Head.FriendlyName; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildProjectListSorter.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildProjectListSorter.cs new file mode 100644 index 0000000000..ed97ca27c8 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildProjectListSorter.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public interface IBuildProjectListSorter + { + List SortByDependencies( + List source, + IEqualityComparer comparer = null); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildStatusGenerator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildStatusGenerator.cs new file mode 100644 index 0000000000..f9cf0b8254 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IBuildStatusGenerator.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public interface IBuildStatusGenerator + { + GitRepositoryBuildStatus Generate( + DotNetProjectBuildConfig buildConfig, + List changedProjects, + List buildSucceededProjects + ); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IChangedProjectFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IChangedProjectFinder.cs new file mode 100644 index 0000000000..b381a073b4 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IChangedProjectFinder.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public interface IChangedProjectFinder + { + List Find(DotNetProjectBuildConfig buildConfig); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuildConfigReader.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuildConfigReader.cs new file mode 100644 index 0000000000..54dd2bd5ac --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuildConfigReader.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Cli.Build +{ + public interface IDotNetProjectBuildConfigReader + { + DotNetProjectBuildConfig Read(string directoryPath); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuilder.cs new file mode 100644 index 0000000000..02f753c325 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectBuilder.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public interface IDotNetProjectBuilder + { + List Build(List projects, string arguments); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectDependencyFiller.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectDependencyFiller.cs new file mode 100644 index 0000000000..cc1ba65bc7 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IDotNetProjectDependencyFiller.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.Build +{ + public interface IDotNetProjectDependencyFiller + { + void Fill(List projects); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IGitRepositoryHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IGitRepositoryHelper.cs new file mode 100644 index 0000000000..a1402c7dda --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IGitRepositoryHelper.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Cli.Build +{ + public interface IGitRepositoryHelper + { + string GetLastCommitId(GitRepository repository); + + string GetFriendlyName(GitRepository repository); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IRepositoryBuildStatusStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IRepositoryBuildStatusStore.cs new file mode 100644 index 0000000000..99be400ec8 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Build/IRepositoryBuildStatusStore.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Cli.Build +{ + public interface IRepositoryBuildStatusStore + { + GitRepositoryBuildStatus Get(string buildNamePrefix, GitRepository repository); + + void Set(string buildNamePrefix, GitRepository repository, GitRepositoryBuildStatus status); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs index eb6339b9da..2b422446ca 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs @@ -9,7 +9,8 @@ namespace Volo.Abp.Cli public static string Log => Path.Combine(AbpRootPath, "cli", "logs"); public static string Root => Path.Combine(AbpRootPath, "cli"); public static string AccessToken => Path.Combine(AbpRootPath, "cli", "access-token.bin"); - + public static string Build => Path.Combine(AbpRootPath, "build"); + private static readonly string AbpRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".abp"); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs new file mode 100644 index 0000000000..6d28b39fad --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/BuildCommand.cs @@ -0,0 +1,132 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Build; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class BuildCommand : IConsoleCommand, ITransientDependency + { + public IDotNetProjectDependencyFiller DotNetProjectDependencyFiller { get; set; } + + public IChangedProjectFinder ChangedProjectFinder { get; set; } + + public IDotNetProjectBuilder DotNetProjectBuilder { get; set; } + + public IRepositoryBuildStatusStore RepositoryBuildStatusStore { get; set; } + + public IDotNetProjectBuildConfigReader DotNetProjectBuildConfigReader { get; set; } + + public IBuildStatusGenerator BuildStatusGenerator { get; set; } + + public IBuildProjectListSorter BuildProjectListSorter { get; set; } + + public Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + var sw = new Stopwatch(); + sw.Start(); + + var workingDirectory = commandLineArgs.Options.GetOrNull( + Options.WorkingDirectory.Short, + Options.WorkingDirectory.Long + ); + + var dotnetBuildArguments = commandLineArgs.Options.GetOrNull( + Options.DotnetBuildArguments.Short, + Options.DotnetBuildArguments.Long + ); + + var buildName = commandLineArgs.Options.GetOrNull( + Options.BuildName.Short, + Options.BuildName.Long + ); + + var forceBuild = commandLineArgs.Options.ContainsKey(Options.ForceBuild.Short) || + commandLineArgs.Options.ContainsKey(Options.ForceBuild.Long); + + var buildConfig = DotNetProjectBuildConfigReader.Read(workingDirectory ?? Directory.GetCurrentDirectory()); + buildConfig.BuildName = buildName; + buildConfig.ForceBuild = forceBuild; + + Console.WriteLine("Finding changed projects..."); + + var changedProjectFiles = ChangedProjectFinder.Find(buildConfig); + + var buildSucceededProjects = DotNetProjectBuilder.Build( + changedProjectFiles, + dotnetBuildArguments ?? "" + ); + + var buildStatus = BuildStatusGenerator.Generate( + buildConfig, + changedProjectFiles, + buildSucceededProjects + ); + + RepositoryBuildStatusStore.Set(buildName, buildConfig.GitRepository, buildStatus); + + sw.Stop(); + Console.WriteLine("Build operation is completed in " + sw.ElapsedMilliseconds + " (ms)"); + + return Task.CompletedTask; + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine(" abp build [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + sb.AppendLine("-wd|--working-directory (default: empty)"); + sb.AppendLine("-m |--max-parallel-builds (default: 1)"); + sb.AppendLine("-a |--dotnet-build-arguments (default: empty)"); + sb.AppendLine("-n |--build-name (default: empty)"); + sb.AppendLine("-f | --force (default: false)"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Builds a dotnet repository and dependent repositories or a solution."; + } + + public static class Options + { + public static class WorkingDirectory + { + public const string Short = "wd"; + public const string Long = "working-directory"; + } + + public static class DotnetBuildArguments + { + public const string Short = "a"; + public const string Long = "dotnet-build-arguments"; + } + + public static class BuildName + { + public const string Short = "n"; + public const string Long = "build-name"; + } + + public static class ForceBuild + { + public const string Short = "f"; + public const string Long = "force"; + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index d5ece4361f..39ecdb8183 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -286,6 +286,8 @@ namespace Volo.Abp.Cli.Commands return UiFramework.Mvc; case "angular": return UiFramework.Angular; + case "blazor": + return UiFramework.Blazor; default: return UiFramework.NotSpecified; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs index 46d2b77f95..ad5da58a8e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs @@ -19,13 +19,14 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building pipeline.Steps.Add(new TemplateCodeDeleteStep()); pipeline.Steps.Add(new SolutionRenameStep()); - if (context.Template.Name == AppProTemplate.TemplateName || + if (context.Template.Name == AppProTemplate.TemplateName || context.Template.Name == ModuleProTemplate.TemplateName) { pipeline.Steps.Add(new LicenseCodeReplaceStep()); } - if (context.BuildArgs.UiFramework == UiFramework.Mvc && context.BuildArgs.MobileApp == MobileApp.None) + if ((context.BuildArgs.UiFramework == UiFramework.Mvc || context.BuildArgs.UiFramework == UiFramework.Blazor) + && context.BuildArgs.MobileApp == MobileApp.None) { pipeline.Steps.Add(new RemoveRootFolderStep()); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs index 1ea2760b1c..17e33dc22d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs @@ -5,6 +5,7 @@ NotSpecified = 0, None = 1, Mvc = 2, - Angular = 3 + Angular = 3, + Blazor = 4 } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFrameworkExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFrameworkExtensions.cs index 9161c9c63e..b247a8f424 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFrameworkExtensions.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFrameworkExtensions.cs @@ -9,9 +9,10 @@ case UiFramework.None: return "none"; case UiFramework.Mvc: return "mvc"; case UiFramework.Angular: return "angular"; + case UiFramework.Blazor: return "blazor"; case UiFramework.NotSpecified: return "NotSpecified"; default: return "NotSpecified"; } } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs index 417c004f36..d309d4858a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs @@ -65,12 +65,22 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ConfigureWithAngularUi(context, steps); break; + + case UiFramework.Blazor: + ConfigureWithBlazorUi(context, steps); + break; + case UiFramework.Mvc: case UiFramework.NotSpecified: ConfigureWithMvcUi(context, steps); break; } + if (context.BuildArgs.UiFramework != UiFramework.Blazor) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Blazor")); + } + if (context.BuildArgs.UiFramework != UiFramework.Angular) { steps.Add(new RemoveFolderStep("/angular")); @@ -101,6 +111,26 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App } } + private static void ConfigureWithBlazorUi(ProjectBuildContext context, List steps) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests")); + + if (context.BuildArgs.ExtraProperties.ContainsKey("separate-identity-server")) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds")); + steps.Add(new BlazorAppsettingsFilePortChangeForSeparatedIdentityServersStep()); + } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer")); + steps.Add(new AppTemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305")); + } + } + private static void ConfigureWithMvcUi(ProjectBuildContext context, List steps) { if (context.BuildArgs.ExtraProperties.ContainsKey("tiered")) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/BlazorAppsettingsFilePortChangeForSeparatedIdentityServersStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/BlazorAppsettingsFilePortChangeForSeparatedIdentityServersStep.cs new file mode 100644 index 0000000000..e83a630343 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/BlazorAppsettingsFilePortChangeForSeparatedIdentityServersStep.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using Volo.Abp.Cli.ProjectBuilding.Building; + +namespace Volo.Abp.Cli.ProjectBuilding.Templates.App +{ + public class BlazorAppsettingsFilePortChangeForSeparatedIdentityServersStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + var appsettingsFile = context.Files.FirstOrDefault(x => + !x.IsDirectory && + x.Name.EndsWith("aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/appsettings.json", + StringComparison.InvariantCultureIgnoreCase) + ); + + appsettingsFile.NormalizeLineEndings(); + var lines = appsettingsFile.GetLines(); + + for (var i = 0; i < lines.Length; i++) + { + var line = lines[i]; + + if (line.Contains("Authority") && line.Contains("localhost")) + { + line = line.Replace("44305", "44301"); + } + else if (line.Contains("BaseUrl") && line.Contains("localhost")) + { + line = line.Replace("44305", "44300"); + } + + lines[i] = line; + } + + appsettingsFile.SetLines(lines); + } + } +} diff --git a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs index ac71649793..23fed34f3c 100644 --- a/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/Collections/Generic/AbpListExtensions.cs @@ -188,18 +188,22 @@ namespace System.Collections.Generic /// The type of the members of values. /// A list of objects to sort /// Function to resolve the dependencies + /// Equality comparer for dependencies /// /// Returns a new list ordered by dependencies. /// If A depends on B, then B will come before than A in the resulting list. /// - public static List SortByDependencies(this IEnumerable source, Func> getDependencies) + public static List SortByDependencies( + this IEnumerable source, + Func> getDependencies, + IEqualityComparer comparer = null) { /* See: http://www.codeproject.com/Articles/869059/Topological-sorting-in-Csharp * http://en.wikipedia.org/wiki/Topological_sorting */ var sorted = new List(); - var visited = new Dictionary(); + var visited = new Dictionary(comparer); foreach (var item in source) { 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.MongoDB/Volo.Abp.MongoDB.csproj b/framework/src/Volo.Abp.MongoDB/Volo.Abp.MongoDB.csproj index 2b9e46f43d..e2f589b399 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo.Abp.MongoDB.csproj +++ b/framework/src/Volo.Abp.MongoDB/Volo.Abp.MongoDB.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 8676ac0f34..02dd0e5de7 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -29,6 +29,8 @@ namespace Volo.Abp.Domain.Repositories.MongoDB public virtual IMongoDatabase Database => DbContext.Database; + public virtual IClientSessionHandle SessionHandle => DbContext.SessionHandle; + public virtual TMongoDbContext DbContext => DbContextProvider.GetDbContext(); protected IMongoDbContextProvider DbContextProvider { get; } @@ -60,10 +62,21 @@ namespace Volo.Abp.Domain.Repositories.MongoDB { await ApplyAbpConceptsForAddedEntityAsync(entity); - await Collection.InsertOneAsync( - entity, - cancellationToken: GetCancellationToken(cancellationToken) - ); + if (SessionHandle != null) + { + await Collection.InsertOneAsync( + SessionHandle, + entity, + cancellationToken: GetCancellationToken(cancellationToken) + ); + } + else + { + await Collection.InsertOneAsync( + entity, + cancellationToken: GetCancellationToken(cancellationToken) + ); + } return entity; } @@ -88,12 +101,27 @@ namespace Volo.Abp.Domain.Repositories.MongoDB await TriggerDomainEventsAsync(entity); var oldConcurrencyStamp = SetNewConcurrencyStamp(entity); + ReplaceOneResult result; + + if (SessionHandle != null) + { + result = await Collection.ReplaceOneAsync( + SessionHandle, + CreateEntityFilter(entity, true, oldConcurrencyStamp), + entity, + cancellationToken: GetCancellationToken(cancellationToken) + ); - var result = await Collection.ReplaceOneAsync( - CreateEntityFilter(entity, true, oldConcurrencyStamp), - entity, - cancellationToken: GetCancellationToken(cancellationToken) - ); + + } + else + { + result = await Collection.ReplaceOneAsync( + CreateEntityFilter(entity, true, oldConcurrencyStamp), + entity, + cancellationToken: GetCancellationToken(cancellationToken) + ); + } if (result.MatchedCount <= 0) { @@ -114,11 +142,25 @@ namespace Volo.Abp.Domain.Repositories.MongoDB if (entity is ISoftDelete softDeleteEntity && !IsHardDeleted(entity)) { softDeleteEntity.IsDeleted = true; - var result = await Collection.ReplaceOneAsync( - CreateEntityFilter(entity, true, oldConcurrencyStamp), - entity, - cancellationToken: GetCancellationToken(cancellationToken) - ); + ReplaceOneResult result; + + if (SessionHandle != null) + { + result = await Collection.ReplaceOneAsync( + SessionHandle, + CreateEntityFilter(entity, true, oldConcurrencyStamp), + entity, + cancellationToken: GetCancellationToken(cancellationToken) + ); + } + else + { + result = await Collection.ReplaceOneAsync( + CreateEntityFilter(entity, true, oldConcurrencyStamp), + entity, + cancellationToken: GetCancellationToken(cancellationToken) + ); + } if (result.MatchedCount <= 0) { @@ -127,10 +169,23 @@ namespace Volo.Abp.Domain.Repositories.MongoDB } else { - var result = await Collection.DeleteOneAsync( - CreateEntityFilter(entity, true, oldConcurrencyStamp), - GetCancellationToken(cancellationToken) - ); + DeleteResult result; + + if (SessionHandle != null) + { + result = await Collection.DeleteOneAsync( + SessionHandle, + CreateEntityFilter(entity, true, oldConcurrencyStamp), + cancellationToken: GetCancellationToken(cancellationToken) + ); + } + else + { + result = await Collection.DeleteOneAsync( + CreateEntityFilter(entity, true, oldConcurrencyStamp), + GetCancellationToken(cancellationToken) + ); + } if (result.DeletedCount <= 0) { @@ -195,9 +250,7 @@ namespace Volo.Abp.Domain.Repositories.MongoDB public virtual IMongoQueryable GetMongoQueryable() { - return ApplyDataFilters( - Collection.AsQueryable() - ); + return ApplyDataFilters(SessionHandle != null ? Collection.AsQueryable(SessionHandle) : Collection.AsQueryable()); } protected virtual bool IsHardDeleted(TEntity entity) { @@ -404,6 +457,13 @@ namespace Volo.Abp.Domain.Repositories.MongoDB bool includeDetails = true, CancellationToken cancellationToken = default) { + if (SessionHandle != null) + { + return await Collection + .Find(SessionHandle, RepositoryFilterer.CreateEntityFilter(id, true)) + .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + } + return await Collection .Find(RepositoryFilterer.CreateEntityFilter(id, true)) .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbContext.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbContext.cs index bfa4b87da1..e961fdf256 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbContext.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbContext.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using MongoDB.Bson; using MongoDB.Driver; using Volo.Abp.DependencyInjection; @@ -10,14 +11,17 @@ namespace Volo.Abp.MongoDB public IMongoDatabase Database { get; private set; } + public IClientSessionHandle SessionHandle { get; private set; } + protected internal virtual void CreateModel(IMongoModelBuilder modelBuilder) { } - public virtual void InitializeDatabase(IMongoDatabase database) + public virtual void InitializeDatabase(IMongoDatabase database, IClientSessionHandle sessionHandle) { Database = database; + SessionHandle = sessionHandle; } public virtual IMongoCollection Collection() @@ -25,6 +29,12 @@ namespace Volo.Abp.MongoDB return Database.GetCollection(GetCollectionName()); } + public virtual void InitializeCollections(IMongoDatabase database) + { + Database = database; + ModelSource.GetModel(this); + } + protected virtual string GetCollectionName() { return GetEntityModel().CollectionName; @@ -42,4 +52,4 @@ namespace Volo.Abp.MongoDB return model; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs index e36e972954..d32f015a33 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Domain; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Modularity; +using Volo.Abp.MongoDB.DependencyInjection; using Volo.Abp.Uow.MongoDB; namespace Volo.Abp.MongoDB @@ -9,18 +11,23 @@ namespace Volo.Abp.MongoDB [DependsOn(typeof(AbpDddDomainModule))] public class AbpMongoDbModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddConventionalRegistrar(new AbpMongoDbConventionalRegistrar()); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.TryAddTransient( typeof(IMongoDbContextProvider<>), typeof(UnitOfWorkMongoDbContextProvider<>) ); - + context.Services.TryAddTransient( typeof(IMongoDbRepositoryFilterer<>), typeof(MongoDbRepositoryFilterer<>) ); - + context.Services.TryAddTransient( typeof(IMongoDbRepositoryFilterer<,>), typeof(MongoDbRepositoryFilterer<,>) diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DependencyInjection/AbpMongoDbConventionalRegistrar.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DependencyInjection/AbpMongoDbConventionalRegistrar.cs new file mode 100644 index 0000000000..cb1ed7ea54 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DependencyInjection/AbpMongoDbConventionalRegistrar.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.MongoDB.DependencyInjection +{ + public class AbpMongoDbConventionalRegistrar : DefaultConventionalRegistrar + { + public override void AddType(IServiceCollection services, Type type) + { + if (!typeof(IAbpMongoDbContext).IsAssignableFrom(type) || type == typeof(AbpMongoDbContext)) + { + return; + } + + var dependencyAttribute = GetDependencyAttributeOrNull(type); + var lifeTime = GetLifeTimeOrNull(type, dependencyAttribute); + + if (lifeTime == null) + { + return; + } + + services.Add(ServiceDescriptor.Describe(typeof(IAbpMongoDbContext), type, ServiceLifetime.Transient)); + } + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/IAbpMongoDbContext.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/IAbpMongoDbContext.cs index a2f48fd323..c4049e1389 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/IAbpMongoDbContext.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/IAbpMongoDbContext.cs @@ -7,5 +7,7 @@ namespace Volo.Abp.MongoDB IMongoDatabase Database { get; } IMongoCollection Collection(); + + IClientSessionHandle SessionHandle { get; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs index 43ffbabf4a..6d8706d7ee 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using MongoDB.Bson; +using MongoDB.Driver; using Volo.Abp.Domain.Entities; namespace Volo.Abp.MongoDB @@ -18,7 +20,7 @@ namespace Volo.Abp.MongoDB _entityModelBuilders = new Dictionary(); } - public MongoDbContextModel Build() + public MongoDbContextModel Build(AbpMongoDbContext dbContext) { lock (SyncObj) { @@ -38,6 +40,8 @@ namespace Volo.Abp.MongoDB } baseClasses.AddRange(entityModel.EntityType.GetBaseClasses(includeObject: false)); + + CreateCollectionIfNotExists(dbContext, entityModel.CollectionName); } baseClasses = baseClasses.Distinct().ToList(); @@ -84,5 +88,16 @@ namespace Volo.Abp.MongoDB { return _entityModelBuilders.Values.Cast().ToImmutableList(); } + + protected virtual void CreateCollectionIfNotExists(AbpMongoDbContext dbContext, string collectionName) + { + var filter = new BsonDocument("name", collectionName); + var options = new ListCollectionNamesOptions { Filter = filter }; + + if (!dbContext.Database.ListCollectionNames(options).Any()) + { + dbContext.Database.CreateCollection(collectionName); + } + } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelSource.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelSource.cs index c5cb0da1c8..295cb8b84f 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelSource.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelSource.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.MongoDB public class MongoModelSource : IMongoModelSource, ISingletonDependency { protected readonly ConcurrentDictionary ModelCache = new ConcurrentDictionary(); - + public virtual MongoDbContextModel GetModel(AbpMongoDbContext dbContext) { return ModelCache.GetOrAdd( @@ -26,7 +26,7 @@ namespace Volo.Abp.MongoDB var modelBuilder = CreateModelBuilder(); BuildModelFromDbContextType(modelBuilder, dbContext.GetType()); BuildModelFromDbContextInstance(modelBuilder, dbContext); - return modelBuilder.Build(); + return modelBuilder.Build(dbContext); } protected virtual MongoModelBuilder CreateModelBuilder() @@ -65,4 +65,4 @@ namespace Volo.Abp.MongoDB dbContext.CreateModel(modelBuilder); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/MongoDbTransactionApi.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/MongoDbTransactionApi.cs new file mode 100644 index 0000000000..6c3ab76ee5 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/MongoDbTransactionApi.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver; + +namespace Volo.Abp.Uow.MongoDB +{ + public class MongoDbTransactionApi : ITransactionApi, ISupportsRollback + { + public IClientSessionHandle SessionHandle { get; } + + public MongoDbTransactionApi(IClientSessionHandle sessionHandle) + { + SessionHandle = sessionHandle; + } + + public async Task CommitAsync() + { + await SessionHandle.CommitTransactionAsync(); + } + + protected void Commit() + { + SessionHandle.CommitTransaction(); + } + + public void Dispose() + { + SessionHandle.Dispose(); + } + + public void Rollback() + { + SessionHandle.AbortTransaction(); + } + + public async Task RollbackAsync(CancellationToken cancellationToken) + { + await SessionHandle.AbortTransactionAsync(cancellationToken); + } + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs index 0d527efc4c..0d9969ee51 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs @@ -1,5 +1,6 @@ using System; using Microsoft.Extensions.DependencyInjection; +using MongoDB.Bson; using MongoDB.Driver; using Volo.Abp.Data; using Volo.Abp.MongoDB; @@ -25,7 +26,8 @@ namespace Volo.Abp.Uow.MongoDB var unitOfWork = _unitOfWorkManager.Current; if (unitOfWork == null) { - throw new AbpException($"A {nameof(IMongoDatabase)} instance can only be created inside a unit of work!"); + throw new AbpException( + $"A {nameof(IMongoDatabase)} instance can only be created inside a unit of work!"); } var connectionString = _connectionStringResolver.Resolve(); @@ -41,18 +43,61 @@ namespace Volo.Abp.Uow.MongoDB //TODO: Create only single MongoDbClient per connection string in an application (extract MongoClientCache for example). var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, - () => + () => new MongoDbDatabaseApi(CreateDbContext(unitOfWork, mongoUrl, databaseName))); + + return ((MongoDbDatabaseApi) databaseApi).DbContext; + } + + private TMongoDbContext CreateDbContext(IUnitOfWork unitOfWork, MongoUrl mongoUrl, string databaseName) + { + var client = new MongoClient(mongoUrl); + var database = client.GetDatabase(databaseName); + + if (unitOfWork.Options.IsTransactional) + { + return CreateDbContextWithTransaction(unitOfWork, mongoUrl, client, database); + } + + var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + dbContext.ToAbpMongoDbContext().InitializeDatabase(database, null); + + return dbContext; + } + + public TMongoDbContext CreateDbContextWithTransaction( + IUnitOfWork unitOfWork, + MongoUrl url, + MongoClient client, + IMongoDatabase database) + { + var transactionApiKey = $"MongoDb_{url}"; + var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as MongoDbTransactionApi; + var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + + if (activeTransaction?.SessionHandle == null) + { + var session = client.StartSession(); + + if (unitOfWork.Options.Timeout.HasValue) { - var database = new MongoClient(mongoUrl).GetDatabase(databaseName); + session.AdvanceOperationTime(new BsonTimestamp(unitOfWork.Options.Timeout.Value)); + } - var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + session.StartTransaction(); - dbContext.ToAbpMongoDbContext().InitializeDatabase(database); + unitOfWork.AddTransactionApi( + transactionApiKey, + new MongoDbTransactionApi(session) + ); - return new MongoDbDatabaseApi(dbContext); - }); + dbContext.ToAbpMongoDbContext().InitializeDatabase(database, session); + } + else + { + dbContext.ToAbpMongoDbContext().InitializeDatabase(database, activeTransaction.SessionHandle); + } - return ((MongoDbDatabaseApi)databaseApi).DbContext; + return dbContext; } } -} \ No newline at end of file +} 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/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/BuildProjectListSorter_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/BuildProjectListSorter_Tests.cs new file mode 100644 index 0000000000..1fde954ce2 --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/BuildProjectListSorter_Tests.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Cli.Build +{ + public class BuildProjectListSorter_Tests : AbpCliTestBase + { + private IBuildProjectListSorter _buildProjectListSorter; + + public BuildProjectListSorter_Tests() + { + _buildProjectListSorter = GetRequiredService(); + } + + [Fact] + public void SortByDependencies_Test() + { + // A -> B, C + // B -> D + // D -> F + // F -> C + // C -> G + // Final build order must be: G, + + var repositoryName = "volo"; + var source = new List + { + new DotNetProjectInfo(repositoryName, "A", true) + { + Dependencies = new List() + { + new DotNetProjectInfo(repositoryName, "B", true), + new DotNetProjectInfo(repositoryName, "C", true) + } + }, + new DotNetProjectInfo(repositoryName, "B", true) + { + Dependencies = new List() + { + new DotNetProjectInfo(repositoryName, "D", true) + } + }, + new DotNetProjectInfo(repositoryName, "D", true) + { + Dependencies = new List() + { + new DotNetProjectInfo(repositoryName, "F", true) + } + }, + new DotNetProjectInfo(repositoryName, "F", true) + { + Dependencies = new List() + { + new DotNetProjectInfo(repositoryName, "C", true) + } + }, + new DotNetProjectInfo(repositoryName, "C", true) + { + Dependencies = new List() + { + new DotNetProjectInfo(repositoryName, "G", true) + } + }, + new DotNetProjectInfo(repositoryName, "G", true) + }; + + var sortedDependencies = + _buildProjectListSorter.SortByDependencies(source, new DotNetProjectInfoEqualityComparer()); + sortedDependencies.Count.ShouldBe(6); + sortedDependencies[0].CsProjPath.ShouldBe("G"); + sortedDependencies[1].CsProjPath.ShouldBe("C"); + sortedDependencies[2].CsProjPath.ShouldBe("F"); + sortedDependencies[3].CsProjPath.ShouldBe("D"); + sortedDependencies[4].CsProjPath.ShouldBe("B"); + sortedDependencies[5].CsProjPath.ShouldBe("A"); + } + } +} diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/BuildStatusGenerator_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/BuildStatusGenerator_Tests.cs new file mode 100644 index 0000000000..24790a8a03 --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/BuildStatusGenerator_Tests.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Cli.Build +{ + public class BuildStatusGenerator_Tests : AbpCliTestBase + { + private readonly IBuildStatusGenerator _buildStatusGenerator; + private IGitRepositoryHelper _gitRepositoryHelper; + + public BuildStatusGenerator_Tests() + { + _buildStatusGenerator = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + _gitRepositoryHelper = Substitute.For(); + services.AddTransient(provider => _gitRepositoryHelper); + } + + [Fact] + public void Should_Set_Repository_CommitId_When_All_Projects_Built() + { + var buildConfig = new DotNetProjectBuildConfig + { + GitRepository = new GitRepository("volo", "dev", "") + { + DependingRepositories = new List() + { + new GitRepository("abp", "dev", "") + } + } + }; + + var changedProjects = new List() + { + new DotNetProjectInfo("volo", "project1.csproj", true) + }; + + var builtProjects = new List() + { + "project1.csproj" + }; + + var lastCommitId = "1"; + _gitRepositoryHelper.GetLastCommitId(buildConfig.GitRepository).Returns(lastCommitId); + _gitRepositoryHelper.GetFriendlyName(buildConfig.GitRepository).Returns("volo"); + + var status = _buildStatusGenerator.Generate(buildConfig, changedProjects, builtProjects); + status.CommitId.ShouldBe(lastCommitId); + } + + + [Fact] + public void Should_Set_Repository_CommitId_When_All_Projects_Built_For_Child_Repository() + { + var buildConfig = new DotNetProjectBuildConfig + { + GitRepository = new GitRepository("volo", "dev", "") + { + DependingRepositories = new List() + { + new GitRepository("abp", "dev", "") + } + } + }; + + var changedProjects = new List() + { + new DotNetProjectInfo("abp", "project1.csproj", true) + }; + + var builtProjects = new List() + { + "project1.csproj" + }; + + var lastCommitId = "1"; + _gitRepositoryHelper.GetLastCommitId(buildConfig.GitRepository).Returns(lastCommitId); + _gitRepositoryHelper.GetFriendlyName(buildConfig.GitRepository).Returns("abp"); + + var status = _buildStatusGenerator.Generate(buildConfig, changedProjects, builtProjects); + status.CommitId.ShouldBe(lastCommitId); + } + + [Fact] + public void Should_Not_Set_Repository_CommitId_When_Building_Single_Solution() + { + var buildConfig = new DotNetProjectBuildConfig + { + GitRepository = new GitRepository("volo", "dev", "") + { + DependingRepositories = new List() + { + new GitRepository("abp", "dev", "") + } + }, + SlFilePath = "test.sln" + }; + + var changedProjects = new List() + { + new DotNetProjectInfo("volo", "project1.csproj", true) + }; + + var builtProjects = new List() + { + "project1.csproj" + }; + + var lastCommitId = "1"; + _gitRepositoryHelper.GetLastCommitId(buildConfig.GitRepository).Returns(lastCommitId); + _gitRepositoryHelper.GetFriendlyName(buildConfig.GitRepository).Returns("volo"); + + var status = _buildStatusGenerator.Generate(buildConfig, changedProjects, builtProjects); + status.CommitId.ShouldBeNull(); + } + } +} diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/GitRepositoryBuildStatus_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/GitRepositoryBuildStatus_Tests.cs new file mode 100644 index 0000000000..387b30da7c --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/GitRepositoryBuildStatus_Tests.cs @@ -0,0 +1,318 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Cli.Build +{ + public class GitRepositoryBuildStatus_Tests : AbpCliTestBase + { + private IGitRepositoryHelper _gitRepositoryHelper; + + protected override void AfterAddApplication(IServiceCollection services) + { + _gitRepositoryHelper = Substitute.For(); + services.AddTransient(provider => _gitRepositoryHelper); + } + + [Fact] + public void Add_New_Build_Status_Test() + { + var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev") + { + SucceedProjects = new List + { + new DotNetProjectBuildStatus + { + CsProjPath = "project1.csproj", + CommitId = "1" + } + } + }; + + var newBuildStatus = new GitRepositoryBuildStatus( + existingBuildStatus.RepositoryName, + existingBuildStatus.BranchName + ) + { + SucceedProjects = new List + { + new DotNetProjectBuildStatus + { + CsProjPath = "project2.csproj", + CommitId = "2" + } + } + }; + + existingBuildStatus.MergeWith(newBuildStatus); + + existingBuildStatus.SucceedProjects.Count.ShouldBe(2); + } + + [Fact] + public void Update_Existing_Build_Status_Test() + { + var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev") + { + SucceedProjects = new List + { + new DotNetProjectBuildStatus + { + CsProjPath = "project1.csproj", + CommitId = "1" + } + } + }; + + var newBuildStatus = new GitRepositoryBuildStatus( + existingBuildStatus.RepositoryName, + existingBuildStatus.BranchName + ) + { + SucceedProjects = new List + { + new DotNetProjectBuildStatus + { + CsProjPath = "project1.csproj", + CommitId = "2" + }, + new DotNetProjectBuildStatus + { + CsProjPath = "project2.csproj", + CommitId = "2" + } + } + }; + + existingBuildStatus.MergeWith(newBuildStatus); + existingBuildStatus.SucceedProjects.Count.ShouldBe(2); + existingBuildStatus.GetSelfOrChild("volo").SucceedProjects.First(p => p.CsProjPath == "project1.csproj") + .CommitId.ShouldBe("2"); + existingBuildStatus.GetSelfOrChild("volo").SucceedProjects.First(p => p.CsProjPath == "project2.csproj") + .CommitId.ShouldBe("2"); + } + + [Fact] + public void Add_New_Build_Status_For_Child_Repository_Test() + { + var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev") + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("abp", "dev") + { + SucceedProjects = new List + { + new DotNetProjectBuildStatus + { + CsProjPath = "project1.csproj", + CommitId = "1" + } + } + } + } + }; + + var newBuildStatus = new GitRepositoryBuildStatus( + existingBuildStatus.RepositoryName, + existingBuildStatus.BranchName + ) + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("abp", "dev") + { + SucceedProjects = new List + { + new DotNetProjectBuildStatus + { + CsProjPath = "project2.csproj", + CommitId = "2" + } + } + } + } + }; + + existingBuildStatus.MergeWith(newBuildStatus); + existingBuildStatus.GetChild("abp").SucceedProjects.Count.ShouldBe(2); + } + + [Fact] + public void Should_Update_Repository_CommitId_When_New_CommitId_Is_Not_Empty() + { + var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev"); + + var newBuildStatus = new GitRepositoryBuildStatus( + existingBuildStatus.RepositoryName, + existingBuildStatus.BranchName + ) + { + CommitId = "42" + }; + + existingBuildStatus.MergeWith(newBuildStatus); + + existingBuildStatus.CommitId.ShouldBe("42"); + } + + [Fact] + public void Should_Not_Update_Repository_CommitId_When_New_CommitId_Is_Empty() + { + var existingBuildStatus = new GitRepositoryBuildStatus("volo", "dev") + { + CommitId = "21" + }; + + var newBuildStatus = new GitRepositoryBuildStatus( + existingBuildStatus.RepositoryName, + existingBuildStatus.BranchName + ) + { + CommitId = "" + }; + + existingBuildStatus.MergeWith(newBuildStatus); + + existingBuildStatus.CommitId.ShouldBe("21"); + } + + [Fact] + public void GetChild_Test() + { + var existingBuildStatus = new GitRepositoryBuildStatus("repo-1", "dev") + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("repo-2", "dev") + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("repo-3", "dev") + } + }, + new GitRepositoryBuildStatus("repo-4", "dev") + } + }; + + existingBuildStatus.GetChild("repo-3").RepositoryName.ShouldBe("repo-3"); + existingBuildStatus.GetChild("repo-4").RepositoryName.ShouldBe("repo-4"); + } + + [Fact] + public void GetUniqueName_Test() + { + var existingBuildStatus = new GitRepositoryBuildStatus("repo-1", "dev") + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("repo-2", "dev") + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("repo-3", "dev") + } + }, + new GitRepositoryBuildStatus("repo-4", "dev") + } + }; + + existingBuildStatus.GetUniqueName("").ShouldBe("B25C935F97D7B3375530A96B392B7644"); + existingBuildStatus.GetUniqueName("production").ShouldBe("production_B25C935F97D7B3375530A96B392B7644"); + } + + [Fact] + public void GetSelfOrChild_Test() + { + var existingBuildStatus = new GitRepositoryBuildStatus("repo-1", "dev") + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("repo-2", "dev") + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("repo-3", "dev") + } + }, + new GitRepositoryBuildStatus("repo-4", "dev") + } + }; + + existingBuildStatus.GetSelfOrChild("repo-1").RepositoryName.ShouldBe("repo-1"); + existingBuildStatus.GetSelfOrChild("repo-2").RepositoryName.ShouldBe("repo-2"); + existingBuildStatus.GetSelfOrChild("repo-3").RepositoryName.ShouldBe("repo-3"); + existingBuildStatus.GetSelfOrChild("repo-4").RepositoryName.ShouldBe("repo-4"); + } + + [Fact] + public void AddOrUpdateProjectStatus_Test() + { + var existingBuildStatus = new GitRepositoryBuildStatus("repo-1", "dev") + { + DependingRepositories = new List() + { + new GitRepositoryBuildStatus("repo-2", "dev") + { + SucceedProjects = new List + { + new DotNetProjectBuildStatus + { + CsProjPath = "A.csproj", + CommitId = "42" + } + }, + DependingRepositories = new List + { + new GitRepositoryBuildStatus("repo-3", "dev") + { + SucceedProjects = new List + { + new DotNetProjectBuildStatus + { + CsProjPath = "B.csproj", + CommitId = "42" + } + } + } + } + }, + new GitRepositoryBuildStatus("repo-4", "dev") + { + SucceedProjects = new List() + { + new DotNetProjectBuildStatus + { + CsProjPath = "C.csproj", + CommitId = "42" + } + } + } + } + }; + + var repo2 = existingBuildStatus.GetChild("repo-2"); + repo2.AddOrUpdateProjectStatus(new DotNetProjectBuildStatus + { + CommitId = "21", + CsProjPath = "A.csproj" + }); + + var repo3 = existingBuildStatus.GetChild("repo-3"); + repo3.AddOrUpdateProjectStatus(new DotNetProjectBuildStatus + { + CommitId = "21", + CsProjPath = "X.csproj" + }); + + repo2.SucceedProjects.Count.ShouldBe(1); + repo2.SucceedProjects.ShouldContain(e=> e.CsProjPath == "A.csproj" && e.CommitId == "21"); + + repo3.SucceedProjects.Count.ShouldBe(2); + repo3.SucceedProjects.ShouldContain(e=> e.CsProjPath == "X.csproj" && e.CommitId == "21"); + } + } +} diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/GitRepository_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/GitRepository_Tests.cs new file mode 100644 index 0000000000..17cc8b9dc4 --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/Build/GitRepository_Tests.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Cli.Build +{ + public class GitRepository_Tests : AbpCliTestBase + { + [Fact] + public void GetUniqueName_Test() + { + var gitRepository = new GitRepository("repo-1", "dev", "") + { + DependingRepositories = new List + { + new GitRepository("repo-2", "dev", ""), + new GitRepository("repo-3", "dev", "") + { + DependingRepositories = new List() + { + new GitRepository("repo-4", "dev", "") + } + } + } + }; + + gitRepository.GetUniqueName("").ShouldBe("B25C935F97D7B3375530A96B392B7644"); + gitRepository.GetUniqueName("production").ShouldBe("production_B25C935F97D7B3375530A96B392B7644"); + } + + [Fact] + public void FindRepositoryOf_Test() + { + var gitRepository = new GitRepository("repo-1", "dev", "/repo-1/dev/") + { + DependingRepositories = new List + { + new GitRepository("repo-2", "dev", "/repo-2/dev/"), + new GitRepository("repo-3", "dev", "/repo-3/dev/") + { + DependingRepositories = new List() + { + new GitRepository("repo-4", "dev", "/repo-4/dev/") + } + } + } + }; + + gitRepository.FindRepositoryOf("/repo-4/dev/A.csproj").ShouldBe("repo-4"); + } + } +} diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs index cfa7b033b7..8ec0b026cd 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/AbpMongoDbTestModule.cs @@ -1,10 +1,15 @@ using System; +using System.Linq; +using System.Threading; using Microsoft.Extensions.DependencyInjection; +using MongoDB.Driver; +using MongoDB.Driver.Core.Servers; using Volo.Abp.Data; using Volo.Abp.Modularity; using Volo.Abp.TestApp; using Volo.Abp.TestApp.Domain; using Volo.Abp.TestApp.MongoDB; +using Volo.Abp.Uow; namespace Volo.Abp.MongoDB { @@ -16,7 +21,7 @@ namespace Volo.Abp.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); @@ -30,6 +35,11 @@ namespace Volo.Abp.MongoDB options.AddDefaultRepositories(); options.AddRepository(); }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } } diff --git a/modules/account/Volo.Abp.Account.sln b/modules/account/Volo.Abp.Account.sln index c2b195f05b..b96627aac9 100644 --- a/modules/account/Volo.Abp.Account.sln +++ b/modules/account/Volo.Abp.Account.sln @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{12FAE513-7 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Account.Application.Tests", "test\Volo.Abp.Account.Application.Tests\Volo.Abp.Account.Application.Tests.csproj", "{356EAC51-5D55-4852-A8A5-2F90EAC8DAE3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Account.Blazor", "src\Volo.Abp.Account.Blazor\Volo.Abp.Account.Blazor.csproj", "{EE8858B3-A638-481B-8EB9-74F5E7D43D80}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +57,10 @@ Global {356EAC51-5D55-4852-A8A5-2F90EAC8DAE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {356EAC51-5D55-4852-A8A5-2F90EAC8DAE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {356EAC51-5D55-4852-A8A5-2F90EAC8DAE3}.Release|Any CPU.Build.0 = Release|Any CPU + {EE8858B3-A638-481B-8EB9-74F5E7D43D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE8858B3-A638-481B-8EB9-74F5E7D43D80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE8858B3-A638-481B-8EB9-74F5E7D43D80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE8858B3-A638-481B-8EB9-74F5E7D43D80}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -67,6 +73,7 @@ Global {EBF0C2B3-14CB-4DFC-9753-ECBF959CBA2E} = {B5881429-EFF7-4F30-8C0B-0AC41E36B74E} {7BCA5955-B3F6-41E8-9D5D-D115195C6098} = {B5881429-EFF7-4F30-8C0B-0AC41E36B74E} {356EAC51-5D55-4852-A8A5-2F90EAC8DAE3} = {12FAE513-7575-4235-89DF-277BA1A3B098} + {EE8858B3-A638-481B-8EB9-74F5E7D43D80} = {B5881429-EFF7-4F30-8C0B-0AC41E36B74E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2B054393-D2B2-4EA8-8A15-D60CBCF3E7A9} diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs new file mode 100644 index 0000000000..f5c4cdc01d --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorAutoMapperProfile.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using Volo.Abp.Account.Blazor.Pages.Account; +using Volo.Abp.AutoMapper; +using Volo.Abp.Identity; + +namespace Volo.Abp.Account.Blazor +{ + public class AbpAccountBlazorAutoMapperProfile : Profile + { + public AbpAccountBlazorAutoMapperProfile() + { + CreateMap() + .Ignore(x => x.PhoneNumberConfirmed) + .Ignore(x => x.EmailConfirmed); + + CreateMap() + .Ignore(x => x.ExtraProperties); + } + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs new file mode 100644 index 0000000000..c0917dd8f7 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorModule.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Routing; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Account.Blazor +{ + [DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingModule), + typeof(AbpAutoMapperModule), + typeof(AbpAccountHttpApiClientModule) + )] + public class AbpAccountBlazorModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAutoMapperObjectMapper(); + + Configure(options => + { + options.AddProfile(validate: true); + }); + + Configure(options => + { + options.MenuContributors.Add(new AbpAccountBlazorUserMenuContributor()); + }); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpAccountBlazorModule).Assembly); + }); + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorUserMenuContributor.cs b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorUserMenuContributor.cs new file mode 100644 index 0000000000..3ceab0f735 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/AbpAccountBlazorUserMenuContributor.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Volo.Abp.Account.Localization; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Account.Blazor +{ + public class AbpAccountBlazorUserMenuContributor : IMenuContributor + { + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.User) + { + return Task.CompletedTask; + } + + var accountResource = context.GetLocalizer(); + + context.Menu.AddItem(new ApplicationMenuItem("Account.Manage", accountResource["ManageYourProfile"], url: "account/manage-profile", icon: "fa fa-cog")); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor/FodyWeavers.xml b/modules/account/src/Volo.Abp.Account.Blazor/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor/FodyWeavers.xsd b/modules/account/src/Volo.Abp.Account.Blazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.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/account/src/Volo.Abp.Account.Blazor/Pages/Account/Manage.razor b/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/Manage.razor new file mode 100644 index 0000000000..d8c9d063b8 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/Manage.razor @@ -0,0 +1,64 @@ +@page "/account/manage-profile" +@inherits AccountManageBase + + + + + @L["ProfileTab:Password"] + @L["ProfileTab:PersonalInfo"] + + + @if (ChangePasswordModel != null) + { + + + @L["DisplayName:CurrentPassword"] + + + + @L["DisplayName:NewPassword"] + + + + @L["DisplayName:NewPasswordConfirm"] + + + + + + + } + @if (PersonalInfoModel != null) + { + + + @L["DisplayName:UserName"] + + + + + @L["DisplayName:Name"] + + + + @L["DisplayName:Surname"] + + + + + @L["DisplayName:Email"] + + + + @L["DisplayName:PhoneNumber"] + + + + + + + } + + + + \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/Manage.razor.cs b/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/Manage.razor.cs new file mode 100644 index 0000000000..77cf43327c --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/Pages/Account/Manage.razor.cs @@ -0,0 +1,117 @@ +using System.Threading.Tasks; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Volo.Abp.Account.Localization; +using Volo.Abp.AspNetCore.Components.WebAssembly; +using Volo.Abp.Identity; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Account.Blazor.Pages.Account +{ + public class AccountManageBase : OwningComponentBase + { + [Inject] protected IAccountAppService AccountAppService { get; set; } + [Inject] protected IProfileAppService ProfileAppService { get; set; } + + [Inject] protected IObjectMapper ObjectMapper { get; set; } + + [Inject] protected IUiMessageService UiMessageService { get; set; } + + [Inject] protected IStringLocalizer UiLocalizer { get; set; } + + [Inject] protected IStringLocalizer L { get; set; } + + protected string SelectedTab = "Password"; + + protected ChangePasswordModel ChangePasswordModel; + + protected PersonalInfoModel PersonalInfoModel; + + protected override async Task OnInitializedAsync() + { + await GetUserInformations(); + } + + protected async Task GetUserInformations() + { + var user = await ProfileAppService.GetAsync(); + + ChangePasswordModel = new ChangePasswordModel + { + HideOldPasswordInput = !user.HasPassword + }; + + PersonalInfoModel = ObjectMapper.Map(user); + } + + protected async Task ChangePasswordAsync() + { + if (string.IsNullOrWhiteSpace(ChangePasswordModel.CurrentPassword)) + { + return; + } + + if (ChangePasswordModel.NewPassword != ChangePasswordModel.NewPasswordConfirm) + { + await UiMessageService.WarnAsync(L["NewPasswordConfirmFailed"]); + return; + } + + if (!await UiMessageService.ConfirmAsync(UiLocalizer["AreYouSure"])) + { + return; + } + + await ProfileAppService.ChangePasswordAsync(new ChangePasswordInput + { + CurrentPassword = ChangePasswordModel.CurrentPassword, + NewPassword = ChangePasswordModel.NewPassword + }); + + await UiMessageService.SuccessAsync(L["PasswordChanged"]); + } + + protected async Task UpdatePersonalInfoAsync() + { + if (!await UiMessageService.ConfirmAsync(UiLocalizer["AreYouSure"])) + { + return; + } + + await ProfileAppService.UpdateAsync( + ObjectMapper.Map(PersonalInfoModel) + ); + + await UiMessageService.SuccessAsync(L["PersonalSettingsSaved"]); + } + } + + public class ChangePasswordModel + { + public string CurrentPassword { get; set; } + + public string NewPassword { get; set; } + + public string NewPasswordConfirm { get; set; } + + public bool HideOldPasswordInput { get; set; } + } + + public class PersonalInfoModel + { + public string UserName { get; set; } + + public string Email { get; set; } + + public string Name { get; set; } + + public string Surname { get; set; } + + public string PhoneNumber { get; set; } + + public bool PhoneNumberConfirmed { get; set; } + + public bool EmailConfirmed { get; set; } + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj b/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj new file mode 100644 index 0000000000..5707bc86a4 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor/Volo.Abp.Account.Blazor.csproj @@ -0,0 +1,27 @@ + + + + + + + netstandard2.1 + 3.0 + Volo.Abp.Account.Blazor + + + + + + + + + + + + + + true + + + + diff --git a/modules/account/src/Volo.Abp.Account.Blazor/_Imports.razor b/modules/account/src/Volo.Abp.Account.Blazor/_Imports.razor new file mode 100644 index 0000000000..4685ac9893 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.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/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs index 5cac32faae..a8d213a81f 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.MongoDB.Tests/Volo/Abp/AuditLogging/MongoDB/AbpAuditLoggingMongoDbTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.AuditLogging.MongoDB { @@ -12,14 +13,19 @@ namespace Volo.Abp.AuditLogging.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } -} \ No newline at end of file +} diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/AbpBackgroundJobsMongoDbTestModule.cs b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/AbpBackgroundJobsMongoDbTestModule.cs index 07be76b792..a7ec1ad65f 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/AbpBackgroundJobsMongoDbTestModule.cs +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo/Abp/BackgroundJobs/MongoDB/AbpBackgroundJobsMongoDbTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.BackgroundJobs.MongoDB { @@ -12,14 +13,19 @@ namespace Volo.Abp.BackgroundJobs.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } -} \ No newline at end of file +} diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestModule.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestModule.cs index 03eb96f6d3..12cb8b0de3 100644 --- a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestModule.cs +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.MongoDB.Tests/MongoDB/BlobStoringDatabaseMongoDbTestModule.cs @@ -1,25 +1,31 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.BlobStoring.Database.MongoDB { [DependsOn( typeof(BlobStoringDatabaseTestBaseModule), typeof(BlobStoringDatabaseMongoDbModule) - )] + )] public class BlobStoringDatabaseMongoDbTestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } -} \ No newline at end of file +} diff --git a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/BloggingMongoDBTestModule.cs b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/BloggingMongoDBTestModule.cs index 1e68cebf21..f66922cfcd 100644 --- a/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/BloggingMongoDBTestModule.cs +++ b/modules/blogging/test/Volo.Blogging.MongoDB.Tests/Volo/Blogging/MongoDB/BloggingMongoDBTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Blogging.MongoDB { @@ -12,14 +13,19 @@ namespace Volo.Blogging.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } -} \ No newline at end of file +} diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/CmsKitMongoDbTestModule.cs b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/CmsKitMongoDbTestModule.cs index 125ec8dbd4..521b93cc8c 100644 --- a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/CmsKitMongoDbTestModule.cs +++ b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/CmsKitMongoDbTestModule.cs @@ -1,25 +1,31 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.CmsKit.MongoDB { [DependsOn( typeof(CmsKitTestBaseModule), typeof(CmsKitMongoDbModule) - )] + )] public class CmsKitMongoDbTestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } -} \ No newline at end of file +} diff --git a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/DocsMongoDBTestModule.cs b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/DocsMongoDBTestModule.cs index b31f9b5acd..d00f25b533 100644 --- a/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/DocsMongoDBTestModule.cs +++ b/modules/docs/test/Volo.Docs.MongoDB.Tests/Volo/Docs/MongoDB/DocsMongoDBTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Docs.MongoDB { @@ -12,14 +13,19 @@ namespace Volo.Docs.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } } 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.Domain.Shared/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainSharedModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainSharedModule.cs index 0c9a2e0f26..04edbfbbc2 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainSharedModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainSharedModule.cs @@ -1,5 +1,6 @@ using Volo.Abp.FeatureManagement.Localization; using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; using Volo.Abp.Validation; using Volo.Abp.Validation.Localization; @@ -27,6 +28,11 @@ namespace Volo.Abp.FeatureManagement typeof(AbpValidationResource) ).AddVirtualJson("Volo/Abp/FeatureManagement/Localization/Domain"); }); + + Configure(options => + { + options.MapCodeNamespace("Volo.Abp.FeatureManagement", typeof(AbpFeatureManagementResource)); + }); } } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureManagementDomainErrorCodes.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureManagementDomainErrorCodes.cs index 010c64efbb..9bcfd3290e 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureManagementDomainErrorCodes.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureManagementDomainErrorCodes.cs @@ -1,7 +1,9 @@ -namespace Volo.Abp.FeatureManagement +using System; + +namespace Volo.Abp.FeatureManagement { public static class FeatureManagementDomainErrorCodes { - + public const string FeatureValueInvalid = "Volo.Abp.FeatureManagement:InvalidFeatureValue"; } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureValueInvalidException.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureValueInvalidException.cs new file mode 100644 index 0000000000..d003295239 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/FeatureValueInvalidException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Volo.Abp.FeatureManagement +{ + [Serializable] + public class FeatureValueInvalidException : BusinessException + { + public FeatureValueInvalidException(string name) : + base(FeatureManagementDomainErrorCodes.FeatureValueInvalid) + { + WithData("0", name); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ar.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ar.json index fcc347b9e3..cc5fae2dff 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ar.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ar.json @@ -2,6 +2,7 @@ "culture": "ar", "texts": { "Features": "المميزات", - "NoFeatureFoundMessage": "لا توجد أي ميزة متاحة." + "NoFeatureFoundMessage": "لا توجد أي ميزة متاحة.", + "Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0} قيمة الميزة غير صالحة!" } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/de.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/de.json index 12a4e1aef0..c6d4bb1d07 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/de.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/de.json @@ -2,6 +2,7 @@ "culture": "de", "texts": { "Features": "Funktionen", - "NoFeatureFoundMessage": "Es ist keine Funktion verfügbar." + "NoFeatureFoundMessage": "Es ist keine Funktion verfügbar.", + "Volo.Abp.FeatureManagement:InvalidFeatureValue" : "Der {0} -Feature-Wert ist ungültig!" } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json index 26f5dc736b..c5194de6c7 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json @@ -4,6 +4,7 @@ "Features": "Features", "NoFeatureFoundMessage": "There isn't any available feature.", "Permission:FeatureManagement": "Feature management", - "Permission:FeatureManagement.ManageHostFeatures": "Manage Host features" + "Permission:FeatureManagement.ManageHostFeatures": "Manage Host features", + "Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0} feature value is not valid!" } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ru.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ru.json index 3945762269..d232c20060 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ru.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/ru.json @@ -2,6 +2,7 @@ "culture": "ru", "texts": { "Features": "Функциональные возможности", - "NoFeatureFoundMessage": "Нет доступных функциональных возможностей." + "NoFeatureFoundMessage": "Нет доступных функциональных возможностей.", + "Volo.Abp.FeatureManagement:InvalidFeatureValue" : "Недопустимое значение функции {0}!" } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json index 8233674933..b69674a620 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json @@ -4,6 +4,7 @@ "Features": "Özellikler", "NoFeatureFoundMessage": "Hiç özellik yok.", "Permission:FeatureManagement": "Özellik yönetimi", - "Permission:FeatureManagement.ManageHostFeatures": "Host özelliklerini düzenle" + "Permission:FeatureManagement.ManageHostFeatures": "Host özelliklerini düzenle", + "Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0} özellik değeri geçerli değil!" } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json index 9737a9a59c..473f581b34 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json @@ -4,6 +4,7 @@ "Features": "功能", "NoFeatureFoundMessage": "没有可用的功能.", "Permission:FeatureManagement": "特性管理", - "Permission:FeatureManagement.ManageHostFeatures": "管理Host特性" + "Permission:FeatureManagement.ManageHostFeatures": "管理Host特性", + "Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0}功能的值无效!" } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json index 7d68fd5bc2..dc84e7c104 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json @@ -4,6 +4,7 @@ "Features": "功能", "NoFeatureFoundMessage": "沒有可用的功能.", "Permission:FeatureManagement": "功能管理", - "Permission:FeatureManagement.ManageHostFeatures": "管理Host功能" + "Permission:FeatureManagement.ManageHostFeatures": "管理Host功能", + "Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0}功能的值無效!" } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs index 454ade5476..38c61cc743 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Features; @@ -14,15 +15,18 @@ namespace Volo.Abp.FeatureManagement protected IFeatureDefinitionManager FeatureDefinitionManager { get; } protected List Providers => _lazyProviders.Value; protected FeatureManagementOptions Options { get; } + protected IStringLocalizerFactory StringLocalizerFactory { get; } private readonly Lazy> _lazyProviders; public FeatureManager( IOptions options, IServiceProvider serviceProvider, - IFeatureDefinitionManager featureDefinitionManager) + IFeatureDefinitionManager featureDefinitionManager, + IStringLocalizerFactory stringLocalizerFactory) { FeatureDefinitionManager = featureDefinitionManager; + StringLocalizerFactory = stringLocalizerFactory; Options = options.Value; //TODO: Instead, use IHybridServiceScopeFactory and create a scope..? @@ -57,7 +61,8 @@ namespace Volo.Abp.FeatureManagement .Select(x => new FeatureNameValue(x.Name, x.Value)).ToList(); } - public async Task GetOrNullWithProviderAsync(string name, string providerName, string providerKey, bool fallback = true) + public async Task GetOrNullWithProviderAsync(string name, + string providerName, string providerKey, bool fallback = true) { Check.NotNull(name, nameof(name)); Check.NotNull(providerName, nameof(providerName)); @@ -65,7 +70,8 @@ namespace Volo.Abp.FeatureManagement return await GetOrNullInternalAsync(name, providerName, providerKey, fallback); } - public async Task> GetAllWithProviderAsync(string providerName, string providerKey, bool fallback = true) + public async Task> GetAllWithProviderAsync(string providerName, + string providerKey, bool fallback = true) { Check.NotNull(providerName, nameof(providerName)); @@ -128,6 +134,11 @@ namespace Volo.Abp.FeatureManagement var feature = FeatureDefinitionManager.Get(name); + if (!feature.ValueType.Validator.IsValid(value)) + { + throw new FeatureValueInvalidException(feature.DisplayName.Localize(StringLocalizerFactory)); + } + var providers = Enumerable .Reverse(Providers) .SkipWhile(p => p.Name != providerName) 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..a5592c2f28 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,68 +1,103 @@ -@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); + } }
@if (Model.FeatureListResultDto != null && Model.FeatureListResultDto.Groups.Any()) { + var featureGroups = Model.FeatureListResultDto.Groups; + - @for (var i = 0; i < Model.FeatureListResultDto.Groups.Count; i++) + @for (var i = 0; i < featureGroups.Count; i++) { - -

@Model.FeatureListResultDto.Groups[i].DisplayName

+ +

@featureGroups[i].DisplayName


- @for (var j = 0; j < Model.FeatureListResultDto.Groups[i].Features.Count; j++) + @for (var j = 0; j < featureGroups[i].Features.Count; j++) { - var feature = Model.FeatureListResultDto.Groups[i].Features[j]; + var feature = featureGroups[i].Features[j]; var disabled = Model.IsDisabled(feature.Provider.Name); -
- - @feature.DisplayName @(disabled ? $"({feature.Provider.Name})" : "") +
+ + @if (feature.ValueType is ToggleStringValueType) + { + + + } + @if (feature.ValueType is FreeTextStringValueType) { - - + var type = "text"; + if(feature.ValueType.Validator is NumericValueValidator) + { + type = "number"; + } + + } - @if (feature.ValueType is SelectionStringValueType) + + @if (feature.ValueType is SelectionStringValueType selectType) { - - + @foreach (var item in selectType.ItemSource.Items) { - + if (item.Value == feature.Value) + { + + } + else + { + + } } - } - - } - @if (feature.ValueType is ToggleStringValueType) - { - - + +
} + +
}
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js index 7e4960999c..f9ec7e11fa 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/feature-management-modal.js @@ -3,12 +3,75 @@ abp.modals = abp.modals || {}; abp.modals.FeatureManagement = function () { - $('.FeatureValueCheckbox').change(function () { - if (this.checked) { - $(this).val('true'); - } else { - $(this).val('false'); + function checkParents($tab, $element, className) { + var parentName = $element + .closest(className) + .attr('data-parent-name'); + + if (!parentName) { + return; + } + + $tab.find('.custom-checkbox') + .filter('[data-feature-name="' + parentName + '"]') + .find('input[type="checkbox"]') + .each(function () { + var $parent = $(this); + $parent.prop('checked', true); + checkParents($tab, $parent, className); + }); + } + + function uncheckChildren($tab, $checkBox) { + var featureName = $checkBox + .closest('.custom-checkbox') + .attr('data-feature-name'); + if (!featureName) { + return; } - }); + + $tab.find('.custom-checkbox') + .filter('[data-parent-name="' + featureName + '"]') + .find('input[type="checkbox"]') + .each(function () { + var $child = $(this); + $child.prop('checked', false); + uncheckChildren($tab, $child); + }); + } + + this.initDom = function ($el) { + $el.find('.tab-pane').each(function () { + var $tab = $(this); + $tab.find('input[type="checkbox"]') + .each(function () { + var $checkBox = $(this); + $checkBox.change(function () { + if ($checkBox.is(':checked')) { + checkParents($tab, $checkBox, '.custom-checkbox') + } else { + uncheckChildren($tab, $checkBox); + } + }); + }); + + $tab.find('.form-control') + .each(function () { + var $element = $(this); + $element.change(function () { + checkParents($tab, $element, '.form-group') + }); + }); + }); + + $(function () { + $('.custom-scroll-content').mCustomScrollbar({ + theme: 'minimal-dark', + }); + $('.custom-scroll-container > .col-4').mCustomScrollbar({ + theme: 'minimal-dark', + }); + }); + }; }; })(jQuery); diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbTestModule.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbTestModule.cs index 04d4ac916f..fdafebef51 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbTestModule.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.MongoDB.Tests/Volo/Abp/FeatureManagement/MongoDB/AbpFeatureManagementMongoDbTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.FeatureManagement.MongoDB { @@ -12,14 +13,19 @@ namespace Volo.Abp.FeatureManagement.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } -} \ No newline at end of file +} 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..fca4cf1913 --- /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..ca813229c3 --- /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/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/Features/IdentityTwoFactorBehaviourFeatureHelper.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityTwoFactorBehaviourFeatureHelper.cs new file mode 100644 index 0000000000..6d8d9f33a7 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Features/IdentityTwoFactorBehaviourFeatureHelper.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 IdentityTwoFactorBehaviourFeatureHelper + { + public static async Task Get([NotNull] 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/IdentityErrorCodes.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs index 1defe67eeb..b1a292d30e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs @@ -8,5 +8,7 @@ public const string DuplicateOrganizationUnitDisplayName = "Volo.Abp.Identity:010004"; public const string StaticRoleRenaming = "Volo.Abp.Identity:010005"; public const string StaticRoleDeletion = "Volo.Abp.Identity:010006"; + public const string UsersCanNotChangeTwoFactor = "Volo.Abp.Identity:010007"; + public const string CanNotChangeTwoFactor = "Volo.Abp.Identity:010008"; } } 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..63ad1f2c3e 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 @@ -67,6 +67,8 @@ "Volo.Abp.Identity:010004": "There is already an organization unit with name {0}. Two units with same name can not be created in same level.", "Volo.Abp.Identity:010005": "Static roles can not be renamed.", "Volo.Abp.Identity:010006": "Static roles can not be deleted.", + "Volo.Abp.Identity:010007": "You can't change your two factor setting.", + "Volo.Abp.Identity:010008": "It's not allowed to change two factor setting.", "Identity.OrganizationUnit.MaxUserMembershipCount": "Maximum allowed organization unit membership count for a user", "Permission:IdentityManagement": "Identity management", "Permission:RoleManagement": "Role management", @@ -76,6 +78,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 +111,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." } } 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..9bdecca7b2 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": "Zorunlu", "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 iki faktörlü kimlik doğrulama davranışını değiştirmelerine izin verin.", + "Description:Abp.Identity.UsersCanChange": "Kullanıcıların iki faktörlü kimlik doğrulama davranışını değiştirmelerine 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..74859aa0f3 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 @@ -61,10 +61,14 @@ "Volo.Abp.Identity:UserNameNotFound": "用户 {0} 不存在.", "Volo.Abp.Identity:UserNotInRole": "用户不具有 '{0}' 角色.", "Volo.Abp.Identity:PasswordConfirmationFailed": "密码或确认密码不一致.", + "Volo.Abp.Identity:010001": "你无法删除自己的帐户!", + "Volo.Abp.Identity:010002": "不能为用户设置超过{MaxUserMembershipCount}个组织单位!", + "Volo.Abp.Identity:010003": "无法更改外部登录用户的密码!", + "Volo.Abp.Identity:010004": "已存在名为 {0} 的组织单位. 无法在同一级别创建相同名称的组织单位.", "Volo.Abp.Identity:010005": "无法重命名静态角色.", "Volo.Abp.Identity:010006": "无法删除静态角色.", - "Volo.Abp.Identity:010004": "已存在名为 {0} 的组织单位. 无法在同一级别创建相同名称的组织单位.", - "Volo.Abp.Identity:010001": "您无法删除自己的帐户!", + "Volo.Abp.Identity:010007": "你不能修改你的双因素身份验证设置", + "Volo.Abp.Identity:010008": "不允许修改双因素身份验证设置.", "Permission:IdentityManagement": "身份标识管理", "Permission:RoleManagement": "角色管理", "Permission:Create": "创建", @@ -73,6 +77,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 +108,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/Localization/zh-Hant.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json index 2881bd886a..54298f530d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json @@ -67,6 +67,8 @@ "Volo.Abp.Identity:010004": "組織內單位內已包含名稱 {0}.同一層級組織,兩個單位不能有相同名稱", "Volo.Abp.Identity:010005": "無法重命名靜態角色.", "Volo.Abp.Identity:010006": "無法刪除靜態角色.", + "Volo.Abp.Identity:010007": "你不能修改你的雙因素身份驗證設置", + "Volo.Abp.Identity:010008": "不允許修改雙因素身份驗證設置.", "Identity.OrganizationUnit.MaxUserMembershipCount": "允許一個使用者至多可隸屬在幾個組織單位", "Permission:IdentityManagement": "身份識別管理", "Permission:RoleManagement": "角色管理", 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.Shared/Volo/Abp/Identity/Settings/IdentityTwoFactorBehaviourSettingHelper.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentityTwoFactorBehaviourSettingHelper.cs new file mode 100644 index 0000000000..46d7d414a6 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentityTwoFactorBehaviourSettingHelper.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Identity.Features; +using Volo.Abp.Settings; + +namespace Volo.Abp.Identity.Settings +{ + public static class IdentityTwoFactorBehaviourSettingHelper + { + public static async Task Get([NotNull] 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/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/IdentityTwoFactorManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityTwoFactorManager.cs new file mode 100644 index 0000000000..75e74afa3c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityTwoFactorManager.cs @@ -0,0 +1,70 @@ +using System.Threading.Tasks; +using Volo.Abp.Domain.Services; +using Volo.Abp.Features; +using Volo.Abp.Identity.Features; +using Volo.Abp.Identity.Settings; +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 IdentityTwoFactorBehaviourFeatureHelper.Get(FeatureChecker); + if (feature == IdentityTwoFactorBehaviour.Optional) + { + var setting = await IdentityTwoFactorBehaviourSettingHelper.Get(SettingProvider); + if (setting == IdentityTwoFactorBehaviour.Optional) + { + return true; + } + } + + return false; + } + + public virtual async Task IsForcedEnableAsync() + { + var feature = await IdentityTwoFactorBehaviourFeatureHelper.Get(FeatureChecker); + if (feature == IdentityTwoFactorBehaviour.Forced) + { + return true; + } + + var setting = await IdentityTwoFactorBehaviourSettingHelper.Get(SettingProvider); + if (setting == IdentityTwoFactorBehaviour.Forced) + { + return true; + } + + return false; + } + + public virtual async Task IsForcedDisableAsync() + { + var feature = await IdentityTwoFactorBehaviourFeatureHelper.Get(FeatureChecker); + if (feature == IdentityTwoFactorBehaviour.Disabled) + { + return true; + } + + var setting = await IdentityTwoFactorBehaviourSettingHelper.Get(SettingProvider); + 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..ccc3e055f5 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,11 @@ 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.Identity.Settings; +using Volo.Abp.Settings; namespace Volo.Abp.Identity { @@ -56,12 +60,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 +78,8 @@ namespace Volo.Abp.Identity GuidGenerator = guidGenerator; Logger = logger; LookupNormalizer = lookupNormalizer; + FeatureChecker = featureChecker; + SettingProvider = settingProvider; ErrorDescriber = describer ?? new IdentityErrorDescriber(); } @@ -931,13 +942,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 IdentityTwoFactorBehaviourFeatureHelper.Get(FeatureChecker); + if (feature == IdentityTwoFactorBehaviour.Disabled) + { + return false; + } + if (feature == IdentityTwoFactorBehaviour.Forced) + { + return true; + } + + var setting = await IdentityTwoFactorBehaviourSettingHelper.Get(SettingProvider); + 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/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs index f730e5f445..101f4836a5 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; using Volo.Abp.PermissionManagement.MongoDB; namespace Volo.Abp.Identity.MongoDB @@ -9,19 +10,24 @@ namespace Volo.Abp.Identity.MongoDB typeof(AbpIdentityTestBaseModule), typeof(AbpPermissionManagementMongoDbModule), typeof(AbpIdentityMongoDbModule) - )] + )] public class AbpIdentityMongoDbTestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } } diff --git a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs index d1721c720f..ae32aa2d4a 100644 --- a/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs +++ b/modules/identityserver/test/Volo.Abp.IdentityServer.MongoDB.Tests/Volo/Abp/IdentityServer/AbpIdentityServerMongoDbTestModule.cs @@ -3,6 +3,7 @@ using Volo.Abp.Data; using Volo.Abp.Identity.MongoDB; using Volo.Abp.IdentityServer.MongoDB; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.IdentityServer { @@ -16,7 +17,7 @@ namespace Volo.Abp.IdentityServer { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + Guid.NewGuid().ToString("N"); @@ -24,6 +25,11 @@ namespace Volo.Abp.IdentityServer { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } } 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/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbTestModule.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbTestModule.cs index 8e3c7b2c22..d896cf01dc 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbTestModule.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.MongoDB.Tests/Volo/Abp/PermissionManagement/MongoDb/AbpPermissionManagementMongoDbTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.PermissionManagement.MongoDB { @@ -11,14 +12,19 @@ namespace Volo.Abp.PermissionManagement.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + - "Db_" + - Guid.NewGuid().ToString("N"); + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + "Db_" + + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } } diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/AbpSettingManagementMongoDbTestModule.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/AbpSettingManagementMongoDbTestModule.cs index ec221eba15..0e456a2df8 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/AbpSettingManagementMongoDbTestModule.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.MongoDB.Tests/Volo/Abp/SettingManagement/MongoDB/AbpSettingManagementMongoDbTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.SettingManagement.MongoDB { @@ -12,14 +13,19 @@ namespace Volo.Abp.SettingManagement.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } } diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs index 9584ec2d44..3bdcf0ad9c 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/AbpTenantManagementMongoDbTestModule.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.TenantManagement.MongoDB { @@ -12,14 +13,19 @@ namespace Volo.Abp.TenantManagement.MongoDB { public override void ConfigureServices(ServiceConfigurationContext context) { - var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + + var connectionString = MongoDbFixture.ConnectionString.EnsureEndsWith('/') + "Db_" + - Guid.NewGuid().ToString("N"); + Guid.NewGuid().ToString("N"); Configure(options => { options.ConnectionStrings.Default = connectionString; }); + + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; + }); } } } diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs index 1b3777f6a8..689f273178 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.MongoDB.Tests/Volo/Abp/TenantManagement/MongoDb/MongoDbFixture.cs @@ -1,5 +1,7 @@ using System; using Mongo2Go; +using MongoDB.Driver; +using Volo.Abp.MongoDB; namespace Volo.Abp.TenantManagement.MongoDB { diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index 142f3de79f..b3f6321b92 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -39,6 +39,7 @@ "@angular-builders/jest": "^10.0.0", "@angular-devkit/build-angular": "~0.1000.6", "@angular-devkit/build-ng-packagr": "~0.1000.6", + "@angular-devkit/schematics-cli": "^0.1001.1", "@angular/animations": "~10.0.10", "@angular/cli": "~10.0.6", "@angular/common": "~10.0.10", diff --git a/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html b/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html index b7c777a449..b98aa7e4ff 100644 --- a/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html +++ b/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html @@ -7,6 +7,7 @@ [nzData]="nodes" [nzTreeTemplate]="treeTemplate" [nzExpandedKeys]="expandedKeys" + [nzExpandedIcon]="expandedIconTemplate?.template" (nzExpandChange)="onExpandedKeysChange($event)" (nzCheckBoxChange)="onCheckboxChange($event)" (nzOnDrop)="onDrop($event)" @@ -18,7 +19,16 @@ [title]="node.title" (click)="onSelectedNodeChange(node)" > - {{ node.title }} + + + + {{ node.title }} +
diff --git a/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts b/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts index ac7a709048..7ba6280c0b 100644 --- a/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts +++ b/npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts @@ -9,6 +9,8 @@ import { } from '@angular/core'; import { NzFormatEmitEvent, NzFormatBeforeDropEvent } from 'ng-zorro-antd/tree'; import { of } from 'rxjs'; +import { TreeNodeTemplateDirective } from '../templates/tree-node-template.directive'; +import { ExpandedIconTemplateDirective } from '../templates/expanded-icon-template.directive'; export type DropEvent = NzFormatEmitEvent & { pos: number }; @@ -25,6 +27,8 @@ export class TreeComponent { dropPosition: number; @ContentChild('menu') menu: TemplateRef; + @ContentChild(TreeNodeTemplateDirective) customNodeTemplate: TreeNodeTemplateDirective; + @ContentChild(ExpandedIconTemplateDirective) expandedIconTemplate: ExpandedIconTemplateDirective; @Output() readonly checkedKeysChange = new EventEmitter(); @Output() readonly expandedKeysChange = new EventEmitter(); @Output() readonly selectedNodeChange = new EventEmitter(); diff --git a/npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts b/npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts new file mode 100644 index 0000000000..35706bd25f --- /dev/null +++ b/npm/ng-packs/packages/components/tree/src/lib/templates/expanded-icon-template.directive.ts @@ -0,0 +1,8 @@ +import { Directive, TemplateRef } from '@angular/core'; + +@Directive({ + selector: '[abpTreeExpandedIconTemplate],[abp-tree-expanded-icon-template]', +}) +export class ExpandedIconTemplateDirective { + constructor(public template: TemplateRef) {} +} diff --git a/npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts b/npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts new file mode 100644 index 0000000000..56c66af04b --- /dev/null +++ b/npm/ng-packs/packages/components/tree/src/lib/templates/tree-node-template.directive.ts @@ -0,0 +1,8 @@ +import { Directive, TemplateRef } from '@angular/core'; + +@Directive({ + selector: '[abpTreeNodeTemplate],[abp-tree-node-template]', +}) +export class TreeNodeTemplateDirective { + constructor(public template: TemplateRef) {} +} diff --git a/npm/ng-packs/packages/components/tree/src/lib/tree.module.ts b/npm/ng-packs/packages/components/tree/src/lib/tree.module.ts index f16ed0dd55..b645a69925 100644 --- a/npm/ng-packs/packages/components/tree/src/lib/tree.module.ts +++ b/npm/ng-packs/packages/components/tree/src/lib/tree.module.ts @@ -1,12 +1,18 @@ +import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NzTreeModule } from 'ng-zorro-antd/tree'; import { TreeComponent } from './components/tree.component'; -import { CommonModule } from '@angular/common'; +import { TreeNodeTemplateDirective } from './templates/tree-node-template.directive'; +import { ExpandedIconTemplateDirective } from './templates/expanded-icon-template.directive'; + +const templates = [TreeNodeTemplateDirective, ExpandedIconTemplateDirective]; + +const exported = [...templates, TreeComponent]; @NgModule({ imports: [CommonModule, NzTreeModule, NgbDropdownModule], - exports: [TreeComponent], - declarations: [TreeComponent], + exports: [...exported], + declarations: [...exported], }) export class TreeModule {} diff --git a/npm/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts b/npm/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts index 6c66315e69..d5d32f0146 100644 --- a/npm/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts +++ b/npm/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts @@ -48,8 +48,26 @@ export class TreeAdapter { } handleRemove({ key }: TreeNode) { - this.tree = createTreeFromList(this.list.filter(item => item.id !== key)); + this.updateTreeFromList(this.list.filter(item => item.id !== key)); + } + + handleUpdate({ key, children }: { key: string; children: T[] }) { + /** + * When we need to update a node with new children, first we need to remove any descendant nodes. + * If we remove immediate children and create a new tree, any other descendant nodes will be removed + * and we won't need to recursively remove sub children. + * Then, you simply add back the new children and create a new tree. + */ + const listWithDescendantNodesRemoved = this.updateTreeFromList( + this.list.filter(item => item.parentId !== key), + ); + this.updateTreeFromList(listWithDescendantNodesRemoved.concat(children)); + } + + updateTreeFromList(list: T[]) { + this.tree = createTreeFromList(list); this.list = createListFromTree(this.tree); + return this.list; } } diff --git a/npm/ng-packs/packages/components/tree/src/public-api.ts b/npm/ng-packs/packages/components/tree/src/public-api.ts index df82ac0f93..1f3dd48e00 100644 --- a/npm/ng-packs/packages/components/tree/src/public-api.ts +++ b/npm/ng-packs/packages/components/tree/src/public-api.ts @@ -1,3 +1,5 @@ export * from './lib/tree.module'; export * from './lib/components/tree.component'; export * from './lib/utils/nz-tree-adapter'; +export * from './lib/templates/tree-node-template.directive'; +export * from './lib/templates/expanded-icon-template.directive'; diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index 597e114d4a..8cf9a90886 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -187,7 +187,7 @@ export class CoreModule { }, { provide: HTTP_INTERCEPTORS, - useClass: ApiInterceptor, + useExisting: ApiInterceptor, multi: true, }, { diff --git a/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts b/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts index de01335058..b27c7fa80a 100644 --- a/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts +++ b/npm/ng-packs/packages/core/src/lib/interceptors/api.interceptor.ts @@ -1,41 +1,47 @@ import { Injectable } from '@angular/core'; -import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; +import { HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders } from '@angular/common/http'; import { OAuthService } from 'angular-oauth2-oidc'; import { Store } from '@ngxs/store'; import { SessionState } from '../states'; import { StartLoader, StopLoader } from '../actions/loader.actions'; import { finalize } from 'rxjs/operators'; -@Injectable() +@Injectable({ + providedIn: 'root', +}) export class ApiInterceptor implements HttpInterceptor { constructor(private oAuthService: OAuthService, private store: Store) {} intercept(request: HttpRequest, next: HttpHandler) { this.store.dispatch(new StartLoader(request)); + return next + .handle( + request.clone({ + setHeaders: this.getAdditionalHeaders(request.headers), + }), + ) + .pipe(finalize(() => this.store.dispatch(new StopLoader(request)))); + } + + getAdditionalHeaders(existingHeaders?: HttpHeaders) { const headers = {} as any; const token = this.oAuthService.getAccessToken(); - if (!request.headers.has('Authorization') && token) { + if (!existingHeaders?.has('Authorization') && token) { headers['Authorization'] = `Bearer ${token}`; } const lang = this.store.selectSnapshot(SessionState.getLanguage); - if (!request.headers.has('Accept-Language') && lang) { + if (!existingHeaders?.has('Accept-Language') && lang) { headers['Accept-Language'] = lang; } const tenant = this.store.selectSnapshot(SessionState.getTenant); - if (!request.headers.has('__tenant') && tenant) { + if (!existingHeaders?.has('__tenant') && tenant) { headers['__tenant'] = tenant.id; } - return next - .handle( - request.clone({ - setHeaders: headers, - }), - ) - .pipe(finalize(() => this.store.dispatch(new StopLoader(request)))); + return headers; } } diff --git a/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts b/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts index 1f3bb65f4e..c974f842a2 100644 --- a/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts +++ b/npm/ng-packs/packages/core/src/lib/models/application-configuration.ts @@ -63,10 +63,15 @@ export namespace ApplicationConfiguration { export interface CurrentUser { email: string; + emailVerified: false; id: string; isAuthenticated: boolean; roles: string[]; tenantId: string; userName: string; + name: string; + phoneNumber: string; + phoneNumberVerified: boolean; + surName: string; } } diff --git a/npm/ng-packs/packages/core/src/lib/models/rest.ts b/npm/ng-packs/packages/core/src/lib/models/rest.ts index 2f0e175002..03d70161bc 100644 --- a/npm/ng-packs/packages/core/src/lib/models/rest.ts +++ b/npm/ng-packs/packages/core/src/lib/models/rest.ts @@ -34,7 +34,7 @@ export namespace Rest { [param: string]: any; }; reportProgress?: boolean; - responseType?: ResponseType; + responseType?: 'arraybuffer' | 'blob' | 'json' | 'text'; url: string; withCredentials?: boolean; } 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/list.service.ts b/npm/ng-packs/packages/core/src/lib/services/list.service.ts index 3ebb6dc193..1a1e7c1aeb 100644 --- a/npm/ng-packs/packages/core/src/lib/services/list.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/list.service.ts @@ -17,6 +17,8 @@ import { LIST_QUERY_DEBOUNCE_TIME } from '../tokens/list.token'; export class ListService implements OnDestroy { private _filter = ''; set filter(value: string) { + if (this._filter !== value) this._page = 0; + this._filter = value; this.get(); } 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/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html index cceef3409c..f857487e1e 100644 --- a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html +++ b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html @@ -9,19 +9,22 @@