diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md index 26fd9c77d8..4ac350717d 100644 --- a/docs/en/Authorization.md +++ b/docs/en/Authorization.md @@ -1,3 +1,239 @@ -## Authorization +# Authorization -TODO \ No newline at end of file +Authorization is used to check if a user is allowed to perform some specific operation in the application. + +ABP extends [ASP.NET Core's Authorization system](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction) by adding **permissions** as auto [policies](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies) and allowing authorization system to be usable in the **[application services](Application-Services.md)** too. + +So, all the ASP.NET Core authorization features and the documentation are valid in an ABP based application. This document focuses on the features added on top of them. + +## Authorize Attribute + +ASP.NET Core defines the [**Authorize**](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/simple) attribute that can be used for an action, a controller or a page. ABP allows you to use the same attribute for an [application service](Application-Services.md) too. + +Example: + +````csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Services; + +namespace Acme.BookStore +{ + [Authorize] + public class AuthorAppService : ApplicationService, IAuthorAppService + { + public Task> GetListAsync() + { + ... + } + + [AllowAnonymous] + public Task GetAsync(Guid id) + { + ... + } + + [Authorize("BookStore_Author_Create")] + public Task CreateAsync(CreateAuthorDto input) + { + ... + } + } +} + +```` + +* `Authorize` attribute forces user to login to the application in order to use the `AuthorAppService` methods. So, `GetListAsync` method is only available to authenticated users. +* `AllowAnonymous` suppress the authentication. So, `GetAsync` method is available to everyone including unauthorized users. +* `[Authorize("BookStore_Author_Create")]` defines a policy (see [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies)) that is checked to authorize the current user. + +"BookStore_Author_Create" is an arbitrary policy name. If you declare an attribute like that, ASP.NET Core authorization system expects a policy defined before. + +You can of course implement your policies as stated in the ASP.NET Core documentation. But for simple true/false cases (that means a policy was granted a user or not), ABP defines the permission system explained in the next section. + +## Permission System + +A permission is a simple policy where it is granted or prohibited for a particular user, role or client. + +### Defining Permissions + +To define permissions, create a class inheriting from the `PermissionDefinitionProvider` as shown below: + +````csharp +using Volo.Abp.Authorization.Permissions; + +namespace Acme.BookStore.Permissions +{ + public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var myGroup = context.AddGroup("BookStore"); + + myGroup.AddPermission("BookStore_Author_Create"); + } + } +} +```` + +> ABP will automatically discover this class. No additional configuration required. + +In the `Define` method, you first need to add a **permission group** (or get an existing group) then add **permissions** to this group. + +When you define a permission, it becomes usable in the ASP.NET Core authorization system as a **policy** name. It also becomes visible in the UI. See permissions dialog for a role: + +![authorization-new-permission-ui](images/authorization-new-permission-ui.png) + +* The "BookStore" group is shown as a new tab on the left side. +* "BookStore_Author_Create" on the right side is the permission name. You can grant or prohibit it for the role. + +When you save the dialog, it is saved to the database and used in the authorization system. + +> The screen above is available when you have installed the identity module, which is basically used for user and role management. Startup templates come with the identity module pre-installed. + +#### Localizing the Permission Name + +"BookStore_Author_Create" is not a good permission name on the UI. Fortunately, `AddPermission` and `AddGroup` methods can take `LocalizableString` as second parameters: + +````csharp +var myGroup = context.AddGroup( + "BookStore", + LocalizableString.Create("BookStore") +); + +myGroup.AddPermission( + "BookStore_Author_Create", + LocalizableString.Create("Permission:BookStore_Author_Create") +); +```` + +Then you can define texts for "BookStore" and "Permission:BookStore_Author_Create" keys in the localization file: + +````json +"BookStore": "Book Store", +"Permission:BookStore_Author_Create": "Creating a new author" +```` + +> See the [localization document](Localization.md) for more details on the localization system. + +The localized UI will be like that: + +![authorization-new-permission-ui-localized](images/authorization-new-permission-ui-localized.png) + +#### Multi-Tenancy + +ABP supports [multi-tenancy](Multi-Tenancy.md) as a first class citizen. You can define multi-tenancy side option while defining a new permission. It gets one of the three values defined below: + +* **Host**: The permission is available only for the host side. +* **Tenant**: The permission is available only for the tenant side. +* **Both** (default): The permission is available both for tenant and host sides. + +> If your application is not multi-tenant, you can ignore this option. + +To set the multi-tenancy side option, pass to the third parameter of the `AddPermission` method: + +````csharp +myGroup.AddPermission( + "BookStore_Author_Create", + LocalizableString.Create("Permission:BookStore_Author_Create"), + multiTenancySide: MultiTenancySides.Tenant //set multi-tenancy side! +); +```` + +#### Child Permissions + +A permission may have child permissions. It is especially useful when you want to create a hierarchical permission tree where a permission may have additional sub permissions which are available only if the parent permission has been granted. + +Example definition: + +````csharp +var authorManagement = myGroup.AddPermission("Author_Management"); +authorManagement.AddChild("Author_Management_Create_Books"); +authorManagement.AddChild("Author_Management_Edit_Books"); +authorManagement.AddChild("Author_Management_Delete_Books"); +```` + +The result on the UI is shown below (you probably want to localize permissions for your application): + +![authorization-new-permission-ui-hierarcy](images/authorization-new-permission-ui-hierarcy.png) + +For the example code, it is assumed that a role/user with "Author_Management" permission granted may have additional permissions. Then a typical application service that checks permissions can be defined as shown below: + +````csharp +[Authorize("Author_Management")] +public class AuthorAppService : ApplicationService, IAuthorAppService +{ + public Task> GetListAsync() + { + ... + } + + public Task GetAsync(Guid id) + { + ... + } + + [Authorize("Author_Management_Create_Books")] + public Task CreateAsync(CreateAuthorDto input) + { + ... + } + + [Authorize("Author_Management_Edit_Books")] + public Task UpdateAsync(CreateAuthorDto input) + { + ... + } + + [Authorize("Author_Management_Delete_Books")] + public Task DeleteAsync(CreateAuthorDto input) + { + ... + } +} +```` + +* `GetListAsync` and `GetAsync` will be available to users if they have `Author_Management` permission granted. +* Other methods require additional permissions. + +## IAuthorizationService + +ASP.NET Core provides the `IAuthorizationService` that can be used to check for authorization. Once you inject, you can use it in your code to conditionally control the authorization. + +Example: + +````csharp +public async Task CreateAsync(CreateAuthorDto input) +{ + var result = await AuthorizationService + .AuthorizeAsync("Author_Management_Create_Books"); + if (result.Succeeded == false) + { + //throw exception + throw new AbpAuthorizationException("..."); + } + + //continue to the normal flow... +} +```` + +> `AuthorizationService` is available as a property when you derive from ABP's `ApplicationService` base class. Since it is widely used in application services, `ApplicationService` pre-injects it for you. Otherwise, you can directly [inject](Dependency-Injection.md) it into your class. + +Since this is a typical code block, ABP provides extension methods to simplify it. + +Example: + +````csharp +public async Task CreateAsync(CreateAuthorDto input) +{ + await AuthorizationService.CheckAsync("Author_Management_Create_Books"); + + //continue to the normal flow... +} +```` + +`CheckAsync` extension method throws `AbpAuthorizationException` if current user/client has not granted for the given permission. There is also `IsGrantedAsync` extension method that returns `true` or `false`. + +> Tip: Prefer to use the `Authorize` attribute wherever possible, since it is declarative & simple. Use `IAuthorizationService` if you need to conditionally check a permission and run a business code based on the permission check. \ No newline at end of file diff --git a/docs/en/images/authorization-new-permission-ui-hierarcy.png b/docs/en/images/authorization-new-permission-ui-hierarcy.png new file mode 100644 index 0000000000..078d67d50f Binary files /dev/null and b/docs/en/images/authorization-new-permission-ui-hierarcy.png differ diff --git a/docs/en/images/authorization-new-permission-ui-localized.png b/docs/en/images/authorization-new-permission-ui-localized.png new file mode 100644 index 0000000000..610ce39faf Binary files /dev/null and b/docs/en/images/authorization-new-permission-ui-localized.png differ diff --git a/docs/en/images/authorization-new-permission-ui.png b/docs/en/images/authorization-new-permission-ui.png new file mode 100644 index 0000000000..554f27e78d Binary files /dev/null and b/docs/en/images/authorization-new-permission-ui.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 534b7c6d92..e71b58eba0 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -248,7 +248,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MailKit", "src\Vol EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MailKit.Tests", "test\Volo.Abp.MailKit.Tests\Volo.Abp.MailKit.Tests.csproj", "{70DD6E17-B98B-4B00-8F38-C489E291BB53}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectMapping.Tests", "test\Volo.Abp.ObjectMapping.Tests\Volo.Abp.ObjectMapping.Tests.csproj", "{667F5544-C1EB-447C-96FD-9B757F04DE2B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectMapping.Tests", "test\Volo.Abp.ObjectMapping.Tests\Volo.Abp.ObjectMapping.Tests.csproj", "{667F5544-C1EB-447C-96FD-9B757F04DE2B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Ddd.Application.Contracts", "src\Volo.Abp.Ddd.Application.Contracts\Volo.Abp.Ddd.Application.Contracts.csproj", "{73559227-EBF0-475F-835B-1FF0CD9132AA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -744,6 +746,10 @@ Global {667F5544-C1EB-447C-96FD-9B757F04DE2B}.Debug|Any CPU.Build.0 = Debug|Any CPU {667F5544-C1EB-447C-96FD-9B757F04DE2B}.Release|Any CPU.ActiveCfg = Release|Any CPU {667F5544-C1EB-447C-96FD-9B757F04DE2B}.Release|Any CPU.Build.0 = Release|Any CPU + {73559227-EBF0-475F-835B-1FF0CD9132AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73559227-EBF0-475F-835B-1FF0CD9132AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73559227-EBF0-475F-835B-1FF0CD9132AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73559227-EBF0-475F-835B-1FF0CD9132AA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -871,6 +877,7 @@ Global {0CAED4CC-1CFD-4092-A326-AFE4DB3A9AB4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {70DD6E17-B98B-4B00-8F38-C489E291BB53} = {447C8A77-E5F0-4538-8687-7383196D04EA} {667F5544-C1EB-447C-96FD-9B757F04DE2B} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {73559227-EBF0-475F-835B-1FF0CD9132AA} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Error/Index.cshtml.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Error/Index.cshtml.cs index af779b4c0a..66ea202442 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Error/Index.cshtml.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Pages/Error/Index.cshtml.cs @@ -2,7 +2,7 @@ using System; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; +using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.Http; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Pages.Error diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 1600419ccb..8bce7bfd62 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -111,6 +111,7 @@ namespace Volo.Abp.AspNetCore.Mvc var application = context.Services.GetSingletonInstance(); partManager.FeatureProviders.Add(new AbpConventionalControllerFeatureProvider(application)); + partManager.ApplicationParts.Add(new AssemblyPart(typeof(AbpAspNetCoreMvcModule).Assembly)); Configure(mvcOptions => { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs index 8ff0846b74..28c1c18fda 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; using Volo.Abp.Json; diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs index e2c930f39c..22c0079571 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.RequestLocalization; using Microsoft.Extensions.DependencyInjection; using Volo.Abp; using Volo.Abp.AspNetCore.Auditing; -using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; +using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.Tracing; using Volo.Abp.AspNetCore.Uow; using Volo.Abp.DependencyInjection; diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs index e108a71b24..cdd921dae4 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs @@ -4,12 +4,13 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; +using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Uow; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; using Volo.Abp.Json; -namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +namespace Volo.Abp.AspNetCore.ExceptionHandling { public class AbpExceptionHandlingMiddleware : IMiddleware, ITransientDependency { diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs index af22ff2f7e..d1ae5dd59a 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultExceptionToErrorInfoConverter.cs @@ -15,7 +15,7 @@ using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Validation; -namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +namespace Volo.Abp.AspNetCore.ExceptionHandling { public class DefaultExceptionToErrorInfoConverter : IExceptionToErrorInfoConverter, ITransientDependency { diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs index 7625fa1d63..d9079fc347 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/DefaultHttpExceptionStatusCodeFinder.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; @@ -9,7 +8,7 @@ using Volo.Abp.Domain.Entities; using Volo.Abp.ExceptionHandling; using Volo.Abp.Validation; -namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +namespace Volo.Abp.AspNetCore.ExceptionHandling { public class DefaultHttpExceptionStatusCodeFinder : IHttpExceptionStatusCodeFinder, ITransientDependency { diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/ExceptionHttpStatusCodeOptions.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/ExceptionHttpStatusCodeOptions.cs index b987eb4073..d8766219f8 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/ExceptionHttpStatusCodeOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/ExceptionHttpStatusCodeOptions.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Net; -namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +namespace Volo.Abp.AspNetCore.ExceptionHandling { public class ExceptionHttpStatusCodeOptions { diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/IExceptionToErrorInfoConverter.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/IExceptionToErrorInfoConverter.cs index ad5e5fa1aa..a25206a918 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/IExceptionToErrorInfoConverter.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/IExceptionToErrorInfoConverter.cs @@ -1,7 +1,7 @@ using System; using Volo.Abp.Http; -namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +namespace Volo.Abp.AspNetCore.ExceptionHandling { /// /// This interface can be implemented to convert an object to an object. diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/IHttpExceptionStatusCodeFinder.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/IHttpExceptionStatusCodeFinder.cs index 0566a247e1..726d738b7c 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/IHttpExceptionStatusCodeFinder.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/IHttpExceptionStatusCodeFinder.cs @@ -2,7 +2,7 @@ using System.Net; using Microsoft.AspNetCore.Http; -namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling +namespace Volo.Abp.AspNetCore.ExceptionHandling { public interface IHttpExceptionStatusCodeFinder { diff --git a/framework/src/Volo.Abp.Authorization/Microsoft/Extensions/DependencyInjection/AbpAuthorizationServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Authorization/Microsoft/Extensions/DependencyInjection/AbpAuthorizationServiceCollectionExtensions.cs index 4294f572de..02f17fe5de 100644 --- a/framework/src/Volo.Abp.Authorization/Microsoft/Extensions/DependencyInjection/AbpAuthorizationServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.Authorization/Microsoft/Extensions/DependencyInjection/AbpAuthorizationServiceCollectionExtensions.cs @@ -8,13 +8,6 @@ namespace Microsoft.Extensions.DependencyInjection { public static class AbpAuthorizationServiceCollectionExtensions { - //TODO: Remove this and use AddAlwaysAllowAuthorization - [Obsolete("Use AddAlwaysAllowAuthorization instead")] - public static IServiceCollection AddAlwaysAllowPermissionChecker(this IServiceCollection services) - { - return services.Replace(ServiceDescriptor.Singleton()); - } - public static IServiceCollection AddAlwaysAllowAuthorization(this IServiceCollection services) { services.Replace(ServiceDescriptor.Singleton()); diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo.Abp.Ddd.Application.Contracts.csproj b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo.Abp.Ddd.Application.Contracts.csproj new file mode 100644 index 0000000000..0b6eaee362 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo.Abp.Ddd.Application.Contracts.csproj @@ -0,0 +1,20 @@ + + + + + + netstandard2.0 + Volo.Abp.Ddd.Application.Contracts + Volo.Abp.Ddd.Application.Contracts + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/AbpDddApplicationContractsModule.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/AbpDddApplicationContractsModule.cs new file mode 100644 index 0000000000..e107072221 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/AbpDddApplicationContractsModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Auditing; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Application +{ + [DependsOn( + typeof(AbpAuditingModule) + )] + public class AbpDddApplicationContractsModule : AbpModule + { + } +} diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/AuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/AuditedEntityDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/CreationAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/CreationAuditedEntityDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/EntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/EntityDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/EntityDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/EntityDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/FullAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/FullAuditedEntityDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IEntityDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IEntityDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IEntityDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IHasTotalCount.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IHasTotalCount.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IHasTotalCount.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IHasTotalCount.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/ILimitedResultRequest.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ILimitedResultRequest.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/ILimitedResultRequest.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ILimitedResultRequest.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IListResult.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IListResult.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IListResult.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IListResult.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IPagedAndSortedResultRequest.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IPagedAndSortedResultRequest.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IPagedAndSortedResultRequest.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IPagedAndSortedResultRequest.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IPagedResult.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IPagedResult.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IPagedResult.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IPagedResult.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IPagedResultRequest.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IPagedResultRequest.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/IPagedResultRequest.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/IPagedResultRequest.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/ISortedResultRequest.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ISortedResultRequest.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/ISortedResultRequest.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ISortedResultRequest.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/LimitedResultRequestDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/LimitedResultRequestDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/LimitedResultRequestDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/LimitedResultRequestDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/ListResultDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ListResultDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/ListResultDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ListResultDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/PagedAndSortedResultRequestDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/PagedAndSortedResultRequestDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/PagedAndSortedResultRequestDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/PagedAndSortedResultRequestDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/PagedResultDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/PagedResultDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/PagedResultDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/PagedResultDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/PagedResultRequestDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/PagedResultRequestDto.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/PagedResultRequestDto.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/PagedResultRequestDto.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/IApplicationService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IApplicationService.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/IApplicationService.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/IApplicationService.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ICrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs similarity index 100% rename from framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ICrudAppService.cs rename to framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj b/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj index cf3cb85cd0..09198e47f6 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj +++ b/framework/src/Volo.Abp.Ddd.Application/Volo.Abp.Ddd.Application.csproj @@ -1,4 +1,4 @@ - + @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs index e5ade49532..14d7bc686f 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/AbpDddApplicationModule.cs @@ -16,6 +16,7 @@ namespace Volo.Abp.Application { [DependsOn( typeof(AbpDddDomainModule), + typeof(AbpDddApplicationContractsModule), typeof(AbpSecurityModule), typeof(AbpObjectMappingModule), typeof(AbpValidationModule), @@ -30,6 +31,7 @@ namespace Volo.Abp.Application { Configure(options => { + //TODO: Should we move related items to their own projects? options.IgnoredInterfaces.AddIfNotContains(typeof(IRemoteService)); options.IgnoredInterfaces.AddIfNotContains(typeof(IApplicationService)); options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled)); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 3604913301..3d4b45d75d 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -30,9 +30,9 @@ namespace Volo.Abp.EntityFrameworkCore { protected virtual Guid? CurrentTenantId => CurrentTenant?.Id; - protected virtual bool IsMultiTenantFilterEnabled => DataFilter.IsEnabled(); + protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled() ?? false; - protected virtual bool IsSoftDeleteFilterEnabled => DataFilter.IsEnabled(); + protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled() ?? false; public ICurrentTenant CurrentTenant { get; set; } @@ -316,17 +316,17 @@ namespace Volo.Abp.EntityFrameworkCore protected virtual void SetCreationAuditProperties(EntityEntry entry) { - AuditPropertySetter.SetCreationProperties(entry.Entity); + AuditPropertySetter?.SetCreationProperties(entry.Entity); } protected virtual void SetModificationAuditProperties(EntityEntry entry) { - AuditPropertySetter.SetModificationProperties(entry.Entity); + AuditPropertySetter?.SetModificationProperties(entry.Entity); } protected virtual void SetDeletionAuditProperties(EntityEntry entry) { - AuditPropertySetter.SetDeletionProperties(entry.Entity); + AuditPropertySetter?.SetDeletionProperties(entry.Entity); } protected virtual void ConfigureBaseProperties(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType) diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs index ecde843177..2835c086b8 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Http.Client; using Volo.Abp.Http.DynamicProxying; @@ -23,11 +22,6 @@ namespace Volo.Abp.Http { options.RemoteServices.Default = new RemoteServiceConfiguration("/"); }); - - //This is needed after ASP.NET Core 3.0 upgrade. - context.Services.AddMvc() - .PartManager.ApplicationParts - .Add(new AssemblyPart(typeof(AbpAspNetCoreMvcModule).Assembly)); } } } diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index aeae7ef1b4..893aad6043 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.Client; using Volo.Abp.TestApp.Application; using Volo.Abp.TestApp.Application.Dto; using Volo.Abp.TestApp.Domain; @@ -103,6 +103,15 @@ namespace Volo.Abp.Http.DynamicProxying personInDb.Age.ShouldBe(person.Age); } + [Fact] + public async Task GetWithAuthorized() + { + (await Assert.ThrowsAsync(async () => + { + await _peopleAppService.GetWithAuthorized(); + })).Error.Message.ShouldContain("Authorization"); + } + [Fact] public async Task GetWithComplexType() { diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs index cf2393869d..38b0e87787 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs @@ -14,6 +14,8 @@ namespace Volo.Abp.TestApp.Application Task RemovePhone(Guid id, string number); + Task GetWithAuthorized(); + Task GetWithComplexType(GetWithComplexTypeInput input); } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index e21cc14d08..d7909542d4 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.TestApp.Domain; using Volo.Abp.Domain.Repositories; @@ -46,6 +47,12 @@ namespace Volo.Abp.TestApp.Application Repository.Update(person); } + [Authorize] + public virtual Task GetWithAuthorized() + { + return Task.CompletedTask; + } + public Task GetWithComplexType(GetWithComplexTypeInput input) { return Task.FromResult(input); diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 38daad78b0..f74a47fb50 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -87,7 +87,7 @@ - + diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index 1b5c446e04..d35e60fd5e 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -187,7 +187,7 @@ namespace Volo.Docs.Pages.Documents.Project ProjectSelectItems = projects.Items.Select(p => new SelectListItem { Text = p.Name, - Value = p.Id != Project.Id ? "/" + DocumentsUrlPrefix + LanguageCode + "/" + p.ShortName + "/" + DocsAppConsts.Latest : null, + Value = p.Id != Project.Id ? DocumentsUrlPrefix + LanguageCode + "/" + p.ShortName + "/" + DocsAppConsts.Latest : null, Selected = p.Id == Project.Id }).ToList(); } diff --git a/npm/ng-packs/.gitignore b/npm/ng-packs/.gitignore index 7f282e1aa3..3915996991 100644 --- a/npm/ng-packs/.gitignore +++ b/npm/ng-packs/.gitignore @@ -3,6 +3,7 @@ # compiled output /tmp /out-tsc +/dist/dev-app # Only exists if Bazel was run /bazel-out diff --git a/npm/ng-packs/angular.json b/npm/ng-packs/angular.json index fb32a2d757..a06c34c90e 100644 --- a/npm/ng-packs/angular.json +++ b/npm/ng-packs/angular.json @@ -434,13 +434,30 @@ "tsConfig": "apps/dev-app/tsconfig.app.json", "aot": false, "assets": ["apps/dev-app/src/favicon.ico", "apps/dev-app/src/assets"], + "extractCss": true, "styles": [ "apps/dev-app/src/styles.scss", "node_modules/bootstrap/dist/css/bootstrap.min.css", - "node_modules/font-awesome/css/font-awesome.min.css", - "node_modules/primeng/resources/themes/nova-light/theme.css", - "node_modules/primeicons/primeicons.css", - "node_modules/primeng/resources/primeng.min.css" + { + "input": "node_modules/font-awesome/css/font-awesome.min.css", + "lazy": true, + "bundleName": "font-awesome.min" + }, + { + "input": "node_modules/primeng/resources/themes/nova-light/theme.css", + "lazy": true, + "bundleName": "primeng-nova-light-theme" + }, + { + "input": "node_modules/primeicons/primeicons.css", + "lazy": true, + "bundleName": "primeicons" + }, + { + "input": "node_modules/primeng/resources/primeng.min.css", + "lazy": true, + "bundleName": "primeng.min" + } ], "scripts": [] }, diff --git a/npm/ng-packs/apps/dev-app/src/app/app.component.ts b/npm/ng-packs/apps/dev-app/src/app/app.component.ts index bf2a27962a..e89c73184d 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app.component.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app.component.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import { LazyLoadService } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', @@ -7,4 +8,17 @@ import { Component } from '@angular/core'; `, }) -export class AppComponent {} +export class AppComponent implements OnInit { + constructor(private lazyLoadService: LazyLoadService) {} + + ngOnInit() { + this.lazyLoadService + .load( + ['primeng.min.css', 'primeicons.css', 'primeng-nova-light-theme.css', 'font-awesome.min.css'], + 'style', + null, + 'head', + ) + .subscribe(); + } +} 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 9912810399..ad34955dac 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 @@ -1,10 +1,12 @@ +import { ABP } from './common'; + export namespace ApplicationConfiguration { export interface Response { localization: Localization; auth: Auth; - setting: Setting; + setting: Value; currentUser: CurrentUser; - features: Features; + features: Value; } export interface Localization { @@ -32,8 +34,8 @@ export namespace ApplicationConfiguration { [key: string]: boolean; } - export interface Setting { - values: { [key: string]: 'Abp.Localization.DefaultLanguage' }; + export interface Value { + values: ABP.Dictionary; } export interface CurrentUser { @@ -42,8 +44,4 @@ export namespace ApplicationConfiguration { tenantId: string; userName: string; } - - export interface Features { - values: Setting; - } } diff --git a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts index a6e8eb1f5c..0cdf546ff0 100644 --- a/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/lazy-load.service.ts @@ -9,50 +9,68 @@ export class LazyLoadService { loadedLibraries: { [url: string]: ReplaySubject } = {}; load( - url: string, + urlOrUrls: string | string[], type: 'script' | 'style', content: string = '', targetQuery: string = 'body', position: InsertPosition = 'afterend', ): Observable { - if (!url && !content) return; - const key = url ? url.slice(url.lastIndexOf('/') + 1) : uuid(); - - if (this.loadedLibraries[key]) { - return this.loadedLibraries[key].asObservable(); + if (!urlOrUrls && !content) { + return; + } else if (!urlOrUrls && content) { + urlOrUrls = [null]; } - this.loadedLibraries[key] = new ReplaySubject(); - - let library; - if (type === 'script') { - library = document.createElement('script'); - library.type = 'text/javascript'; - if (url) { - (library as HTMLScriptElement).src = url; - } - - (library as HTMLScriptElement).text = content; - } else if (url) { - library = document.createElement('link'); - library.type = 'text/css'; - (library as HTMLLinkElement).rel = 'stylesheet'; - - if (url) { - (library as HTMLLinkElement).href = url; - } - } else { - library = document.createElement('style'); - (library as HTMLStyleElement).textContent = content; + if (!Array.isArray(urlOrUrls)) { + urlOrUrls = [urlOrUrls]; } - library.onload = () => { - this.loadedLibraries[key].next(); - this.loadedLibraries[key].complete(); - }; + return new Observable(subscriber => { + (urlOrUrls as string[]).forEach((url, index) => { + const key = url ? url.slice(url.lastIndexOf('/') + 1) : uuid(); + + if (this.loadedLibraries[key]) { + subscriber.next(); + subscriber.complete(); + return; + } + + this.loadedLibraries[key] = new ReplaySubject(); + + let library; + if (type === 'script') { + library = document.createElement('script'); + library.type = 'text/javascript'; + if (url) { + (library as HTMLScriptElement).src = url; + } + + (library as HTMLScriptElement).text = content; + } else if (url) { + library = document.createElement('link'); + library.type = 'text/css'; + (library as HTMLLinkElement).rel = 'stylesheet'; + + if (url) { + (library as HTMLLinkElement).href = url; + } + } else { + library = document.createElement('style'); + (library as HTMLStyleElement).textContent = content; + } + + library.onload = () => { + this.loadedLibraries[key].next(); + this.loadedLibraries[key].complete(); - document.querySelector(targetQuery).insertAdjacentElement(position, library); + if (index === urlOrUrls.length - 1) { + subscriber.next(); + subscriber.complete(); + } + }; - return this.loadedLibraries[key].asObservable(); + document.querySelector(targetQuery).insertAdjacentElement(position, library); + }); + }); } } 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 f454be2872..1c0f533079 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 @@ -1,14 +1,14 @@ -import { State, Selector, createSelector, Action, StateContext, Store } from '@ngxs/store'; -import { Config } from '../models/config'; -import { ABP } from '../models/common'; -import { GetAppConfiguration, PatchRouteByName } from '../actions/config.actions'; -import { ApplicationConfigurationService } from '../services/application-configuration.service'; -import { tap, switchMap } from 'rxjs/operators'; +import { Action, createSelector, Selector, State, StateContext, Store } from '@ngxs/store'; +import { of } from 'rxjs'; +import { switchMap, tap } from 'rxjs/operators'; import snq from 'snq'; +import { GetAppConfiguration, PatchRouteByName } from '../actions/config.actions'; import { SetLanguage } from '../actions/session.actions'; +import { ABP } from '../models/common'; +import { Config } from '../models/config'; +import { ApplicationConfigurationService } from '../services/application-configuration.service'; +import { organizeRoutes } from '../utils/route-utils'; import { SessionState } from './session.state'; -import { of } from 'rxjs'; -import { setChildRoute, sortRoutes, organizeRoutes } from '../utils/route-utils'; @State({ name: 'ConfigState', @@ -90,14 +90,31 @@ export class ConfigState { return selector; } - static getSetting(key: string) { + static getSetting(key: string, findContain?: boolean) { const selector = createSelector( [ConfigState], (state: Config.State) => { return snq(() => state.setting.values[key]); }, ); + return selector; + } + + static getSettings(keyword?: string) { + const selector = createSelector( + [ConfigState], + (state: Config.State) => { + if (keyword) { + const keys = snq(() => Object.keys(state.setting.values).filter(key => key.indexOf(keyword) > -1), []); + if (keys.length) { + return keys.reduce((acc, key) => ({ ...acc, [key]: state.setting.values[key] }), {}); + } + } + + return snq(() => state.setting.values, {}); + }, + ); return selector; } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/layout/layout.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/layout/layout.component.html index 3e091aa5a1..89ab7f1b69 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/layout/layout.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/layout/layout.component.html @@ -11,7 +11,7 @@
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/animations/slide.animations.ts b/npm/ng-packs/packages/theme-shared/src/lib/animations/slide.animations.ts index 90f46662be..32ff155e55 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/animations/slide.animations.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/animations/slide.animations.ts @@ -1,6 +1,7 @@ import { animate, state, style, transition, trigger, query } from '@angular/animations'; -export const slideFromBottom = trigger('routeAnimations', [ - state('void', style({ 'margin-top': '20px', opacity: '0' })), - state('*', style({ 'margin-top': '0px', opacity: '1' })), - transition(':enter', [animate('0.2s ease-out', style({ opacity: '1', 'margin-top': '0px' }))]), +export const slideFromBottom = trigger('slideFromBottom', [ + transition('* <=> *', [ + style({ 'margin-top': '20px', opacity: '0' }), + animate('0.2s ease-out', style({ opacity: '1', 'margin-top': '0px' })), + ]), ]); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts index 50dab84c0f..59c6b3c13d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts @@ -6,14 +6,14 @@ import { Component, Input } from '@angular/core'; - ` + `, }) export class ButtonComponent { @Input() buttonClass = 'btn btn-primary'; @Input() - type = 'button'; + buttonType; // TODO: Add initial value. @Input() iconClass: string; @@ -24,6 +24,11 @@ export class ButtonComponent { @Input() disabled = false; + /** + * @deprecated Use buttonType instead. To be deleted in v1 + */ + @Input() type = 'button'; + get icon(): string { return `${this.loading ? 'fa fa-pulse fa-spinner' : this.iconClass || 'd-none'}`; } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/contants/styles.ts b/npm/ng-packs/packages/theme-shared/src/lib/contants/styles.ts index c7fa014373..a53bd4573b 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/contants/styles.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/contants/styles.ts @@ -38,7 +38,7 @@ export default ` left: 0 !important; width: 100% !important; height: 100% !important; - background-color: rgba(0, 0, 0, .6) !important; + background-color: rgba(0, 0, 0, 0.6) !important; z-index: 1040 !important; } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 2c7292ebc4..1159b10735 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -35,7 +35,7 @@ export function appendScript(injector: Injector) { 'head', 'afterbegin', ) /* lazyLoadService.load(null, 'script', scripts) */, - ).pipe(take(1)); + ).toPromise(); }; return fn; diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 26f9c571b0..b08cda26e2 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -59,6 +59,7 @@ $projects = ( "framework/src/Volo.Abp.Dapper", "framework/src/Volo.Abp.Data", "framework/src/Volo.Abp.Ddd.Application", + "framework/src/Volo.Abp.Ddd.Application.Contracts", "framework/src/Volo.Abp.Ddd.Domain", "framework/src/Volo.Abp.Emailing", "framework/src/Volo.Abp.EntityFrameworkCore", diff --git a/templates/app/angular/angular.json b/templates/app/angular/angular.json index 8a844d2562..f7bb4656fe 100644 --- a/templates/app/angular/angular.json +++ b/templates/app/angular/angular.json @@ -23,17 +23,31 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "aot": false, - "assets": [ - "src/favicon.ico", - "src/assets" - ], + "extractCss": true, + "assets": ["src/favicon.ico", "src/assets"], "styles": [ "src/styles.scss", "node_modules/bootstrap/dist/css/bootstrap.min.css", - "node_modules/font-awesome/css/font-awesome.min.css", - "node_modules/primeng/resources/themes/nova-light/theme.css", - "node_modules/primeicons/primeicons.css", - "node_modules/primeng/resources/primeng.min.css" + { + "input": "node_modules/font-awesome/css/font-awesome.min.css", + "lazy": true, + "bundleName": "font-awesome.min" + }, + { + "input": "node_modules/primeng/resources/themes/nova-light/theme.css", + "lazy": true, + "bundleName": "primeng-nova-light-theme" + }, + { + "input": "node_modules/primeicons/primeicons.css", + "lazy": true, + "bundleName": "primeicons" + }, + { + "input": "node_modules/primeng/resources/primeng.min.css", + "lazy": true, + "bundleName": "primeng.min" + } ], "scripts": [] }, @@ -100,10 +114,7 @@ "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], + "assets": ["src/favicon.ico", "src/assets"], "styles": [ "src/styles.scss", "node_modules/bootstrap/dist/css/bootstrap.min.css", @@ -118,14 +129,8 @@ "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] + "tsConfig": ["tsconfig.app.json", "tsconfig.spec.json", "e2e/tsconfig.json"], + "exclude": ["**/node_modules/**"] } }, "e2e": { @@ -143,8 +148,5 @@ } } }, - "defaultProject": "myProjectName", - "cli": { - "analytics": "07ef29be-2b85-4919-9f78-44994f63b8b7" - } -} \ No newline at end of file + "defaultProject": "myProjectName" +} diff --git a/templates/app/angular/src/app/app.component.ts b/templates/app/angular/src/app/app.component.ts index bf2a27962a..e89c73184d 100644 --- a/templates/app/angular/src/app/app.component.ts +++ b/templates/app/angular/src/app/app.component.ts @@ -1,4 +1,5 @@ -import { Component } from '@angular/core'; +import { LazyLoadService } from '@abp/ng.core'; +import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', @@ -7,4 +8,17 @@ import { Component } from '@angular/core'; `, }) -export class AppComponent {} +export class AppComponent implements OnInit { + constructor(private lazyLoadService: LazyLoadService) {} + + ngOnInit() { + this.lazyLoadService + .load( + ['primeng.min.css', 'primeicons.css', 'primeng-nova-light-theme.css', 'font-awesome.min.css'], + 'style', + null, + 'head', + ) + .subscribe(); + } +}