diff --git a/build/common.ps1 b/build/common.ps1 index e4ba6a8adc..06fa20b35e 100644 --- a/build/common.ps1 +++ b/build/common.ps1 @@ -28,6 +28,7 @@ $solutionPaths = ( "../samples/BookStore-Modular/modules/book-management", "../samples/BookStore-Modular/application", "../samples/DashboardDemo", + "../samples/EfCoreMigrationDemo", "../samples/MicroserviceDemo", "../samples/RabbitMqEventBus", "../abp_io/AbpIoLocalization" diff --git a/common.DotSettings b/common.DotSettings index cb0b2c919f..0eb4875d49 100644 --- a/common.DotSettings +++ b/common.DotSettings @@ -20,4 +20,5 @@ False False SQL + True \ No newline at end of file diff --git a/common.props b/common.props index bd6b976758..8b0b8291c4 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 2.4.0 + 2.5.0 $(NoWarn);CS1591 https://abp.io/assets/abp_nupkg.png https://abp.io diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md index 3c3c2ec26c..460623440f 100644 --- a/docs/en/API/Dynamic-CSharp-API-Clients.md +++ b/docs/en/API/Dynamic-CSharp-API-Clients.md @@ -69,7 +69,7 @@ public class MyClientAppModule : AbpModule } ```` -See the "RemoteServiceOptions" section below for more detailed configuration. +See the "AbpRemoteServiceOptions" section below for more detailed configuration. ## Usage @@ -104,7 +104,7 @@ While you can inject `IBookAppService` like above to use the client proxy, you c ## Configuration -### RemoteServiceOptions +### AbpRemoteServiceOptions `AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: diff --git a/docs/en/Application-Services.md b/docs/en/Application-Services.md index 3fb52377b3..f7f7675f46 100644 --- a/docs/en/Application-Services.md +++ b/docs/en/Application-Services.md @@ -132,6 +132,8 @@ The `CreateAsync` method above manually creates a `Book` entity from given `Crea However, in many cases, it's very practical to use **auto object mapping** to set properties of an object from a similar object. ABP provides an [object to object mapping](Object-To-Object-Mapping.md) infrastructure to make this even easier. +Object to object mapping provides abstractions and it is implemented by the [AutoMapper](https://automapper.org/) library by default. + Let's create another method to get a book. First, define the method in the `IBookAppService` interface: ````csharp @@ -146,7 +148,6 @@ public interface IBookAppService : IApplicationService `BookDto` is a simple [DTO](Data-Transfer-Objects.md) class defined as below: ````csharp -[AbpAutoMapFrom(typeof(Book))] //Defines the mapping public class BookDto { public Guid Id { get; set; } @@ -159,21 +160,38 @@ public class BookDto } ```` -* `BookDto` defines `[AbpAutoMapFrom(typeof(Book))]` attribute to create the object mapping from `Book` to `BookDto`. +AutoMapper requires to create a mapping [profile class](https://docs.automapper.org/en/stable/Configuration.html#profile-instances). Example: -Then you can implement the `GetAsync` method as shown below: +````csharp +public class MyProfile : Profile +{ + public MyProfile() + { + CreateMap(); + } +} +```` + +You should then register profiles using the `AbpAutoMapperOptions`: ````csharp -public async Task GetAsync(Guid id) +[DependsOn(typeof(AbpAutoMapperModule))] +public class MyModule : AbpModule { - var book = await _bookRepository.GetAsync(id); - return book.MapTo(); + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + //Add all mappings defined in the assembly of the MyModule class + options.AddMaps(); + }); + } } ```` -`MapTo` extension method converts `Book` object to `BookDto` object by copying all properties with the same naming. +`AddMaps` registers all profile classes defined in the assembly of the given class, typically your module class. It also registers for the [attribute mapping](https://docs.automapper.org/en/stable/Attribute-mapping.html). -An alternative to the `MapTo` is using the `IObjectMapper` service: +Then you can implement the `GetAsync` method as shown below: ````csharp public async Task GetAsync(Guid id) @@ -183,8 +201,6 @@ public async Task GetAsync(Guid id) } ```` -While the second syntax is a bit harder to write, it better works if you write unit tests. - See the [object to object mapping document](Object-To-Object-Mapping.md) for more. ## Validation @@ -250,7 +266,6 @@ public interface ICrudAppService< DTO classes used in this example are `BookDto` and `CreateUpdateBookDto`: ````csharp -[AbpAutoMapFrom(typeof(Book))] public class BookDto : AuditedEntityDto { public string Name { get; set; } @@ -260,7 +275,6 @@ public class BookDto : AuditedEntityDto public float Price { get; set; } } -[AbpAutoMapTo(typeof(Book))] public class CreateUpdateBookDto { [Required] @@ -275,6 +289,19 @@ public class CreateUpdateBookDto } ```` +[Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances) class of DTO class. + +```csharp +public class MyProfile : Profile +{ + public MyProfile() + { + CreateMap(); + CreateMap(); + } +} +``` + * `CreateUpdateBookDto` is shared by create and update operations, but you could use separated DTO classes as well. And finally, the `BookAppService` implementation is very simple: diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md index 1bceda951f..1522fa586a 100644 --- a/docs/en/Authorization.md +++ b/docs/en/Authorization.md @@ -142,6 +142,20 @@ myGroup.AddPermission( ); ``` +#### Enable/Disable Permissions + +A permission is enabled by default. It is possible to disable a permission. A disabled permission will be prohibited for everyone. You can still check for the permission, but it will always return prohibited. + +Example definition: + +````csharp +myGroup.AddPermission("Author_Management", isEnabled: false); +```` + +You normally don't need to define a disabled permission (unless you temporary want disable a feature of your application). However, you may want to disable a permission defined in a depended module. In this way you can disable the related application functionality. See the "*Changing Permission Definitions of a Depended Module*" section below for an example usage. + +> Note: Checking an undefined permission will throw an exception while a disabled permission check simply returns prohibited (false). + #### 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. @@ -208,6 +222,18 @@ See [policy based authorization](https://docs.microsoft.com/en-us/aspnet/core/se A class deriving from the `PermissionDefinitionProvider` (just like the example above) can also get existing permission definitions (defined by the depended [modules](Module-Development-Basics.md)) and change their definitions. +Example: + +````csharp +context + .GetPermissionOrNull(IdentityPermissions.Roles.Delete) + .IsEnabled = false; +```` + +When you write this code inside your permission definition provider, it finds the "role deletion" permission of the [Identity Module](Modules/Identity.md) and disabled the permission, so no one can delete a role on the application. + +> Tip: It is better to check the value returned by the `GetPermissionOrNull` method since it may return null if the given permission was not defined. + ## 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. @@ -339,10 +365,10 @@ A permission value provider should return one of the following values from the ` - `PermissionGrantResult.Prohibited` is returned to prohibit the user for the permission. If any of the providers return `Prohibited`, the result will always be `Prohibited`. Doesn't matter what other providers return. - `PermissionGrantResult.Undefined` is returned if this value provider could not decide about the permission value. Return this to let other providers check the permission. -Once a provider is defined, it should be added to the `PermissionOptions` as shown below: +Once a provider is defined, it should be added to the `AbpPermissionOptions` as shown below: ```csharp -Configure(options => +Configure(options => { options.ValueProviders.Add(); }); diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index 588fcace9e..622558ac68 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -39,5 +39,46 @@ public class YourModule : AbpModule ```` ## Configuration +You can install any storage for Hangfire. The most common one is SQL Server (see the [Hangfire.SqlServer](https://www.nuget.org/packages/Hangfire.SqlServer) NuGet package). -TODO... \ No newline at end of file +After you have installed these NuGet packages, you need to configure your project to use Hangfire. + +1.First, we change the `Module` class (example: `HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method: + + +````csharp + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + var hostingEnvironment = context.Services.GetHostingEnvironment(); + + //... other configarations + + ConfigureHangfire(context, configuration); + } + + private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) + { + context.Services.AddHangfire(config => + { + config.UseSqlServerStorage(configuration.GetConnectionString("Default")); + }); + } +```` + +2. We need to add `UseHangfireServer` call in the `OnApplicationInitialization` method in `Module` class + +If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDashboard` + +````csharp + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + + // ... others + + app.UseHangfireServer(); + app.UseHangfireDashboard(); + + } +```` diff --git a/docs/en/Best-Practices/Entity-Framework-Core-Integration.md b/docs/en/Best-Practices/Entity-Framework-Core-Integration.md index db8117d798..f487f9d61d 100644 --- a/docs/en/Best-Practices/Entity-Framework-Core-Integration.md +++ b/docs/en/Best-Practices/Entity-Framework-Core-Integration.md @@ -107,10 +107,10 @@ public static class IdentityDbContextModelBuilderExtensions ```` * **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above). -* **Do** create a **configuration options** class by inheriting from the `ModelBuilderConfigurationOptions`. Example: +* **Do** create a **configuration options** class by inheriting from the `AbpModelBuilderConfigurationOptions`. Example: ````C# -public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfigurationOptions +public class IdentityModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions { public IdentityModelBuilderConfigurationOptions() : base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema) diff --git a/docs/en/Best-Practices/MongoDB-Integration.md b/docs/en/Best-Practices/MongoDB-Integration.md index 76b6e8561f..74a0e429f1 100644 --- a/docs/en/Best-Practices/MongoDB-Integration.md +++ b/docs/en/Best-Practices/MongoDB-Integration.md @@ -90,11 +90,11 @@ public static class AbpIdentityMongoDbContextExtensions } ``` -- **Do** create a **configuration options** class by inheriting from the `MongoModelBuilderConfigurationOptions`. Example: +- **Do** create a **configuration options** class by inheriting from the `AbpMongoModelBuilderConfigurationOptions`. Example: ```c# public class IdentityMongoModelBuilderConfigurationOptions - : MongoModelBuilderConfigurationOptions + : AbpMongoModelBuilderConfigurationOptions { public IdentityMongoModelBuilderConfigurationOptions() : base(AbpIdentityConsts.DefaultDbTablePrefix) diff --git a/docs/en/CLI.md b/docs/en/CLI.md index a347599135..f0fd3ae7bd 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -60,6 +60,7 @@ abp new Acme.BookStore * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://.zip`). * `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. +* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. You can set your own connection string if you don't want to use the default. Be aware that the default database provider is `SQL Server`, therefore you can only enter connection string for SQL Server! ### add-package diff --git a/docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md b/docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md new file mode 100644 index 0000000000..10b1e05a4a --- /dev/null +++ b/docs/en/How-To/Azure-Active-Directory-Authentication-MVC.md @@ -0,0 +1,201 @@ +# How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications + +This guide demonstrates how to integrate AzureAD to an ABP application that enables users to sign in using OAuth 2.0 with credentials from **Azure Active Directory**. + +Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly. + +Two different **alternative approaches** for AzureAD integration will be demonstrated for better coverage. + +1. **AddAzureAD**: This approach uses Microsoft [AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/) which is very popular when users search the web about how to integrate AzureAD to their web application. + +2. **AddOpenIdConnect**: This approach uses default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for not only AzureAD but for all OpenId connections. + +> There is **no difference** in functionality between these approaches. AddAzureAD is an abstracted way of OpenIdConnection ([source](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADAuthenticationBuilderExtensions.cs#L122)) with predefined cookie settings. +> +> However there are key differences in integration to ABP applications because of default configurated signin schemes which will be explained below. + +## 1. AddAzureAD + +This approach uses the most common way to integrate AzureAD by using the [Microsoft AzureAD UI nuget package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureAD.UI/). + +If you choose this approach, you will need to install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** project. Also, since AddAzureAD extension uses [configuration binding](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#default-configuration), you need to update your appsettings.json file located in your **.Web** project. + +#### **Updating `appsettings.json`** + +You need to add a new section to your `appsettings.json` which will be binded to configuration when configuring the `OpenIdConnectOptions`: + +````json + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "", + "ClientId": "", + "Domain": "domain.onmicrosoft.com", + "CallbackPath": "/signin-azuread-oidc" + } +```` + +> Important configuration here is the CallbackPath. This value must be the same with one of your Azure AD-> app registrations-> Authentication -> RedirectUri. + +Then, you need to configure the `OpenIdConnectOptions` to complete the integration. + +#### Configuring OpenIdConnectOptions + +In your **.Web** project, locate your **ApplicationWebModule** and modify `ConfigureAuthentication` method with the following: + +````csharp +private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) + { + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); + context.Services.AddAuthentication() + .AddIdentityServerAuthentication(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = false; + options.ApiName = "Acme.BookStore"; + }) + .AddAzureAD(options => configuration.Bind("AzureAd", options)); + + context.Services.Configure(AzureADDefaults.OpenIdScheme, options => + { + options.Authority = options.Authority + "/v2.0/"; + options.ClientId = configuration["AzureAd:ClientId"]; + options.CallbackPath = configuration["AzureAd:CallbackPath"]; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.RequireHttpsMetadata = false; + + options.TokenValidationParameters.ValidateIssuer = false; + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.SignInScheme = IdentityConstants.ExternalScheme; + + options.Scope.Add("email"); + }); + } +```` + +> **Don't forget to:** +> +> * Add `.AddAzureAD(options => configuration.Bind("AzureAd", options))` after `.AddAuthentication()`. This binds your AzureAD appsettings and easy to miss out. +> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear()`. This will disable the default Microsoft claim type mapping. +> * Add `JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier)`. Mapping this to [ClaimTypes.NameIdentifier](https://github.com/dotnet/runtime/blob/6d395de48ac718a913e567ae80961050f2a9a4fa/src/libraries/System.Security.Claims/src/System/Security/Claims/ClaimTypes.cs#L59) is important since default SignIn Manager behavior uses this claim type for external login information. +> * Add `options.SignInScheme = IdentityConstants.ExternalScheme` since [default signin scheme is `AzureADOpenID`](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L35). +> * Add `options.Scope.Add("email")` if you are using **v2.0** endpoint of AzureAD since v2.0 endpoint doesn't return the `email` claim as default. The [Account Module](../Modules/Account.md) uses `email` claim to [register external users](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L215). + +You are done and integration is completed. + +## 2. Alternative Approach: AddOpenIdConnect + +If you don't want to use an extra nuget package in your application, you can use the straight default [OpenIdConnect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect/) which can be used for all OpenId connections including AzureAD external authentication. + +You don't have to use `appsettings.json` configuration but it is a good practice to set AzureAD information in the `appsettings.json`. + +To get the AzureAD information from `appsettings.json`, which will be used in `OpenIdConnectOptions` configuration, simply add a new section to `appsettings.json` located in your **.Web** project: + +````json + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "", + "ClientId": "", + "Domain": "domain.onmicrosoft.com", + "CallbackPath": "/signin-azuread-oidc" + } +```` + +Then, In your **.Web** project; you can modify the `ConfigureAuthentication` method located in your **ApplicationWebModule** with the following: + +````csharp +private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) + { + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); + + context.Services.AddAuthentication() + .AddIdentityServerAuthentication(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = false; + options.ApiName = "BookStore"; + }) + .AddOpenIdConnect("AzureOpenId", "Azure Active Directory OpenId", options => + { + options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"] + "/v2.0/"; + options.ClientId = configuration["AzureAd:ClientId"]; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.CallbackPath = configuration["AzureAd:CallbackPath"]; + options.RequireHttpsMetadata = false; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + + options.Scope.Add("email"); + }); + } +```` + +And that's it, integration is completed. Keep on mind that you can connect any other external authentication providers. + +## The Source Code + +You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization). + +# FAQ + +* Help! `GetExternalLoginInfoAsync` returns `null`! + + * There can be 2 reasons for this; + + 1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`: + + ````csharp + options.SignInScheme = IdentityConstants.ExternalScheme; + ```` + + 2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping: + + ````csharp + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier); + ```` + + +* Help! I keep getting ***AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application*** error! + + * If you set your **CallbackPath** in appsettings as: + + ````csharp + "AzureAd": { + ... + "CallbackPath": "/signin-azuread-oidc" + } + ```` + + your **Redirect URI** of your application in azure portal must be with domain like `https://localhost:44320/signin-azuread-oidc`, not only `/signin-azuread-oidc`. + +* Help! I am getting ***System.ArgumentNullException: Value cannot be null. (Parameter 'userName')*** error! + + + * This occurs when you use Azure Authority **v2.0 endpoint** without requesting `email` scope. [Abp checks unique email to create user](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs#L208). Simply add + + ````csharp + options.Scope.Add("email"); + ```` + + to your openid configuration. + +* How can I **debug/watch** which claims I get before they get mapped? + + * You can add a simple event under openid configuration to debug before mapping like: + + ````csharp + options.Events.OnTokenValidated = (async context => + { + var claimsFromOidcProvider = context.Principal.Claims.ToList(); + await Task.CompletedTask; + }); + ```` + + +## See Also + +* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md). +* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md). \ No newline at end of file diff --git a/docs/en/How-To/Customize-Login-Page-MVC.md b/docs/en/How-To/Customize-Login-Page-MVC.md new file mode 100644 index 0000000000..5678828bfb --- /dev/null +++ b/docs/en/How-To/Customize-Login-Page-MVC.md @@ -0,0 +1,113 @@ +# How to Customize the Login Page for MVC / Razor Page Applications + +When you create a new application using the [application startup template](../Startup-Templates/Application.md), source code of the login page will not be inside your solution, so you can not directly change it. The login page comes from the [Account Module](../Modules/Account.md) that is used a [NuGet package](https://www.nuget.org/packages/Volo.Abp.Account.Web) reference. + +This document explains how to customize the login page for your own application. + +## Create a Login PageModel + +Create a new class inheriting from the [LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs) of the Account module. + +````csharp +public class CustomLoginModel : LoginModel +{ + public CustomLoginModel( + Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider, + Microsoft.Extensions.Options.IOptions accountOptions) + : base(schemeProvider, accountOptions) + { + } +} +```` + +> Naming convention is important here. If your class name doesn't end with `LoginModel`, you need to manually replace the `LoginModel` using the [dependency injection](../Dependency-Injection.md) system. + +Then you can override any method you need and add new methods and properties needed by the UI. + +## Overriding the Login Page UI + +Create folder named **Account** under **Pages** directory and create a **Login.cshtml** under this folder. It will automatically override the `Login.cshtml` file defined in the Account Module thanks to the [Virtual File System](../Virtual-File-System.md). + +A good way to customize a page is to copy its source code. [Click here](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml) for the source code of the login page. At the time this document has been written, the source code was like below: + +````xml +@page +@using Volo.Abp.Account.Settings +@using Volo.Abp.Settings +@model Acme.BookStore.Web.Pages.Account.CustomLoginModel +@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage +@inject Volo.Abp.Settings.ISettingProvider SettingProvider +@if (Model.EnableLocalLogin) +{ +
+
+

@L["Login"]

+ @if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) + { + + @L["AreYouANewUser"] + @L["Register"] + + } +
+ + +
+ + + +
+
+ + + +
+
+ +
+ @L["Login"] +
+
+ + +
+} + +@if (Model.VisibleExternalProviders.Any()) +{ +
+

@L["UseAnotherServiceToLogIn"]

+
+ + + @foreach (var provider in Model.VisibleExternalProviders) + { + + } +
+
+} + +@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) +{ +
+ @L["InvalidLoginRequest"] + @L["ThereAreNoLoginSchemesConfiguredForThisClient"] +
+} +```` + +Just changed the `@model` to `Acme.BookStore.Web.Pages.Account.CustomLoginModel` to use the customized `PageModel` class. You can change it however your application needs. + +## The Source Code + +You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization). + +## See Also + +* [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](../UI/AspNetCore/Customization-User-Interface.md). \ No newline at end of file diff --git a/docs/en/How-To/Customize-SignIn-Manager.md b/docs/en/How-To/Customize-SignIn-Manager.md new file mode 100644 index 0000000000..61b6c3b7b5 --- /dev/null +++ b/docs/en/How-To/Customize-SignIn-Manager.md @@ -0,0 +1,101 @@ +# How to Customize the SignIn Manager for ABP Applications + +After creating a new application using the [application startup template](../Startup-Templates/Application.md), you may want extend or change the default behavior of the SignIn Manager for your authentication and registration flow needs. ABP [Account Module](../Modules/Account.md) uses the [Identity Management Module](../Modules/Identity.md) for SignIn Manager and the [Identity Management Module](../Modules/Identity.md) uses default [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) ([see here](https://github.com/abpframework/abp/blob/be32a55449e270d2d456df3dabdc91f3ffdd4fa9/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs#L17)). + +To write your Custom SignIn Manager, you need to extend [Microsoft Identity SignIn Manager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) class and register it to the DI container. + +This document explains how to customize the SignIn Manager for your own application. + +## Create a CustomSignInManager + +Create a new class inheriting the [SignInMager](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs) of Microsoft Identity package. + +````csharp +public class CustomSignInManager : Microsoft.AspNetCore.Identity.SignInManager +{ + public CustomSignInManager( + Microsoft.AspNetCore.Identity.UserManager userManager, + Microsoft.AspNetCore.Http.IHttpContextAccessor contextAccessor, + Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory claimsFactory, + Microsoft.Extensions.Options.IOptions optionsAccessor, + Microsoft.Extensions.Logging.ILogger> logger, + Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemes, + Microsoft.AspNetCore.Identity.IUserConfirmation confirmation) + : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) + { + } +} +```` + +> It is important to use **Volo.Abp.Identity.IdentityUser** type for SignInManager to inherit, not the AppUser of your application. + +Afterwards you can override any of the SignIn Manager methods you need and add new methods and properties needed for your authentication or registration flow. + +## Overriding the GetExternalLoginInfoAsync Method + +In this case we'll be overriding the `GetExternalLoginInfoAsync` method which is invoked when a third party authentication is implemented. + +A good way to override a method is copying its [source code](https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L638-L674). In this case, we will be using a minorly modified version of the source code which explicitly shows the namespaces of the methods and properties to help better understanding of the concept. + +````csharp +public override async Task GetExternalLoginInfoAsync(string expectedXsrf = null) +{ + var auth = await Context.AuthenticateAsync(Microsoft.AspNetCore.Identity.IdentityConstants.ExternalScheme); + var items = auth?.Properties?.Items; + if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey")) + { + return null; + } + + if (expectedXsrf != null) + { + if (!items.ContainsKey("XsrfKey")) + { + return null; + } + var userId = items[XsrfKey] as string; + if (userId != expectedXsrf) + { + return null; + } + } + + var providerKey = auth.Principal.FindFirstValue(ClaimTypes.NameIdentifier); + var provider = items[LoginProviderKey] as string; + if (providerKey == null || provider == null) + { + return null; + } + + var providerDisplayName = (await GetExternalAuthenticationSchemesAsync()).FirstOrDefault(p => p.Name == provider)?.DisplayName + ?? provider; + return new Microsoft.AspNetCore.Identity.ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName) + { + AuthenticationTokens = auth.Properties.GetTokens() + }; +} +```` + +To get your overridden method invoked and your customized SignIn Manager class to work, you need to register your class to the [Dependency Injection System](../Dependency-Injection.md). + +## Register to Dependency Injection + +Registering `CustomSignInManager` should be done with adding **AddSignInManager** extension method of the [IdentityBuilderExtensions](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/IdentityBuilderExtensions.cs) of the [IdentityBuilder](https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Extensions.Core/src/IdentityBuilder.cs). + +Inside your `.Web` project, locate the `YourProjectNameWebModule` and add the following code under the `PreConfigureServices` method to replace the old `SignInManager` with your customized one: + +````csharp +PreConfigure(identityBuilder => +{ + identityBuilder.AddSignInManager(); +}); +```` + +## The Source Code + +You can find the source code of the completed example [here](https://github.com/abpframework/abp-samples/tree/master/aspnet-core/Authentication-Customization). + +## See Also + +* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md). +* [Identity Management Module](../Modules/Identity.md). \ No newline at end of file diff --git a/docs/en/How-To/Index.md b/docs/en/How-To/Index.md new file mode 100644 index 0000000000..da209d4bcb --- /dev/null +++ b/docs/en/How-To/Index.md @@ -0,0 +1,9 @@ +# "How To" Guides + +This section contains "how to" guides for some specific questions frequently asked. While some of them are common development tasks and not directly related to the ABP Framework, we think it is useful to have some concrete examples those directly work with your ABP based applications. + +## Authentication + +* [How to Customize the Login Page for MVC / Razor Page Applications](Customize-Login-Page-MVC.md) +* [How to Use the Azure Active Directory Authentication for MVC / Razor Page Applications](Azure-Active-Directory-Authentication-MVC.md) +* [How to Customize the SignIn Manager for ABP Applications](Customize-SignIn-Manager.md) \ No newline at end of file diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md index db752ceb74..bbed28e8fe 100644 --- a/docs/en/Modules/Docs.md +++ b/docs/en/Modules/Docs.md @@ -134,7 +134,7 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => + Configure(options => { options.DefinitionProviders.Add(); }); diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md index 8298c01f1b..af76bb14ff 100644 --- a/docs/en/Startup-Templates/Application.md +++ b/docs/en/Startup-Templates/Application.md @@ -261,14 +261,181 @@ You should run the application with the given order: ### Angular UI -If you choose Angular as the UI framework (using the `-u angular` option), the solution is separated into two folders: +If you choose `Angular` as the UI framework (using the `-u angular` option), the solution is being separated into three folders: -* `angular` folder contains the Angular UI solution, the client side. -* `aspnet-core` folder contains the ASP.NET Core solution, the server side. +* `angular` folder contains the Angular UI application, the client-side code. +* `aspnet-core` folder contains the ASP.NET Core solution, the server-side code. +* `react-native` folder contains the React Native UI application, the client-side code for mobile. -Server side is very similar to the solution described above. `.HttpApi.Host` project serves the API, so the Angular application can consume it. +The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the `Angular` application consumes it. -The files under the `angular/src/environments` folder has the essential configuration of the application. +Angular application folder structure looks like below: + +![angular-folder-structure](../images/angular-folder-structure.png) + + +Each of ABP Commercial modules is an NPM package. Some ABP modules are added as a dependency in `package.json`. These modules install with their dependencies. To see all ABP packages, you can run the following command in the `angular` folder: + +```bash +yarn list --pattern abp +``` + +Angular application module structure: + +![Angular template structure diagram](../images/angular-template-structure-diagram.png) + +#### AppModule + +`AppModule` is the root module of the application. Some of ABP modules and some essential modules imported to the `AppModule`. + +ABP Config modules also have imported to `AppModule`  for initially requirements of lazy-loadable ABP modules. + +#### AppRoutingModule + +There are lazy-loadable ABP modules in the `AppRoutingModule` as routes. + +> Paths of ABP Modules should not be changed. + +You should add `routes` property in the `data` object to add a link on the menu to redirect to your custom pages. + +```js +{ + path: 'dashboard', + loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule), + canActivate: [AuthGuard, PermissionGuard], + data: { + routes: { + name: 'ProjectName::Menu:Dashboard', + order: 2, + iconClass: 'fa fa-dashboard', + requiredPolicy: 'ProjectName.Dashboard.Host' + } as ABP.Route + } +} +``` +In the above example; +* If the user is not logged in, AuthGuard blocks access and redirects to the login page. +* PermissionGuard checks the user's permission with `requiredPolicy` property of the `rotues` object. If the user is not authorized to access the page, the 403 page appears. +* `name` property of `routes` is the menu link label. A localization key can be defined . +* `iconClass` property of `routes` object is the menu link icon class. +* `requiredPolicy` property of `routes` object is the required policy key to access the page. + +After the above `routes` definition, if the user is authorized, the dashboard link will appear on the menu. + +#### Shared Module + +The modules that may be required for all modules have imported to the `SharedModule`. You should import the `SharedModule` to all modules. + +See the [Sharing Modules](https://angular.io/guide/sharing-ngmodules) document. + +#### Environments + +The files under the `src/environments` folder has the essential configuration of the application. + +#### Home Module + +Home module is an example lazy-loadable module that loads on the root address of the application. + +#### Styles + +The required style files added to `styles` array in the `angular.json`. `AppComponent` loads some style files lazily via `LazyLoadService` after the main bundle is loaded to shorten the first rendering time. + +#### Testing + +You should create your tests in the same folder as the file file you want to test. + +See the [testing document](https://angular.io/guide/testing). + +#### Depended Packages + +* [NG Bootstrap](https://ng-bootstrap.github.io/) is used as UI component library. +* [NGXS](https://www.ngxs.io/) is used as state management library. +* [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc) is used to support for OAuth 2 and OpenId Connect (OIDC). +* [Chart.js](https://www.chartjs.org/) is used to create widgets. +* [ngx-validate](https://github.com/ng-turkey/ngx-validate) is used for dynamic validation of reactive forms. + +### React Native + +The solution includes the [React Native](https://reactnative.dev/) application in the `react-native` folder as default. + +The server-side is similar to the solution described above. `*.HttpApi.Host` project serves the API, so the React Native application consumes it. + +The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app and, while it has many features. + +React Native application folder structure as like below: + +![react-native-folder-structure](../images/react-native-folder-structure.png) + +* `App.js` is bootstrap component of the application. +* `Environment.js` file has the essential configuration of the application. `prod` and `dev` configurations defined in this file. +* [Contexts](https://reactjs.org/docs/context.html) are created in the `src/contexts` folder. +* [Higher order components](https://reactjs.org/docs/higher-order-components.html) are created in the`src/hocs` folder. +* [Custom hooks](https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook) are created in the`src/hooks`. +* [Axios interceptors](https://github.com/axios/axios#interceptors) are created in the `src/interceptors` folder. +* Utility functions are exported from `src/utils` folder. + +#### Components + +Components that can be used on all screens are created in the `src/components` folder. All components have created as a function that able to use [hooks](https://reactjs.org/docs/hooks-intro.html). + +#### Screens + +![react-native-navigation-structure](../images/react-native-navigation-structure.png) + +Screens are created by creating folders that separate their names in the `src/screens` folder. Certain parts of some screens can be split into components. + +Each screen is used in a navigator in the `src/navigators` folder. + +#### Navigation + +[React Navigation](https://reactnavigation.org/) is used as a navigation library. Navigators are created in the `src/navigators`. A [drawer](https://reactnavigation.org/docs/drawer-based-navigation/) navigator and several [stack](https://reactnavigation.org/docs/hello-react-navigation/#installing-the-stack-navigator-library) navigators have created in this folder. See the [above diagram](#screens) for navigation structure. + +#### State Management + +[Redux](https://redux.js.org/) is used as state management library. [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development. + +Actions, reducers, sagas, selectors are created in the `src/store` folder. Store folder as like below: + +![react-native-store-folder](../images/react-native-store-folder.png) + +* [**Store**](https://redux.js.org/basics/store) is defined in the `src/store/index.js` file. +* [**Actions**](https://redux.js.org/basics/actions/) are payloads of information that send data from your application to your store. +* [**Reducers**](https://redux.js.org/basics/reducers) specify how the application's state changes in response to actions sent to the store. +* [**Redux-Saga**](https://redux-saga.js.org/) is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage. Sagas are created in the `src/store/sagas` folder. +* [**Reselect**](https://github.com/reduxjs/reselect) library is used to create memoized selectors. Selectors are created in the `src/store/selectors` folder. + +#### APIs + +[Axios](https://github.com/axios/axios) is used as an HTTP client library. An Axios instance has exported from `src/api/API.js` file to make HTTP calls with the same config. `src/api` folder also has the API files that have been created for API calls. + +#### Theming + +[Native Base](https://nativebase.io/) is used as UI components library. Native Base components can customize easily. See the [Native Base customize](https://docs.nativebase.io/Customize.html#Customize) documentation. We followed the same way. + +* Native Base theme variables are in the `src/theme/variables` folder. +* Native Base component styles are in the `src/theme/components` folder. These files have been generated with Native Base's `ejectTheme` script. +* Styles of components override with the files under the `src/theme/overrides` folder. + +#### Testing + +Unit tests will be created. + +See the [Testing Overview](https://reactjs.org/docs/testing.html) document. + +#### Depended Libraries + +* [Native Base](https://nativebase.io/) is used as UI components library. +* [React Navigation](https://reactnavigation.org/) is used as navigation library. +* [Axios](https://github.com/axios/axios) is used as HTTP client library. +* [Redux](https://redux.js.org/) is used as state management library. +* [Redux Toolkit](https://redux-toolkit.js.org/) library is used as a toolset for efficient Redux development. +* [Redux-Saga](https://redux-saga.js.org/) is used to manage asynchronous processes. +* [Redux Persist](https://github.com/rt2zz/redux-persist) is used as state persistance. +* [Reselect](https://github.com/reduxjs/reselect) is used to create memoized selectors. +* [i18n-js](https://github.com/fnando/i18n-js) is used as i18n library. +* [expo-font](https://docs.expo.io/versions/latest/sdk/font/) library allows loading fonts easily. +* [Formik](https://github.com/jaredpalmer/formik) is used to build forms. +* [Yup](https://github.com/jquense/yup) is used for form validations. ## What's Next? diff --git a/docs/en/UI/Angular/Content-Security-Strategy.md b/docs/en/UI/Angular/Content-Security-Strategy.md new file mode 100644 index 0000000000..f32317c9ca --- /dev/null +++ b/docs/en/UI/Angular/Content-Security-Strategy.md @@ -0,0 +1,74 @@ +# ContentSecurityStrategy + +`ContentSecurityStrategy` is an abstract class exposed by @abp/ng.core package. It helps you mark inline scripts or styles as safe in terms of [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy). + + + + +## API + + +### constructor + +```js +constructor(public nonce?: string) +``` + +- `nonce` enables whitelisting inline script or styles in order to avoid using `unsafe-inline` in [script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script) and [style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles) directives. + + +### applyCSP + +```js +applyCSP(element: HTMLScriptElement | HTMLStyleElement): void +``` + +This method maps the aforementioned properties to the given `element`. + + + + +## LooseContentSecurityPolicy + +`LooseContentSecurityPolicy` is a class that extends `ContentSecurityStrategy`. It requires `nonce` and marks given `` element will place at the **end** of ``. + +Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. + + +### How to Insert Styles + +If you pass a `StyleContentStrategy` instance as the first parameter of `insertContent` method, the `DomInsertionService` will create a `` element will place at the **end** of ``. + +Please refer to [ContentStrategy](./Content-Strategy.md) to see all available content strategies and how you can build your own content strategy. + + +## API + +### insertContent + +```js +insertContent(strategy: ContentStrategy): void +``` + +`strategy` parameter is the primary focus here and is explained above. + + +## What's Next? + +- [TrackByService](./Track-By-Service.md) diff --git a/docs/en/UI/Angular/Dom-Strategy.md b/docs/en/UI/Angular/Dom-Strategy.md new file mode 100644 index 0000000000..2318e13205 --- /dev/null +++ b/docs/en/UI/Angular/Dom-Strategy.md @@ -0,0 +1,89 @@ +# DomStrategy + +`DomStrategy` is a class exposed by @abp/ng.core package. Its instances define how an element will be attached to the DOM and are consumed by other classes such as `LoadingStrategy`. + + +## API + + +### constructor + +```js +constructor( + public target?: HTMLElement, + public position?: InsertPosition +) +``` + +- `target` is an HTMLElement (_default: document.head_). +- `position` defines where the created element will be placed. All possible values of `position` can be found [here](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement) (_default: 'beforeend'_). + + +### insertElement + +```js +insertElement(element: HTMLElement): void +``` + +This method inserts given `element` to `target` based on the `position`. + + + +## Predefined Dom Strategies + +Predefined dom strategies are accessible via `DOM_STRATEGY` constant. + + +### AppendToBody + +```js +DOM_STRATEGY.AppendToBody() +``` + +`insertElement` will place the given `element` at the end of ``. + + +### AppendToHead + +```js +DOM_STRATEGY.AppendToHead() +``` + +`insertElement` will place the given `element` at the end of ``. + + +### PrependToHead + +```js +DOM_STRATEGY.PrependToHead() +``` + +`insertElement` will place the given `element` at the beginning of ``. + + +### AfterElement + +```js +DOM_STRATEGY.AfterElement(target: HTMLElement) +``` + +`insertElement` will place the given `element` after (as a sibling to) the `target`. + + +### BeforeElement + +```js +DOM_STRATEGY.BeforeElement(target: HTMLElement) +``` + +`insertElement` will place the given `element` before (as a sibling to) the `target`. + + + + +## See Also + +- [DomInsertionService](./Dom-Insertion-Service.md) +- [LazyLoadService](./Lazy-Load-Service.md) +- [LoadingStrategy](./Loading-Strategy.md) +- [ContentStrategy](./Content-Strategy.md) diff --git a/docs/en/UI/Angular/Lazy-Load-Service.md b/docs/en/UI/Angular/Lazy-Load-Service.md new file mode 100644 index 0000000000..a03381869e --- /dev/null +++ b/docs/en/UI/Angular/Lazy-Load-Service.md @@ -0,0 +1,213 @@ +# How to Lazy Load Scripts and Styles + +You can use the `LazyLoadService` in @abp/ng.core package in order to lazy load scripts and styles in an easy and explicit way. + + + + +## Getting Started + +You do not have to provide the `LazyLoadService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. + +```js +import { LazyLoadService } from '@abp/ng.core'; + +@Component({ + /* class metadata here */ +}) +class DemoComponent { + constructor(private lazyLoadService: LazyLoadService) {} +} +``` + + + + +## Usage + +You can use the `load` method of `LazyLoadService` to create a `