From 175b6a416e813e963e88435ef415a719b129099b Mon Sep 17 00:00:00 2001 From: suravi999 Date: Thu, 19 Mar 2020 20:17:28 +0530 Subject: [PATCH 01/68] Update Background-Jobs-Hangfire.md Add Hangfire Configuration --- docs/en/Background-Jobs-Hangfire.md | 47 ++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index 588fcace9e..ebaf8271b8 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -39,5 +39,50 @@ 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 `HttpApiHostModule` class 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.UsePostgreSqlStorage(configuration.GetConnectionString("Default")); + }); + } +```` + +2. We need to add `UseHangfireServer` call in the Configure method in `Startup` class + +If you want to use hangfire's dashboard, you can add it, too: by `UseHangfireDashboard` + +````csharp + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + app.UseHangfireServer(); + app.UseHangfireDashboard(); + } + } +```` From 0e70f9e9b6fc8fc19a8e88a4eaba8eccf9180e40 Mon Sep 17 00:00:00 2001 From: suravi999 Date: Fri, 20 Mar 2020 12:33:59 +0530 Subject: [PATCH 02/68] Update Background-Jobs-Hangfire.md Add requested changes in documentation. --- docs/en/Background-Jobs-Hangfire.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/en/Background-Jobs-Hangfire.md b/docs/en/Background-Jobs-Hangfire.md index ebaf8271b8..622558ac68 100644 --- a/docs/en/Background-Jobs-Hangfire.md +++ b/docs/en/Background-Jobs-Hangfire.md @@ -43,7 +43,7 @@ You can install any storage for Hangfire. The most common one is SQL Server (see After you have installed these NuGet packages, you need to configure your project to use Hangfire. -1.First, we change the `HttpApiHostModule` class to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method: +1.First, we change the `Module` class (example: `HttpApiHostModule`) to add Hangfire configuration of the storage and connection string in the `ConfigureServices` method: ````csharp @@ -61,28 +61,24 @@ After you have installed these NuGet packages, you need to configure your projec { context.Services.AddHangfire(config => { - config.UsePostgreSqlStorage(configuration.GetConnectionString("Default")); + config.UseSqlServerStorage(configuration.GetConnectionString("Default")); }); } ```` -2. We need to add `UseHangfireServer` call in the Configure method in `Startup` class +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 class Startup + public override void OnApplicationInitialization(ApplicationInitializationContext context) { - public void ConfigureServices(IServiceCollection services) - { - services.AddApplication(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) - { - app.InitializeApplication(); - app.UseHangfireServer(); - app.UseHangfireDashboard(); - } + var app = context.GetApplicationBuilder(); + + // ... others + + app.UseHangfireServer(); + app.UseHangfireDashboard(); + } ```` From 3613f906627ea311a8dd89890051ac2cf2a28c76 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 21 Mar 2020 01:59:10 +0300 Subject: [PATCH 03/68] Added popover tag helper hoverable functionality --- .../TagHelpers/Popover/AbpPopoverTagHelper.cs | 2 ++ .../TagHelpers/Popover/AbpPopoverTagHelperService.cs | 10 ++++++++++ .../Pages/Components/Popovers.cshtml | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelper.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelper.cs index d23ac6c912..dfa2c2cfb1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelper.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelper.cs @@ -18,6 +18,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover public bool? Dismissible { get; set; } + public bool? Hoverable { get; set; } + public string AbpPopover { get; set; } public string AbpPopoverRight { get; set; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs index b7bda04134..de5e844888 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs @@ -23,6 +23,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover protected virtual void SetDisabled(TagHelperContext context, TagHelperOutput output) { var triggerAsHtml = TagHelper.Dismissible ?? false ? "datatrigger=\"focus\" " : ""; + // If not dismissable for hoverable condition + if (string.IsNullOrEmpty(triggerAsHtml)) + { + triggerAsHtml = TagHelper.Hoverable ?? false ? "data-trigger=\"hover\" " : string.Empty; + } var dataPlacementAsHtml = "data-placement=\"" + GetDirectory().ToString().ToLowerInvariant() + "\" "; var titleAttribute = output.Attributes.FirstOrDefault(at => at.Name == "title"); var titleAsHtml = titleAttribute == null ? "" : "title=\"" + titleAttribute.Value + "\" "; @@ -40,6 +45,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover if (TagHelper.Dismissible ?? false) { output.Attributes.Add("data-trigger", "focus"); + } + // Dismissible has priority over hoverable + else if (TagHelper.Hoverable ?? false) + { + output.Attributes.Add("data-trigger", "hover"); } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml index b221ebc4f6..624f17cf20 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml @@ -36,7 +36,7 @@ Popover Default - + Popover With Title From c4a1dd6be70920ffd8d7fcf76ebcb973c948d010 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Mon, 23 Mar 2020 10:16:16 +0300 Subject: [PATCH 04/68] Update AbpPopoverTagHelperService.cs --- .../Popover/AbpPopoverTagHelperService.cs | 218 +++++++++--------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs index de5e844888..168d2a5a96 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs @@ -1,115 +1,115 @@ -using System.Linq; -using Microsoft.AspNetCore.Razor.TagHelpers; - -namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover -{ - public class AbpPopoverTagHelperService : AbpTagHelperService - { - public override void Process(TagHelperContext context, TagHelperOutput output) - { - if (!TagHelper.Disabled ?? true) - { - SetDataToggle(context, output); - SetDataPlacement(context, output); - SetPopoverData(context, output); - SetDataTriggerIfDismissible(context, output); - } - else - { - SetDisabled(context, output); - } - } - - protected virtual void SetDisabled(TagHelperContext context, TagHelperOutput output) - { - var triggerAsHtml = TagHelper.Dismissible ?? false ? "datatrigger=\"focus\" " : ""; - // If not dismissable for hoverable condition +using System.Linq; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover +{ + public class AbpPopoverTagHelperService : AbpTagHelperService + { + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (!TagHelper.Disabled ?? true) + { + SetDataToggle(context, output); + SetDataPlacement(context, output); + SetPopoverData(context, output); + SetDataTriggerIfDismissible(context, output); + } + else + { + SetDisabled(context, output); + } + } + + protected virtual void SetDisabled(TagHelperContext context, TagHelperOutput output) + { + var triggerAsHtml = TagHelper.Dismissible ?? false ? "data-trigger=\"focus\" " : ""; + // If not dismissable for hoverable condition if (string.IsNullOrEmpty(triggerAsHtml)) { triggerAsHtml = TagHelper.Hoverable ?? false ? "data-trigger=\"hover\" " : string.Empty; - } - var dataPlacementAsHtml = "data-placement=\"" + GetDirectory().ToString().ToLowerInvariant() + "\" "; - var titleAttribute = output.Attributes.FirstOrDefault(at => at.Name == "title"); - var titleAsHtml = titleAttribute == null ? "" : "title=\"" + titleAttribute.Value + "\" "; - var preElementHtml = ""; - var postElementHtml = ""; - - output.PreElement.SetHtmlContent(preElementHtml); - output.PostElement.SetHtmlContent(postElementHtml); - - output.Attributes.Add("style", "pointer-events: none;"); - } - - protected virtual void SetDataTriggerIfDismissible(TagHelperContext context, TagHelperOutput output) - { - if (TagHelper.Dismissible ?? false) - { - output.Attributes.Add("data-trigger", "focus"); + } + var dataPlacementAsHtml = "data-placement=\"" + GetDirectory().ToString().ToLowerInvariant() + "\" "; + var titleAttribute = output.Attributes.FirstOrDefault(at => at.Name == "title"); + var titleAsHtml = titleAttribute == null ? "" : "title=\"" + titleAttribute.Value + "\" "; + var preElementHtml = ""; + var postElementHtml = ""; + + output.PreElement.SetHtmlContent(preElementHtml); + output.PostElement.SetHtmlContent(postElementHtml); + + output.Attributes.Add("style", "pointer-events: none;"); + } + + protected virtual void SetDataTriggerIfDismissible(TagHelperContext context, TagHelperOutput output) + { + if (TagHelper.Dismissible ?? false) + { + output.Attributes.Add("data-trigger", "focus"); } // Dismissible has priority over hoverable else if (TagHelper.Hoverable ?? false) - { - output.Attributes.Add("data-trigger", "hover"); - } - } - - protected virtual void SetDataToggle(TagHelperContext context, TagHelperOutput output) - { - output.Attributes.Add("data-toggle", "popover"); - } - - protected virtual void SetDataPlacement(TagHelperContext context, TagHelperOutput output) - { - var directory = GetDirectory(); - if (directory == PopoverDirectory.Default) - { - directory = PopoverDirectory.Bottom; - } - output.Attributes.Add("data-placement", directory.ToString().ToLowerInvariant()); - } - - protected virtual void SetPopoverData(TagHelperContext context, TagHelperOutput output) - { - output.Attributes.Add("data-content", GetDataContent()); - } - - protected virtual string GetDataContent() - { - switch (GetDirectory()) - { - case PopoverDirectory.Top: - return TagHelper.AbpPopoverTop; - case PopoverDirectory.Right: - return TagHelper.AbpPopoverRight; - case PopoverDirectory.Bottom: - return TagHelper.AbpPopoverBottom; - case PopoverDirectory.Left: - return TagHelper.AbpPopoverLeft; - default: - return TagHelper.AbpPopover; - } - } - - protected virtual PopoverDirectory GetDirectory() - { - if (!string.IsNullOrWhiteSpace(TagHelper.AbpPopoverTop)) - { - return PopoverDirectory.Top; - } - if (!string.IsNullOrWhiteSpace(TagHelper.AbpPopoverBottom)) - { - return PopoverDirectory.Bottom; - } - if (!string.IsNullOrWhiteSpace(TagHelper.AbpPopoverRight)) - { - return PopoverDirectory.Right; - } - if (!string.IsNullOrWhiteSpace(TagHelper.AbpPopoverLeft)) - { - return PopoverDirectory.Left; - } - - return PopoverDirectory.Default; - } - } -} \ No newline at end of file + { + output.Attributes.Add("data-trigger", "hover"); + } + } + + protected virtual void SetDataToggle(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.Add("data-toggle", "popover"); + } + + protected virtual void SetDataPlacement(TagHelperContext context, TagHelperOutput output) + { + var directory = GetDirectory(); + if (directory == PopoverDirectory.Default) + { + directory = PopoverDirectory.Bottom; + } + output.Attributes.Add("data-placement", directory.ToString().ToLowerInvariant()); + } + + protected virtual void SetPopoverData(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.Add("data-content", GetDataContent()); + } + + protected virtual string GetDataContent() + { + switch (GetDirectory()) + { + case PopoverDirectory.Top: + return TagHelper.AbpPopoverTop; + case PopoverDirectory.Right: + return TagHelper.AbpPopoverRight; + case PopoverDirectory.Bottom: + return TagHelper.AbpPopoverBottom; + case PopoverDirectory.Left: + return TagHelper.AbpPopoverLeft; + default: + return TagHelper.AbpPopover; + } + } + + protected virtual PopoverDirectory GetDirectory() + { + if (!string.IsNullOrWhiteSpace(TagHelper.AbpPopoverTop)) + { + return PopoverDirectory.Top; + } + if (!string.IsNullOrWhiteSpace(TagHelper.AbpPopoverBottom)) + { + return PopoverDirectory.Bottom; + } + if (!string.IsNullOrWhiteSpace(TagHelper.AbpPopoverRight)) + { + return PopoverDirectory.Right; + } + if (!string.IsNullOrWhiteSpace(TagHelper.AbpPopoverLeft)) + { + return PopoverDirectory.Left; + } + + return PopoverDirectory.Default; + } + } +} From 6d0759ff76e8f99f7dd9b3c62fbc77ef2ffa3f72 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Mon, 23 Mar 2020 14:44:45 +0300 Subject: [PATCH 05/68] Added multi usage attributes together --- .../Popover/AbpPopoverTagHelperService.cs | 66 ++++++++++++++----- .../Pages/Components/Popovers.cshtml | 31 +++++---- 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs index de5e844888..7ecb351290 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Popover/AbpPopoverTagHelperService.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Microsoft.AspNetCore.Razor.TagHelpers; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover @@ -13,44 +14,67 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover SetDataPlacement(context, output); SetPopoverData(context, output); SetDataTriggerIfDismissible(context, output); + SetDataTriggerIfHoverable(context, output); } else { SetDisabled(context, output); } - } - + } protected virtual void SetDisabled(TagHelperContext context, TagHelperOutput output) { - var triggerAsHtml = TagHelper.Dismissible ?? false ? "datatrigger=\"focus\" " : ""; - // If not dismissable for hoverable condition - if (string.IsNullOrEmpty(triggerAsHtml)) + var triggerAsHtml = TagHelper.Dismissible ?? false ? "data-trigger=\"focus\" " : ""; + if (TagHelper.Hoverable ?? false) { - triggerAsHtml = TagHelper.Hoverable ?? false ? "data-trigger=\"hover\" " : string.Empty; + if (triggerAsHtml.Contains("focus")) + { + triggerAsHtml = triggerAsHtml.Replace("focus", "focus hover"); + } + else + { + triggerAsHtml = "data-trigger=\"hover\" "; + } } var dataPlacementAsHtml = "data-placement=\"" + GetDirectory().ToString().ToLowerInvariant() + "\" "; + + // data-placement="default" with data-trigger="focus" causes Cannot read property 'indexOf' of undefined at computeAutoPlacement(bootstrap.bundle.js?_v=637146714627330435:2185) error + if (IsDismissibleOrHoverable() && GetDirectory() == PopoverDirectory.Default) + { + //dataPlacementAsHtml = string.Empty; //bootstrap default placement is right, abp's is top. + dataPlacementAsHtml = dataPlacementAsHtml.Replace("default", "top"); + } var titleAttribute = output.Attributes.FirstOrDefault(at => at.Name == "title"); var titleAsHtml = titleAttribute == null ? "" : "title=\"" + titleAttribute.Value + "\" "; - var preElementHtml = ""; + var preElementHtml = ""; var postElementHtml = ""; output.PreElement.SetHtmlContent(preElementHtml); output.PostElement.SetHtmlContent(postElementHtml); output.Attributes.Add("style", "pointer-events: none;"); - } - + } protected virtual void SetDataTriggerIfDismissible(TagHelperContext context, TagHelperOutput output) { if (TagHelper.Dismissible ?? false) { output.Attributes.Add("data-trigger", "focus"); - } - // Dismissible has priority over hoverable - else if (TagHelper.Hoverable ?? false) - { - output.Attributes.Add("data-trigger", "hover"); } + } + + protected virtual void SetDataTriggerIfHoverable(TagHelperContext context, TagHelperOutput output) + { + if (TagHelper.Hoverable ?? false) + { + //If already has focus data trigger + if (output.Attributes.TryGetAttribute("data-trigger", out _)) + { + output.Attributes.SetAttribute(new TagHelperAttribute("data-trigger", "focus hover")); + } + else + { + output.Attributes.Add("data-trigger", "hover"); + } + } } protected virtual void SetDataToggle(TagHelperContext context, TagHelperOutput output) @@ -111,5 +135,17 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Popover return PopoverDirectory.Default; } + protected virtual bool IsDismissibleOrHoverable() + { + if (TagHelper.Dismissible ?? false) + { + return true; + } + if (TagHelper.Hoverable ?? false) + { + return true; + } + return false; + } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml index 624f17cf20..779494d70f 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/Popovers.cshtml @@ -31,20 +31,23 @@

Example

-
- - - Popover Default - - - Popover With Title - - - Dismissible Popover - - - Disabled Popover - +
+ + + Popover Default + + + Popover With Title + + + Dismissible Popover + + + Disabled Popover + + + Disabled Popover +
From 0f1db55c8e35a1ba9f5a275e16b87b2769d41a90 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 24 Mar 2020 17:34:56 +0800 Subject: [PATCH 06/68] Update outdated documentation. Resolve #3327 --- docs/en/Application-Services.md | 47 +++++++++++++++++++++++++--- docs/zh-Hans/Application-Services.md | 47 +++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/docs/en/Application-Services.md b/docs/en/Application-Services.md index 3fb52377b3..da22479593 100644 --- a/docs/en/Application-Services.md +++ b/docs/en/Application-Services.md @@ -146,7 +146,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,7 +158,36 @@ public class BookDto } ```` -* `BookDto` defines `[AbpAutoMapFrom(typeof(Book))]` attribute to create the object mapping from `Book` to `BookDto`. +we creating a [Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances) class. Example: + +````csharp +public class MyProfile : Profile +{ + public MyProfile() + { + CreateMap(); + } +} +```` + +You should then register profiles using the `AbpAutoMapperOptions`: + +````csharp +[DependsOn(typeof(AbpAutoMapperModule))] +public class MyModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + //Add all mappings defined in the assembly of the MyModule class + options.AddMaps(); + }); + } +} +```` + +`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). For more information, please refer to the [object to object mapping](Object-To-Object-Mapping.md) document. Then you can implement the `GetAsync` method as shown below: @@ -250,7 +278,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 +287,6 @@ public class BookDto : AuditedEntityDto public float Price { get; set; } } -[AbpAutoMapTo(typeof(Book))] public class CreateUpdateBookDto { [Required] @@ -275,6 +301,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/zh-Hans/Application-Services.md b/docs/zh-Hans/Application-Services.md index e51f0af87b..72e0319cde 100644 --- a/docs/zh-Hans/Application-Services.md +++ b/docs/zh-Hans/Application-Services.md @@ -146,7 +146,6 @@ public interface IBookAppService : IApplicationService `BookDto`是一个简单的[DTO](Data-Transfer-Objects.md)类, 定义如下: ````csharp -[AbpAutoMapFrom(typeof(Book))] //Defines the mapping public class BookDto { public Guid Id { get; set; } @@ -159,7 +158,36 @@ public class BookDto } ```` -* `BookDto`定义了`[AbpAutoMapFrom(typeof(Book))]`属性来从创建对象映射Book到BookDto. +我们创建一个Automapper的[Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances)类. 例如: + +```csharp +public class MyProfile : Profile +{ + public MyProfile() + { + CreateMap(); + } +} +``` + +然后使用`AbpAutoMapperOptions`注册配置文件: + +````csharp +[DependsOn(typeof(AbpAutoMapperModule))] +public class MyModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + //Add all mappings defined in the assembly of the MyModule class + options.AddMaps(); + }); + } +} +```` + +`AddMaps` 注册给定类的程序集中所有的配置类,通常使用模块类. 它还会注册 [attribute 映射](https://docs.automapper.org/en/stable/Attribute-mapping.html). 更多信息请参考[对象到对象映射](Object-To-Object-Mapping.md)文档 然后你可以实现`GetAsync`方法. 如下所示: @@ -249,7 +277,6 @@ public interface ICrudAppService< 示例中使用的DTO类是`BookDto`和`CreateUpdateBookDto`: ````csharp -[AbpAutoMapFrom(typeof(Book))] public class BookDto : AuditedEntityDto { public string Name { get; set; } @@ -259,7 +286,6 @@ public class BookDto : AuditedEntityDto public float Price { get; set; } } -[AbpAutoMapTo(typeof(Book))] public class CreateUpdateBookDto { [Required] @@ -274,6 +300,19 @@ public class CreateUpdateBookDto } ```` +DTO类的[Profile](https://docs.automapper.org/en/stable/Configuration.html#profile-instances)类. + +```csharp +public class MyProfile : Profile +{ + public MyProfile() + { + CreateMap(); + CreateMap(); + } +} +``` + * `CreateUpdateBookDto`由创建和更新操作共享,但你也可以使用单独的DTO类. 最后`BookAppService`实现非常简单: From a8d4e7c229d8e76f7a174a30de31a02e475d9fe4 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 25 Mar 2020 15:38:37 +0800 Subject: [PATCH 07/68] Use Exists property to determine if the file exists. Resolve #3341 --- .../Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs index 73b6e025ea..40fd3e6ea5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs @@ -63,7 +63,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers foreach (var bundleFile in bundleFiles) { var file = WebContentFileProvider.GetFileInfo(bundleFile); - if (file == null) + if (!file.Exists) { throw new AbpException($"Could not find the bundle file from {nameof(IWebContentFileProvider)}"); } From 283da6783b632bf5d00d834dd8f7b10c098a0b6c Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 26 Mar 2020 14:45:46 +0800 Subject: [PATCH 08/68] Apply Entity Extension System for samples Resolve #3361 --- .../Acme.BookStore.Domain/Users/AppUser.cs | 16 ++++++-- .../Acme.BookStore.Domain/Users/AppUser.cs | 16 ++++++-- .../BookStoreMigrationsDbContext.cs | 7 ---- .../BookStoreMigrationsDbContextFactory.cs | 2 + .../EntityFrameworkCore/BookStoreDbContext.cs | 8 ++-- ...okStoreDbContextModelCreatingExtensions.cs | 8 ---- .../BookStoreEntityExtensions.cs | 33 +++++++++++++++ .../BookStoreEntityFrameworkCoreModule.cs | 5 +++ .../BookManagementEntityExtensions.cs | 32 +++++++++++++++ ...BookManagementEntityFrameworkCoreModule.cs | 5 +++ .../Acme.BookStore.Domain/Users/AppUser.cs | 16 ++++++-- .../BookStoreMigrationsDbContext.cs | 7 ---- .../BookStoreMigrationsDbContextFactory.cs | 2 + .../EntityFrameworkCore/BookStoreDbContext.cs | 8 ++-- ...okStoreDbContextModelCreatingExtensions.cs | 8 ---- .../BookStoreEntityExtensions.cs | 33 +++++++++++++++ .../BookStoreEntityFrameworkCoreModule.cs | 5 +++ .../src/DashboardDemo.Domain/Users/AppUser.cs | 16 ++++++-- .../DashboardDemoMigrationsDbContext.cs | 7 ---- ...DashboardDemoMigrationsDbContextFactory.cs | 2 + .../DashboardDemoDbContext.cs | 8 ++-- ...ardDemoDbContextModelCreatingExtensions.cs | 8 ---- .../DashboardDemoEntityExtensions.cs | 33 +++++++++++++++ .../DashboardDemoEntityFrameworkCoreModule.cs | 5 +++ .../Acme.BookStore.Domain/Users/AppUser.cs | 16 ++++++-- .../BookStoreMigrationsDbContext.cs | 13 ------ .../BookStoreMigrationsDbContextFactory.cs | 2 + .../EntityFrameworkCore/BookStoreDbContext.cs | 8 ++-- ...okStoreDbContextModelCreatingExtensions.cs | 19 +-------- .../BookStoreEntityExtensions.cs | 40 +++++++++++++++++++ .../BookStoreEntityFrameworkCoreModule.cs | 5 +++ 31 files changed, 290 insertions(+), 103 deletions(-) create mode 100644 samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs create mode 100644 samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityExtensions.cs create mode 100644 samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs create mode 100644 samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityExtensions.cs create mode 100644 samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs diff --git a/samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain/Users/AppUser.cs b/samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain/Users/AppUser.cs index 9068cfbcd5..37f67ba60a 100644 --- a/samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain/Users/AppUser.cs +++ b/samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain/Users/AppUser.cs @@ -41,9 +41,19 @@ namespace Acme.BookStore.Users #endregion /* Add your own properties here. Example: - * - * public virtual string MyProperty { get; set; } - */ + * + * public string MyProperty { get; set; } + * + * If you add a property and using the EF Core, remember these; + * + * 1. update BookStoreDbContext.OnModelCreating + * to configure the mapping for your new property + * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * and add your new property to the migration. + * 3. Use the Add-Migration to add a new database migration. + * 4. Run the .DbMigrator project (or use the Update-Database command) to apply + * schema change to the database. + */ private AppUser() { diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.Domain/Users/AppUser.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.Domain/Users/AppUser.cs index 9068cfbcd5..37f67ba60a 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.Domain/Users/AppUser.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.Domain/Users/AppUser.cs @@ -41,9 +41,19 @@ namespace Acme.BookStore.Users #endregion /* Add your own properties here. Example: - * - * public virtual string MyProperty { get; set; } - */ + * + * public string MyProperty { get; set; } + * + * If you add a property and using the EF Core, remember these; + * + * 1. update BookStoreDbContext.OnModelCreating + * to configure the mapping for your new property + * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * and add your new property to the migration. + * 3. Use the Add-Migration to add a new database migration. + * 4. Run the .DbMigrator project (or use the Update-Database command) to apply + * schema change to the database. + */ private AppUser() { diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs index 6a74deef7e..7d7b1cb243 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs @@ -42,13 +42,6 @@ namespace Acme.BookStore.EntityFrameworkCore builder.ConfigureTenantManagement(); builder.ConfigureBookManagement(); - /* Configure customizations for entities from the modules included */ - - builder.Entity(b => - { - b.ConfigureCustomUserProperties(); - }); - /* Configure your own tables/entities inside the ConfigureBookStore method */ builder.ConfigureBookStore(); diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs index 7212eb7340..4f285e597e 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs @@ -11,6 +11,8 @@ namespace Acme.BookStore.EntityFrameworkCore { public BookStoreMigrationsDbContext CreateDbContext(string[] args) { + BookStoreEntityExtensions.Configure(); + var configuration = BuildConfiguration(); var builder = new DbContextOptionsBuilder() diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs index 85644bf40a..7ddeab7a5f 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs @@ -3,6 +3,7 @@ using Acme.BookStore.Users; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; +using Volo.Abp.Identity; using Volo.Abp.Users.EntityFrameworkCore; namespace Acme.BookStore.EntityFrameworkCore @@ -39,12 +40,13 @@ namespace Acme.BookStore.EntityFrameworkCore builder.Entity(b => { - b.ToTable("AbpUsers"); //Sharing the same table "AbpUsers" with the IdentityUser + b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser b.ConfigureByConvention(); b.ConfigureAbpUser(); - //Moved customization to a method so we can share it with the BookStoreMigrationsDbContext class - b.ConfigureCustomUserProperties(); + /* Configure mappings for your additional properties + * Also see the BookStoreEntityExtensions class + */ }); /* Configure your own tables/entities inside the ConfigureBookStore method */ diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs index 1510809d5f..c132d297a4 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs @@ -1,7 +1,5 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; using Volo.Abp; -using Volo.Abp.Users; namespace Acme.BookStore.EntityFrameworkCore { @@ -20,11 +18,5 @@ namespace Acme.BookStore.EntityFrameworkCore // //... //}); } - - public static void ConfigureCustomUserProperties(this EntityTypeBuilder b) - where TUser: class, IUser - { - //b.Property(nameof(AppUser.MyProperty))... - } } } \ No newline at end of file diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs new file mode 100644 index 0000000000..771390698f --- /dev/null +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs @@ -0,0 +1,33 @@ +using Volo.Abp.EntityFrameworkCore.Extensions; +using Volo.Abp.Identity; +using Volo.Abp.Threading; + +namespace Acme.BookStore.EntityFrameworkCore +{ + public static class BookStoreEntityExtensions + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + OneTimeRunner.Run(() => + { + /* You can configure entity extension properties for the + * entities defined in the used modules. + * + * Example: + * + * EntityExtensionManager.AddProperty( + * "MyProperty", + * b => + * { + * b.HasMaxLength(128); + * }); + * + * See the documentation for more: + * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities + */ + }); + } + } +} \ No newline at end of file diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs index 5cb2a6dc7e..c8142b8088 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs @@ -29,6 +29,11 @@ namespace Acme.BookStore.EntityFrameworkCore )] public class BookStoreEntityFrameworkCoreModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + BookStoreEntityExtensions.Configure(); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext(options => diff --git a/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityExtensions.cs b/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityExtensions.cs new file mode 100644 index 0000000000..b7a3871453 --- /dev/null +++ b/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityExtensions.cs @@ -0,0 +1,32 @@ +using Volo.Abp.EntityFrameworkCore.Extensions; +using Volo.Abp.Threading; + +namespace Acme.BookStore.BookManagement.EntityFrameworkCore +{ + public static class BookManagementEntityExtensions + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + OneTimeRunner.Run(() => + { + /* You can configure entity extension properties for the + * entities defined in the used modules. + * + * Example: + * + * EntityExtensionManager.AddProperty( + * "MyProperty", + * b => + * { + * b.HasMaxLength(128); + * }); + * + * See the documentation for more: + * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities + */ + }); + } + } +} \ No newline at end of file diff --git a/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityFrameworkCoreModule.cs b/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityFrameworkCoreModule.cs index c2b2867938..3734551e73 100644 --- a/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityFrameworkCoreModule.cs +++ b/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityFrameworkCoreModule.cs @@ -10,6 +10,11 @@ namespace Acme.BookStore.BookManagement.EntityFrameworkCore )] public class BookManagementEntityFrameworkCoreModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + BookManagementEntityExtensions.Configure(); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext(options => diff --git a/samples/BookStore/src/Acme.BookStore.Domain/Users/AppUser.cs b/samples/BookStore/src/Acme.BookStore.Domain/Users/AppUser.cs index 9068cfbcd5..37f67ba60a 100644 --- a/samples/BookStore/src/Acme.BookStore.Domain/Users/AppUser.cs +++ b/samples/BookStore/src/Acme.BookStore.Domain/Users/AppUser.cs @@ -41,9 +41,19 @@ namespace Acme.BookStore.Users #endregion /* Add your own properties here. Example: - * - * public virtual string MyProperty { get; set; } - */ + * + * public string MyProperty { get; set; } + * + * If you add a property and using the EF Core, remember these; + * + * 1. update BookStoreDbContext.OnModelCreating + * to configure the mapping for your new property + * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * and add your new property to the migration. + * 3. Use the Add-Migration to add a new database migration. + * 4. Run the .DbMigrator project (or use the Update-Database command) to apply + * schema change to the database. + */ private AppUser() { diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs index 541a91a0fd..5287d6f535 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs @@ -40,13 +40,6 @@ namespace Acme.BookStore.EntityFrameworkCore builder.ConfigureFeatureManagement(); builder.ConfigureTenantManagement(); - /* Configure customizations for entities from the modules included */ - - builder.Entity(b => - { - b.ConfigureCustomUserProperties(); - }); - /* Configure your own tables/entities inside the ConfigureBookStore method */ builder.ConfigureBookStore(); diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs index da72e6fb21..cf8a18e468 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs @@ -11,6 +11,8 @@ namespace Acme.BookStore.EntityFrameworkCore { public BookStoreMigrationsDbContext CreateDbContext(string[] args) { + BookStoreEntityExtensions.Configure(); + var configuration = BuildConfiguration(); var builder = new DbContextOptionsBuilder() diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs index 386eb2623b..5849e12137 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs @@ -3,6 +3,7 @@ using Acme.BookStore.Users; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; +using Volo.Abp.Identity; using Volo.Abp.Users.EntityFrameworkCore; namespace Acme.BookStore.EntityFrameworkCore @@ -41,15 +42,16 @@ namespace Acme.BookStore.EntityFrameworkCore builder.Entity(b => { - b.ToTable("AbpUsers"); //Sharing the same table "AbpUsers" with the IdentityUser + b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser b.ConfigureFullAudited(); b.ConfigureExtraProperties(); b.ConfigureConcurrencyStamp(); b.ConfigureAbpUser(); - //Moved customization to a method so we can share it with the BookStoreMigrationsDbContext class - b.ConfigureCustomUserProperties(); + /* Configure mappings for your additional properties + * Also see the BookStoreEntityExtensions class + */ }); /* Configure your own tables/entities inside the ConfigureBookStore method */ diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs index 922793e029..f11ec0d00a 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs @@ -1,8 +1,6 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; using Volo.Abp; using Volo.Abp.EntityFrameworkCore.Modeling; -using Volo.Abp.Users; namespace Acme.BookStore.EntityFrameworkCore { @@ -21,11 +19,5 @@ namespace Acme.BookStore.EntityFrameworkCore b.Property(x => x.Name).IsRequired().HasMaxLength(128); }); } - - public static void ConfigureCustomUserProperties(this EntityTypeBuilder b) - where TUser: class, IUser - { - //b.Property(nameof(AppUser.MyProperty))... - } } } \ No newline at end of file diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs new file mode 100644 index 0000000000..771390698f --- /dev/null +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs @@ -0,0 +1,33 @@ +using Volo.Abp.EntityFrameworkCore.Extensions; +using Volo.Abp.Identity; +using Volo.Abp.Threading; + +namespace Acme.BookStore.EntityFrameworkCore +{ + public static class BookStoreEntityExtensions + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + OneTimeRunner.Run(() => + { + /* You can configure entity extension properties for the + * entities defined in the used modules. + * + * Example: + * + * EntityExtensionManager.AddProperty( + * "MyProperty", + * b => + * { + * b.HasMaxLength(128); + * }); + * + * See the documentation for more: + * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities + */ + }); + } + } +} \ No newline at end of file diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs index 156a4207cb..be5f6039b4 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs @@ -27,6 +27,11 @@ namespace Acme.BookStore.EntityFrameworkCore )] public class BookStoreEntityFrameworkCoreModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + BookStoreEntityExtensions.Configure(); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext(options => diff --git a/samples/DashboardDemo/src/DashboardDemo.Domain/Users/AppUser.cs b/samples/DashboardDemo/src/DashboardDemo.Domain/Users/AppUser.cs index 2f2ba91689..cfad912abe 100644 --- a/samples/DashboardDemo/src/DashboardDemo.Domain/Users/AppUser.cs +++ b/samples/DashboardDemo/src/DashboardDemo.Domain/Users/AppUser.cs @@ -41,9 +41,19 @@ namespace DashboardDemo.Users #endregion /* Add your own properties here. Example: - * - * public virtual string MyProperty { get; set; } - */ + * + * public string MyProperty { get; set; } + * + * If you add a property and using the EF Core, remember these; + * + * 1. update BookStoreDbContext.OnModelCreating + * to configure the mapping for your new property + * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * and add your new property to the migration. + * 3. Use the Add-Migration to add a new database migration. + * 4. Run the .DbMigrator project (or use the Update-Database command) to apply + * schema change to the database. + */ private AppUser() { diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContext.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContext.cs index c94d27faf0..ccec84b7e1 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContext.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContext.cs @@ -40,13 +40,6 @@ namespace DashboardDemo.EntityFrameworkCore builder.ConfigureFeatureManagement(); builder.ConfigureTenantManagement(); - /* Configure customizations for entities from the modules included */ - - builder.Entity(b => - { - b.ConfigureCustomUserProperties(); - }); - /* Configure your own tables/entities inside the ConfigureDashboardDemo method */ builder.ConfigureDashboardDemo(); diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContextFactory.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContextFactory.cs index 0773892abc..10ce7b7da1 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContextFactory.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContextFactory.cs @@ -11,6 +11,8 @@ namespace DashboardDemo.EntityFrameworkCore { public DashboardDemoMigrationsDbContext CreateDbContext(string[] args) { + DashboardDemoEntityExtensions.Configure(); + var configuration = BuildConfiguration(); var builder = new DbContextOptionsBuilder() diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContext.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContext.cs index 94ec22abb3..33c1ddb82d 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContext.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContext.cs @@ -3,6 +3,7 @@ using DashboardDemo.Users; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; +using Volo.Abp.Identity; using Volo.Abp.Users.EntityFrameworkCore; namespace DashboardDemo.EntityFrameworkCore @@ -39,15 +40,16 @@ namespace DashboardDemo.EntityFrameworkCore builder.Entity(b => { - b.ToTable("AbpUsers"); //Sharing the same table "AbpUsers" with the IdentityUser + b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser b.ConfigureFullAudited(); b.ConfigureExtraProperties(); b.ConfigureConcurrencyStamp(); b.ConfigureAbpUser(); - //Moved customization to a method so we can share it with the DashboardDemoMigrationsDbContext class - b.ConfigureCustomUserProperties(); + /* Configure mappings for your additional properties + * Also see the DashboardDemoEntityExtensions class + */ }); /* Configure your own tables/entities inside the ConfigureDashboardDemo method */ diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContextModelCreatingExtensions.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContextModelCreatingExtensions.cs index 60dae7c4cf..0a5d05f6bc 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContextModelCreatingExtensions.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContextModelCreatingExtensions.cs @@ -1,7 +1,5 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; using Volo.Abp; -using Volo.Abp.Users; namespace DashboardDemo.EntityFrameworkCore { @@ -20,11 +18,5 @@ namespace DashboardDemo.EntityFrameworkCore // //... //}); } - - public static void ConfigureCustomUserProperties(this EntityTypeBuilder b) - where TUser: class, IUser - { - //b.Property(nameof(AppUser.MyProperty))... - } } } \ No newline at end of file diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityExtensions.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityExtensions.cs new file mode 100644 index 0000000000..b41ccb8ac8 --- /dev/null +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityExtensions.cs @@ -0,0 +1,33 @@ +using Volo.Abp.EntityFrameworkCore.Extensions; +using Volo.Abp.Identity; +using Volo.Abp.Threading; + +namespace DashboardDemo.EntityFrameworkCore +{ + public static class DashboardDemoEntityExtensions + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + OneTimeRunner.Run(() => + { + /* You can configure entity extension properties for the + * entities defined in the used modules. + * + * Example: + * + * EntityExtensionManager.AddProperty( + * "MyProperty", + * b => + * { + * b.HasMaxLength(128); + * }); + * + * See the documentation for more: + * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities + */ + }); + } + } +} \ No newline at end of file diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityFrameworkCoreModule.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityFrameworkCoreModule.cs index de03726a20..b183c49446 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityFrameworkCoreModule.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityFrameworkCoreModule.cs @@ -27,6 +27,11 @@ namespace DashboardDemo.EntityFrameworkCore )] public class DashboardDemoEntityFrameworkCoreModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + DashboardDemoEntityExtensions.Configure(); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext(options => diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Users/AppUser.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Users/AppUser.cs index 9068cfbcd5..37f67ba60a 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Users/AppUser.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Users/AppUser.cs @@ -41,9 +41,19 @@ namespace Acme.BookStore.Users #endregion /* Add your own properties here. Example: - * - * public virtual string MyProperty { get; set; } - */ + * + * public string MyProperty { get; set; } + * + * If you add a property and using the EF Core, remember these; + * + * 1. update BookStoreDbContext.OnModelCreating + * to configure the mapping for your new property + * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * and add your new property to the migration. + * 3. Use the Add-Migration to add a new database migration. + * 4. Run the .DbMigrator project (or use the Update-Database command) to apply + * schema change to the database. + */ private AppUser() { diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs index 7a83a323d8..8064243388 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs @@ -34,19 +34,6 @@ namespace Acme.BookStore.EntityFrameworkCore builder.ConfigureFeatureManagement(); builder.ConfigureTenantManagement(); - /* Configure customizations for entities from the modules included */ - - //CONFIGURE THE CUSTOM ROLE PROPERTIES - builder.Entity(b => - { - b.ConfigureCustomRoleProperties(); - }); - - builder.Entity(b => - { - b.ConfigureCustomUserProperties(); - }); - /* Configure your own tables/entities inside the ConfigureBookStore method */ builder.ConfigureBookStore(); diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs index da72e6fb21..cf8a18e468 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs @@ -11,6 +11,8 @@ namespace Acme.BookStore.EntityFrameworkCore { public BookStoreMigrationsDbContext CreateDbContext(string[] args) { + BookStoreEntityExtensions.Configure(); + var configuration = BuildConfiguration(); var builder = new DbContextOptionsBuilder() diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs index 091e5820fc..d3726865cd 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs @@ -4,6 +4,7 @@ using Acme.BookStore.Users; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; +using Volo.Abp.Identity; using Volo.Abp.Users.EntityFrameworkCore; namespace Acme.BookStore.EntityFrameworkCore @@ -51,12 +52,13 @@ namespace Acme.BookStore.EntityFrameworkCore builder.Entity(b => { - b.ToTable("AbpUsers"); //Sharing the same table "AbpUsers" with the IdentityUser + b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser b.ConfigureByConvention(); b.ConfigureAbpUser(); - //Moved customization to a method so we can share it with the BookStoreMigrationsDbContext class - b.ConfigureCustomUserProperties(); + /* Configure mappings for your additional properties + * Also see the BookStoreEntityExtensions class + */ }); /* Configure your own tables/entities inside the ConfigureBookStore method */ diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs index 345bde14a0..c132d297a4 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs @@ -1,10 +1,5 @@ -using System; -using Acme.BookStore.Roles; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore; using Volo.Abp; -using Volo.Abp.Domain.Entities; -using Volo.Abp.Users; namespace Acme.BookStore.EntityFrameworkCore { @@ -23,17 +18,5 @@ namespace Acme.BookStore.EntityFrameworkCore // //... //}); } - - public static void ConfigureCustomUserProperties(this EntityTypeBuilder b) - where TUser: class, IUser - { - //b.Property(nameof(AppUser.MyProperty))... - } - - public static void ConfigureCustomRoleProperties(this EntityTypeBuilder b) - where TRole : class, IEntity - { - b.Property(nameof(AppRole.Title)).HasMaxLength(128); - } } } \ No newline at end of file diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs new file mode 100644 index 0000000000..9d59942de8 --- /dev/null +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs @@ -0,0 +1,40 @@ +using Volo.Abp.EntityFrameworkCore.Extensions; +using Volo.Abp.Identity; +using Volo.Abp.Threading; + +namespace Acme.BookStore.EntityFrameworkCore +{ + public static class BookStoreEntityExtensions + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + OneTimeRunner.Run(() => + { + /* You can configure entity extension properties for the + * entities defined in the used modules. + * + * Example: + * + * EntityExtensionManager.AddProperty( + * "MyProperty", + * b => + * { + * b.HasMaxLength(128); + * }); + * + * See the documentation for more: + * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities + */ + + EntityExtensionManager.AddProperty( + nameof(AppRole.Title), + b => + { + b.HasMaxLength(128); + }); + }); + } + } +} \ No newline at end of file diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs index 156a4207cb..be5f6039b4 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs @@ -27,6 +27,11 @@ namespace Acme.BookStore.EntityFrameworkCore )] public class BookStoreEntityFrameworkCoreModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + BookStoreEntityExtensions.Configure(); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext(options => From 3d85fd01417a5e141c06847b90124e82f78868fc Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 26 Mar 2020 15:48:30 +0800 Subject: [PATCH 09/68] Undo changes to EfCoreMigrationDemo. --- build/common.ps1 | 1 + .../Acme.BookStore.Domain/Users/AppUser.cs | 16 ++------ .../BookStoreMigrationsDbContext.cs | 13 ++++++ .../BookStoreMigrationsDbContextFactory.cs | 3 +- .../EntityFrameworkCore/BookStoreDbContext.cs | 8 ++-- ...okStoreDbContextModelCreatingExtensions.cs | 19 ++++++++- .../BookStoreEntityExtensions.cs | 40 ------------------- .../BookStoreEntityFrameworkCoreModule.cs | 5 --- ...BookStore.EntityFrameworkCore.Tests.csproj | 1 + .../BookStoreEntityFrameworkCoreTestModule.cs | 12 +++++- .../BookStoreWebTestModule.cs | 8 ++++ 11 files changed, 59 insertions(+), 67 deletions(-) delete mode 100644 samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs 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/samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Users/AppUser.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Users/AppUser.cs index 37f67ba60a..9068cfbcd5 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Users/AppUser.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.Domain/Users/AppUser.cs @@ -41,19 +41,9 @@ namespace Acme.BookStore.Users #endregion /* Add your own properties here. Example: - * - * public string MyProperty { get; set; } - * - * If you add a property and using the EF Core, remember these; - * - * 1. update BookStoreDbContext.OnModelCreating - * to configure the mapping for your new property - * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity - * and add your new property to the migration. - * 3. Use the Add-Migration to add a new database migration. - * 4. Run the .DbMigrator project (or use the Update-Database command) to apply - * schema change to the database. - */ + * + * public virtual string MyProperty { get; set; } + */ private AppUser() { diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs index 8064243388..7a83a323d8 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContext.cs @@ -34,6 +34,19 @@ namespace Acme.BookStore.EntityFrameworkCore builder.ConfigureFeatureManagement(); builder.ConfigureTenantManagement(); + /* Configure customizations for entities from the modules included */ + + //CONFIGURE THE CUSTOM ROLE PROPERTIES + builder.Entity(b => + { + b.ConfigureCustomRoleProperties(); + }); + + builder.Entity(b => + { + b.ConfigureCustomUserProperties(); + }); + /* Configure your own tables/entities inside the ConfigureBookStore method */ builder.ConfigureBookStore(); diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs index cf8a18e468..7364cd29ab 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs @@ -11,8 +11,7 @@ namespace Acme.BookStore.EntityFrameworkCore { public BookStoreMigrationsDbContext CreateDbContext(string[] args) { - BookStoreEntityExtensions.Configure(); - + var configuration = BuildConfiguration(); var builder = new DbContextOptionsBuilder() diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs index d3726865cd..091e5820fc 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs @@ -4,7 +4,6 @@ using Acme.BookStore.Users; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.Modeling; -using Volo.Abp.Identity; using Volo.Abp.Users.EntityFrameworkCore; namespace Acme.BookStore.EntityFrameworkCore @@ -52,13 +51,12 @@ namespace Acme.BookStore.EntityFrameworkCore builder.Entity(b => { - b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser + b.ToTable("AbpUsers"); //Sharing the same table "AbpUsers" with the IdentityUser b.ConfigureByConvention(); b.ConfigureAbpUser(); - /* Configure mappings for your additional properties - * Also see the BookStoreEntityExtensions class - */ + //Moved customization to a method so we can share it with the BookStoreMigrationsDbContext class + b.ConfigureCustomUserProperties(); }); /* Configure your own tables/entities inside the ConfigureBookStore method */ diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs index c132d297a4..345bde14a0 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContextModelCreatingExtensions.cs @@ -1,5 +1,10 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Acme.BookStore.Roles; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Volo.Abp; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Users; namespace Acme.BookStore.EntityFrameworkCore { @@ -18,5 +23,17 @@ namespace Acme.BookStore.EntityFrameworkCore // //... //}); } + + public static void ConfigureCustomUserProperties(this EntityTypeBuilder b) + where TUser: class, IUser + { + //b.Property(nameof(AppUser.MyProperty))... + } + + public static void ConfigureCustomRoleProperties(this EntityTypeBuilder b) + where TRole : class, IEntity + { + b.Property(nameof(AppRole.Title)).HasMaxLength(128); + } } } \ No newline at end of file diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs deleted file mode 100644 index 9d59942de8..0000000000 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Volo.Abp.EntityFrameworkCore.Extensions; -using Volo.Abp.Identity; -using Volo.Abp.Threading; - -namespace Acme.BookStore.EntityFrameworkCore -{ - public static class BookStoreEntityExtensions - { - private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); - - public static void Configure() - { - OneTimeRunner.Run(() => - { - /* You can configure entity extension properties for the - * entities defined in the used modules. - * - * Example: - * - * EntityExtensionManager.AddProperty( - * "MyProperty", - * b => - * { - * b.HasMaxLength(128); - * }); - * - * See the documentation for more: - * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities - */ - - EntityExtensionManager.AddProperty( - nameof(AppRole.Title), - b => - { - b.HasMaxLength(128); - }); - }); - } - } -} \ No newline at end of file diff --git a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs index be5f6039b4..156a4207cb 100644 --- a/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs +++ b/samples/EfCoreMigrationDemo/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs @@ -27,11 +27,6 @@ namespace Acme.BookStore.EntityFrameworkCore )] public class BookStoreEntityFrameworkCoreModule : AbpModule { - public override void PreConfigureServices(ServiceConfigurationContext context) - { - BookStoreEntityExtensions.Configure(); - } - public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext(options => diff --git a/samples/EfCoreMigrationDemo/test/Acme.BookStore.EntityFrameworkCore.Tests/Acme.BookStore.EntityFrameworkCore.Tests.csproj b/samples/EfCoreMigrationDemo/test/Acme.BookStore.EntityFrameworkCore.Tests/Acme.BookStore.EntityFrameworkCore.Tests.csproj index dd2137ad06..b03b0ed22d 100644 --- a/samples/EfCoreMigrationDemo/test/Acme.BookStore.EntityFrameworkCore.Tests/Acme.BookStore.EntityFrameworkCore.Tests.csproj +++ b/samples/EfCoreMigrationDemo/test/Acme.BookStore.EntityFrameworkCore.Tests/Acme.BookStore.EntityFrameworkCore.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/samples/EfCoreMigrationDemo/test/Acme.BookStore.EntityFrameworkCore.Tests/EntityFrameworkCore/BookStoreEntityFrameworkCoreTestModule.cs b/samples/EfCoreMigrationDemo/test/Acme.BookStore.EntityFrameworkCore.Tests/EntityFrameworkCore/BookStoreEntityFrameworkCoreTestModule.cs index e1171d6d90..a3f2bb5aa7 100644 --- a/samples/EfCoreMigrationDemo/test/Acme.BookStore.EntityFrameworkCore.Tests/EntityFrameworkCore/BookStoreEntityFrameworkCoreTestModule.cs +++ b/samples/EfCoreMigrationDemo/test/Acme.BookStore.EntityFrameworkCore.Tests/EntityFrameworkCore/BookStoreEntityFrameworkCoreTestModule.cs @@ -1,4 +1,5 @@ -using Microsoft.Data.Sqlite; +using Acme.BookStore.DbMigrationsForSecondDb.EntityFrameworkCore; +using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; @@ -51,6 +52,15 @@ namespace Acme.BookStore.EntityFrameworkCore context.GetService().CreateTables(); } + var secondOptions = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + using (var context = new BookStoreSecondMigrationsDbContext(secondOptions)) + { + context.GetService().CreateTables(); + } + return connection; } } diff --git a/samples/EfCoreMigrationDemo/test/Acme.BookStore.Web.Tests/BookStoreWebTestModule.cs b/samples/EfCoreMigrationDemo/test/Acme.BookStore.Web.Tests/BookStoreWebTestModule.cs index 46e4312035..786015d18b 100644 --- a/samples/EfCoreMigrationDemo/test/Acme.BookStore.Web.Tests/BookStoreWebTestModule.cs +++ b/samples/EfCoreMigrationDemo/test/Acme.BookStore.Web.Tests/BookStoreWebTestModule.cs @@ -12,6 +12,7 @@ using Acme.BookStore.Web; using Acme.BookStore.Web.Menus; using Volo.Abp; using Volo.Abp.AspNetCore.TestBase; +using Volo.Abp.Data; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.UI.Navigation; @@ -38,6 +39,13 @@ namespace Acme.BookStore { ConfigureLocalizationServices(context.Services); ConfigureNavigationServices(context.Services); + + Configure(options => + { + //SqliteConnection does not support nested transactions. + //So Dbcontext and connectionString need be same. + options.ConnectionStrings.Clear(); + }); } private static void ConfigureLocalizationServices(IServiceCollection services) From 272ca7b315c224ebd71929c12826ab78172c2f2e Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 05:13:28 +0300 Subject: [PATCH 10/68] added initial post --- docs/en/Blog-Posts/2020-04-01/Post.md | 176 ++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 docs/en/Blog-Posts/2020-04-01/Post.md diff --git a/docs/en/Blog-Posts/2020-04-01/Post.md b/docs/en/Blog-Posts/2020-04-01/Post.md new file mode 100644 index 0000000000..bc3148ec5e --- /dev/null +++ b/docs/en/Blog-Posts/2020-04-01/Post.md @@ -0,0 +1,176 @@ +# Using Azure Active Directory Authentication in ABP Applications + +## Introduction + +This post 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. + +There will be two samples of connections for better covarage; + +- **AddOpenIdConnect** (Default Microsoft.AspNetCore.Authentication.OpenIdConnect package) +- **AddAzureAD** (Microsoft.AspNetCore.Authentication.AzureAD.UI package) + + + +## AddOpenIdConnect + +#### **Update your `appsettings.json`** + +In your **.Web** application, add the following section filled with your AzureAD application settings. + +````xml + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "", + "ClientSecret": "", + "Domain": "domain.onmicrosoft.com", + "CallbackPath": "/signin-azuread-oidc" + } +```` + +Modify `ConfigureAuthentication` method of your **BookStoreWebModule** with the following: + +````xml +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", "AzureAD", options => + { + options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"]; + options.ClientId = configuration["AzureAd:ClientId"]; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.CallbackPath = configuration["AzureAd:CallbackPath"]; + options.ClientSecret = configuration["AzureAd:ClientSecret"]; + options.RequireHttpsMetadata = false; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + }); + } +```` + + + +## AddAzureAD + +#### **Update your `appsettings.json`** + +Install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** application. + +In your **.Web** application, add the following section filled with your AzureAD application settings. + +Notice that you don't need to add `ClientSecret` when you are using `Microsoft.AspNetCore.Authentication.AzureAD.UI` package. + +````xml + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "", + "Domain": "domain.onmicrosoft.com", + "CallbackPath": "/signin-azuread-oidc" + } +```` + +Modify `ConfigureAuthentication` method of your **BookStoreWebModule** with the following: + +````xml +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.IdToken; + options.RequireHttpsMetadata = false; + + options.TokenValidationParameters.ValidateIssuer = false; + options.GetClaimsFromUserInfoEndpoint = true; + options.SaveTokens = true; + options.SignInScheme = IdentityConstants.ExternalScheme; + + options.Scope.Add("email"); + }); + } +```` + + + +# 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`: + + ````xml + options.SignInScheme = IdentityConstants.ExternalScheme; + ```` + + 2. Your `ClaimTypes.NameIdentifier` is null. Check if you added claim mapping: + + ````xml + 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: + + ````xml + "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`. + +* 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: + + ````xml + options.Events.OnTokenValidated = (async context => + { + var claimsFromOidcProvider = context.Principal.Claims.ToList(); + await Task.CompletedTask; + }); + ```` + +* I want to debug further, how can I implement my custom **SignInManager**? + + * You can check [Customizing SignInManager in Abp Framework](link here) post. + +* I want to add extra properties to user while they are being created. How can I do that? + + * You can check [Customizing Login Page in Abp Framework]() post. + +* Why can't I see **External Register Page** after I sign in from external provider for the first time? + + * ABP framework automatically registers your user with supported email claim from your external authentication provider. You can change this behaviour by [Customizing Login Page in Abp Framework](will be link here). + From a6099b10fd59e3fea4473d2390664e39b7bf6ad5 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 05:30:01 +0300 Subject: [PATCH 11/68] added post --- docs/en/Blog-Posts/2020-04-02/Post.md | 74 +++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/en/Blog-Posts/2020-04-02/Post.md diff --git a/docs/en/Blog-Posts/2020-04-02/Post.md b/docs/en/Blog-Posts/2020-04-02/Post.md new file mode 100644 index 0000000000..a2df997091 --- /dev/null +++ b/docs/en/Blog-Posts/2020-04-02/Post.md @@ -0,0 +1,74 @@ +# Custom SignIn Manager + +ABP Framework uses Microsoft Identity underneath hence supports customization as much as Microsoft Identity does. + +### Creating CustomSignInManager + +To create your own custom SignIn Manager, you need to inherit `SignInManager`. + +````xml +public class CustomSignInManager : SignInManager +{ + public CustomSigninManager( + UserManager userManager, + IHttpContextAccessor contextAccessor, + IUserClaimsPrincipalFactory claimsFactory, + IOptions optionsAccessor, + ILogger> logger, + IAuthenticationSchemeProvider schemes, + IUserConfirmation confirmation) + : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation) + { + } +} +```` + +### Overriding Methods + +Afterwards you can override a method like `GetExternalLoginInfoAsync`: + +````xml +public override async Task GetExternalLoginInfoAsync(string expectedXsrf = null) +{ + var auth = await Context.AuthenticateAsync(IdentityConstants.ExternalScheme); + var items = auth?.Properties?.Items; + if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProvider")) + { + return null; + } + + if (expectedXsrf != null) + { + if (!items.ContainsKey("XsrfId")) + { + 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 ExternalLoginInfo(auth.Principal, provider, providerKey, providerDisplayName) + { + AuthenticationTokens = auth.Properties.GetTokens() + }; +} +```` + +### Registering to DI + +You need to register your Custom SignIn Manager to DI to activate it. Inside the `.Web` project, locate the `ApplicationNameWebModule` add the following under `ConfigureServices` method: + +````xml +context.Services +.GetObject() + .AddSignInManager(); +```` \ No newline at end of file From d305b39cb1b03022bd7358a9003473b70620a12f Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 05:53:14 +0300 Subject: [PATCH 12/68] naming update --- docs/en/Blog-Posts/2020-04-01/Post.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Blog-Posts/2020-04-01/Post.md b/docs/en/Blog-Posts/2020-04-01/Post.md index bc3148ec5e..6650aabbe5 100644 --- a/docs/en/Blog-Posts/2020-04-01/Post.md +++ b/docs/en/Blog-Posts/2020-04-01/Post.md @@ -1,4 +1,4 @@ -# Using Azure Active Directory Authentication in ABP Applications +# Using Azure Active Directory Authentication in ABP ASP.NET Core MVC application ## Introduction From 4689f4835aa093733fd53a9729a71b68dc8f817a Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 07:29:14 +0300 Subject: [PATCH 13/68] added post --- docs/en/Blog-Posts/2020-04-03/Post.md | 122 ++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 docs/en/Blog-Posts/2020-04-03/Post.md diff --git a/docs/en/Blog-Posts/2020-04-03/Post.md b/docs/en/Blog-Posts/2020-04-03/Post.md new file mode 100644 index 0000000000..3ac5d212c2 --- /dev/null +++ b/docs/en/Blog-Posts/2020-04-03/Post.md @@ -0,0 +1,122 @@ +# Custom Login PageModel in **ABP** ASP.NET Core MVC application + +ABP Framework uses Microsoft Identity underneath hence supports customization as much as Microsoft Identity does. + +### Creating Login PageModel + +To create your own custom Login PageModel, you need to inherit [Abp LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs). + +````xml +public class CustomLoginModel : LoginModel +{ + public CustomLoginModel( + Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider, + Microsoft.Extensions.Options.IOptions accountOptions) + : base(schemeProvider, accountOptions) + { + } +} +```` + +### Overriding Methods + +Afterwards you can override a method like `CreateExternalUserAsync`: + +````xml +protected override async Task CreateExternalUserAsync(ExternalLoginInfo info) +{ + var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email); + + var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id); + + CheckIdentityErrors(await UserManager.CreateAsync(user)); + CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress)); + CheckIdentityErrors(await UserManager.AddLoginAsync(user, info)); + + return user; +} +```` + +### Overriding Login Page UI + +Overriding `.cshtml` files can be easily done via [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). Create folder named **Account** under **Pages** directory. Create **Login.cshtml** under Pages/Account directory. + +Set the model with your newly created Login Page Model and customize to your preferences like: + +````xml +@page +@using Volo.Abp.Account.Settings +@using Volo.Abp.Settings +@model Acme.BookStore.Web.CustomLoginModel +@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage +@inject Volo.Abp.Settings.ISettingProvider SettingProvider + +
+

My Customized Login Page!

+
+@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"] +
+} +```` + +Further readings, [ASP.NET Core (MVC / Razor Pages) User Interface Customization Guide](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface#asp-net-core-mvc-razor-pages-user-interface-customization-guide). \ No newline at end of file From a3e990e33585f234cbafed9e071db2cff8ee4fdd Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 15:19:44 +0300 Subject: [PATCH 14/68] removed client secret settings from openId --- docs/en/Blog-Posts/2020-04-01/Post.md | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/docs/en/Blog-Posts/2020-04-01/Post.md b/docs/en/Blog-Posts/2020-04-01/Post.md index 6650aabbe5..9b2f49cd51 100644 --- a/docs/en/Blog-Posts/2020-04-01/Post.md +++ b/docs/en/Blog-Posts/2020-04-01/Post.md @@ -13,8 +13,6 @@ There will be two samples of connections for better covarage; -## AddOpenIdConnect - #### **Update your `appsettings.json`** In your **.Web** application, add the following section filled with your AzureAD application settings. @@ -30,6 +28,10 @@ In your **.Web** application, add the following section filled with your AzureAD } ```` + + +## AddOpenIdConnect + Modify `ConfigureAuthentication` method of your **BookStoreWebModule** with the following: ````xml @@ -51,7 +53,6 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi options.ClientId = configuration["AzureAd:ClientId"]; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.CallbackPath = configuration["AzureAd:CallbackPath"]; - options.ClientSecret = configuration["AzureAd:ClientSecret"]; options.RequireHttpsMetadata = false; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; @@ -67,21 +68,7 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi Install `Microsoft.AspNetCore.Authentication.AzureAD.UI` package to your **.Web** application. -In your **.Web** application, add the following section filled with your AzureAD application settings. - -Notice that you don't need to add `ClientSecret` when you are using `Microsoft.AspNetCore.Authentication.AzureAD.UI` package. - -````xml - "AzureAd": { - "Instance": "https://login.microsoftonline.com/", - "TenantId": "", - "Domain": "domain.onmicrosoft.com", - "CallbackPath": "/signin-azuread-oidc" - } -```` - -Modify `ConfigureAuthentication` method of your **BookStoreWebModule** with the following: +In your **.Web** application, add the following section filled with your AzureAD application settings. Modify `ConfigureAuthentication` method of your **BookStoreWebModule** with the following: ````xml private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) From 0be742fa0628c62aa96fa4d9a97b1f0f991c207f Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 15:36:43 +0300 Subject: [PATCH 15/68] updated post --- docs/en/Blog-Posts/2020-04-01/Post.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/en/Blog-Posts/2020-04-01/Post.md b/docs/en/Blog-Posts/2020-04-01/Post.md index 9b2f49cd51..b57883ac8b 100644 --- a/docs/en/Blog-Posts/2020-04-01/Post.md +++ b/docs/en/Blog-Posts/2020-04-01/Post.md @@ -22,7 +22,6 @@ In your **.Web** application, add the following section filled with your AzureAD "Instance": "https://login.microsoftonline.com/", "TenantId": "", - "ClientSecret": "", "Domain": "domain.onmicrosoft.com", "CallbackPath": "/signin-azuread-oidc" } @@ -108,7 +107,7 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi * Help! `GetExternalLoginInfoAsync` returns `null`! - * There can be 2 reasons for this; + * **There** can be 2 reasons for this; 1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`: @@ -137,6 +136,17 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi 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 + + ````xml + 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: From d8f3d5b85c65322f84cbb3b1a93e5085fa432218 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 16:00:40 +0300 Subject: [PATCH 16/68] changed order of connections --- docs/en/Blog-Posts/2020-04-01/Post.md | 76 +++++++++++++-------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/docs/en/Blog-Posts/2020-04-01/Post.md b/docs/en/Blog-Posts/2020-04-01/Post.md index b57883ac8b..9f1d3d964b 100644 --- a/docs/en/Blog-Posts/2020-04-01/Post.md +++ b/docs/en/Blog-Posts/2020-04-01/Post.md @@ -8,10 +8,9 @@ Adding Azure Active Directory is pretty straightforward in Abp framework. Couple There will be two samples of connections for better covarage; -- **AddOpenIdConnect** (Default Microsoft.AspNetCore.Authentication.OpenIdConnect package) - **AddAzureAD** (Microsoft.AspNetCore.Authentication.AzureAD.UI package) - - +- **AddOpenIdConnect** (Default Microsoft.AspNetCore.Authentication.OpenIdConnect package) +- #### **Update your `appsettings.json`** @@ -29,38 +28,6 @@ In your **.Web** application, add the following section filled with your AzureAD -## AddOpenIdConnect - -Modify `ConfigureAuthentication` method of your **BookStoreWebModule** with the following: - -````xml -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", "AzureAD", options => - { - options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"]; - options.ClientId = configuration["AzureAd:ClientId"]; - options.ResponseType = OpenIdConnectResponseType.CodeIdToken; - options.CallbackPath = configuration["AzureAd:CallbackPath"]; - options.RequireHttpsMetadata = false; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - }); - } -```` - - - ## AddAzureAD #### **Update your `appsettings.json`** @@ -103,11 +70,43 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi +## AddOpenIdConnect + +Modify `ConfigureAuthentication` method of your **BookStoreWebModule** with the following: + +````xml +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", "AzureAD", options => + { + options.Authority = "https://login.microsoftonline.com/" + configuration["AzureAd:TenantId"]; + options.ClientId = configuration["AzureAd:ClientId"]; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.CallbackPath = configuration["AzureAd:CallbackPath"]; + options.RequireHttpsMetadata = false; + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + }); + } +```` + + + # FAQ * Help! `GetExternalLoginInfoAsync` returns `null`! - * **There** can be 2 reasons for this; + * There can be 2 reasons for this; 1. You are trying to authenticate against wrong scheme. Check if you set **SignInScheme** to `IdentityConstants.ExternalScheme`: @@ -115,7 +114,7 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi options.SignInScheme = IdentityConstants.ExternalScheme; ```` - 2. Your `ClaimTypes.NameIdentifier` is null. Check if you added claim mapping: + 2. Your `ClaimTypes.NameIdentifier` is `null`. Check if you added claim mapping: ````xml JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); @@ -169,5 +168,4 @@ private void ConfigureAuthentication(ServiceConfigurationContext context, IConfi * Why can't I see **External Register Page** after I sign in from external provider for the first time? - * ABP framework automatically registers your user with supported email claim from your external authentication provider. You can change this behaviour by [Customizing Login Page in Abp Framework](will be link here). - + * ABP framework automatically registers your user with supported email claim from your external authentication provider. You can change this behavior by [Customizing Login Page in Abp Framework](will be link here). From 01378dc04c37dd3eab4a0957f4410b627875c0b7 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 16:09:04 +0300 Subject: [PATCH 17/68] organized headers --- docs/en/Blog-Posts/2020-04-01/Post.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/en/Blog-Posts/2020-04-01/Post.md b/docs/en/Blog-Posts/2020-04-01/Post.md index 9f1d3d964b..2fb937467b 100644 --- a/docs/en/Blog-Posts/2020-04-01/Post.md +++ b/docs/en/Blog-Posts/2020-04-01/Post.md @@ -4,17 +4,24 @@ This post 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. +Adding Azure Active Directory is pretty straightforward in ABP framework. Couple of configurations needs to be done correctly. -There will be two samples of connections for better covarage; +There will be two samples of connections for better coverage; - **AddAzureAD** (Microsoft.AspNetCore.Authentication.AzureAD.UI package) - **AddOpenIdConnect** (Default Microsoft.AspNetCore.Authentication.OpenIdConnect package) -- -#### **Update your `appsettings.json`** -In your **.Web** application, add the following section filled with your AzureAD application settings. + +## Sample Code + +https://github.com/abpframework/abp-samples/tree/master/aspnet-core/BookStore-AzureAD + + + +## Setup + +Update your `appsettings.json` in your **.Web** application and add the following section filled with your AzureAD application settings. ````xml "AzureAd": { From 2867cc34f9e8de7c57827a0765a38773649e5c79 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 16:14:26 +0300 Subject: [PATCH 18/68] updated headers and added sample link --- docs/en/Blog-Posts/2020-04-03/Post.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/en/Blog-Posts/2020-04-03/Post.md b/docs/en/Blog-Posts/2020-04-03/Post.md index 3ac5d212c2..7ee3c8b1ed 100644 --- a/docs/en/Blog-Posts/2020-04-03/Post.md +++ b/docs/en/Blog-Posts/2020-04-03/Post.md @@ -1,8 +1,18 @@ # Custom Login PageModel in **ABP** ASP.NET Core MVC application +## Introduction + ABP Framework uses Microsoft Identity underneath hence supports customization as much as Microsoft Identity does. -### Creating Login PageModel + + +## Sample Code + +https://github.com/abpframework/abp-samples/tree/master/aspnet-core/BookStore-AzureAD + + + +## Creating Login PageModel To create your own custom Login PageModel, you need to inherit [Abp LoginModel](https://github.com/abpframework/abp/blob/037ef9abe024c03c1f89ab6c933710bcfe3f5c93/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs). @@ -18,7 +28,9 @@ public class CustomLoginModel : LoginModel } ```` -### Overriding Methods + + +## Overriding Methods Afterwards you can override a method like `CreateExternalUserAsync`: @@ -37,7 +49,9 @@ protected override async Task CreateExternalUser } ```` -### Overriding Login Page UI + + +## Overriding Login Page UI Overriding `.cshtml` files can be easily done via [Virtual File System](https://docs.abp.io/en/abp/latest/Virtual-File-System). Create folder named **Account** under **Pages** directory. Create **Login.cshtml** under Pages/Account directory. From 90c3a36ec11add5c048aeffd1747e82a7ee6b24a Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 16:17:04 +0300 Subject: [PATCH 19/68] updated headers and added sample link --- docs/en/Blog-Posts/2020-04-02/Post.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/en/Blog-Posts/2020-04-02/Post.md b/docs/en/Blog-Posts/2020-04-02/Post.md index a2df997091..7919d49032 100644 --- a/docs/en/Blog-Posts/2020-04-02/Post.md +++ b/docs/en/Blog-Posts/2020-04-02/Post.md @@ -1,8 +1,18 @@ # Custom SignIn Manager +## Introduction + ABP Framework uses Microsoft Identity underneath hence supports customization as much as Microsoft Identity does. -### Creating CustomSignInManager + + +## Sample Code + +https://github.com/abpframework/abp-samples/blob/master/aspnet-core/BookStore-AzureAD/src/Acme.BookStore.Web/CustomSignInManager.cs + + + +## Creating CustomSignInManager To create your own custom SignIn Manager, you need to inherit `SignInManager`. @@ -23,7 +33,9 @@ public class CustomSignInManager : SignInManager } ```` -### Overriding Methods + + +## Overriding Methods Afterwards you can override a method like `GetExternalLoginInfoAsync`: @@ -63,9 +75,11 @@ public override async Task GetExternalLoginInfoAsync(string e } ```` -### Registering to DI -You need to register your Custom SignIn Manager to DI to activate it. Inside the `.Web` project, locate the `ApplicationNameWebModule` add the following under `ConfigureServices` method: + +## Registering to DI + +You need to register your Custom SignIn Manager to DI to activate it. Inside the `.Web` project, locate the `ApplicationNameWebModule` and add the following under `ConfigureServices` method: ````xml context.Services From 4b5cb6a96937ecc7b71eb3e891924176f918a8e7 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Sat, 28 Mar 2020 16:18:30 +0300 Subject: [PATCH 20/68] udpated link --- docs/en/Blog-Posts/2020-04-03/Post.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Blog-Posts/2020-04-03/Post.md b/docs/en/Blog-Posts/2020-04-03/Post.md index 7ee3c8b1ed..a95eb6c873 100644 --- a/docs/en/Blog-Posts/2020-04-03/Post.md +++ b/docs/en/Blog-Posts/2020-04-03/Post.md @@ -8,7 +8,7 @@ ABP Framework uses Microsoft Identity underneath hence supports customization as ## Sample Code -https://github.com/abpframework/abp-samples/tree/master/aspnet-core/BookStore-AzureAD +https://github.com/abpframework/abp-samples/blob/master/aspnet-core/BookStore-AzureAD/src/Acme.BookStore.Web/Pages/CustomLoginModel.cs From d0ee68e612c7e362ba1108e9a5c57aacd47913f5 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 30 Mar 2020 10:33:20 +0800 Subject: [PATCH 21/68] Use object extension system. --- .../Acme.BookStore.Domain/Users/AppUser.cs | 4 ++-- .../Acme.BookStore.Domain/Users/AppUser.cs | 4 ++-- .../BookStoreMigrationsDbContextFactory.cs | 2 +- .../EntityFrameworkCore/BookStoreDbContext.cs | 2 +- .../BookStoreEntityExtensions.cs | 22 +++++++++++-------- .../BookStoreEntityFrameworkCoreModule.cs | 2 +- ...anagementEfCoreEntityExtensionMappings.cs} | 20 ++++++++++------- ...BookManagementEntityFrameworkCoreModule.cs | 2 +- .../Acme.BookStore.Domain/Users/AppUser.cs | 4 ++-- .../BookStoreMigrationsDbContextFactory.cs | 2 +- .../EntityFrameworkCore/BookStoreDbContext.cs | 2 +- ...BookStoreEfCoreEntityExtensionMappings.cs} | 22 +++++++++++-------- .../BookStoreEntityFrameworkCoreModule.cs | 2 +- .../src/DashboardDemo.Domain/Users/AppUser.cs | 4 ++-- ...DashboardDemoMigrationsDbContextFactory.cs | 2 +- .../DashboardDemoDbContext.cs | 2 +- ...boardDemoEfCoreEntityExtensionMappings.cs} | 22 +++++++++++-------- .../DashboardDemoEntityFrameworkCoreModule.cs | 2 +- .../Users/AppUser.cs | 2 +- 19 files changed, 70 insertions(+), 54 deletions(-) rename samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/{BookManagementEntityExtensions.cs => BookManagementEfCoreEntityExtensionMappings.cs} (51%) rename samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/{BookStoreEntityExtensions.cs => BookStoreEfCoreEntityExtensionMappings.cs} (50%) rename samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/{DashboardDemoEntityExtensions.cs => DashboardDemoEfCoreEntityExtensionMappings.cs} (50%) diff --git a/samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain/Users/AppUser.cs b/samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain/Users/AppUser.cs index 37f67ba60a..b746e68052 100644 --- a/samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain/Users/AppUser.cs +++ b/samples/BookStore-Angular-MongoDb/aspnet-core/src/Acme.BookStore.Domain/Users/AppUser.cs @@ -46,9 +46,9 @@ namespace Acme.BookStore.Users * * If you add a property and using the EF Core, remember these; * - * 1. update BookStoreDbContext.OnModelCreating + * 1. Update BookStoreDbContext.OnModelCreating * to configure the mapping for your new property - * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * 2. Update BookStoreEfCoreEntityExtensionMappings to extend the IdentityUser entity * and add your new property to the migration. * 3. Use the Add-Migration to add a new database migration. * 4. Run the .DbMigrator project (or use the Update-Database command) to apply diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.Domain/Users/AppUser.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.Domain/Users/AppUser.cs index 37f67ba60a..b746e68052 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.Domain/Users/AppUser.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.Domain/Users/AppUser.cs @@ -46,9 +46,9 @@ namespace Acme.BookStore.Users * * If you add a property and using the EF Core, remember these; * - * 1. update BookStoreDbContext.OnModelCreating + * 1. Update BookStoreDbContext.OnModelCreating * to configure the mapping for your new property - * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * 2. Update BookStoreEfCoreEntityExtensionMappings to extend the IdentityUser entity * and add your new property to the migration. * 3. Use the Add-Migration to add a new database migration. * 4. Run the .DbMigrator project (or use the Update-Database command) to apply diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs index 4f285e597e..dad059f971 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs @@ -11,7 +11,7 @@ namespace Acme.BookStore.EntityFrameworkCore { public BookStoreMigrationsDbContext CreateDbContext(string[] args) { - BookStoreEntityExtensions.Configure(); + BookStoreEfCoreEntityExtensionMappings.Configure(); var configuration = BuildConfiguration(); diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs index 7ddeab7a5f..bfa602ef6b 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs @@ -45,7 +45,7 @@ namespace Acme.BookStore.EntityFrameworkCore b.ConfigureAbpUser(); /* Configure mappings for your additional properties - * Also see the BookStoreEntityExtensions class + * Also see the BookStoreEfCoreEntityExtensionMappings class */ }); diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs index 771390698f..cadfa73ccd 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs @@ -1,10 +1,10 @@ -using Volo.Abp.EntityFrameworkCore.Extensions; -using Volo.Abp.Identity; +using Volo.Abp.Identity; +using Volo.Abp.ObjectExtending; using Volo.Abp.Threading; namespace Acme.BookStore.EntityFrameworkCore { - public static class BookStoreEntityExtensions + public static class BookStoreEfCoreEntityExtensionMappings { private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); @@ -15,14 +15,18 @@ namespace Acme.BookStore.EntityFrameworkCore /* You can configure entity extension properties for the * entities defined in the used modules. * + * The properties defined here becomes table fields. + * If you want to use the ExtraProperties dictionary of the entity + * instead of creating a new field, then define the property in the + * MyProjectNameDomainObjectExtensions class. + * * Example: * - * EntityExtensionManager.AddProperty( - * "MyProperty", - * b => - * { - * b.HasMaxLength(128); - * }); + * ObjectExtensionManager.Instance + * .MapEfCoreProperty( + * "MyProperty", + * b => b.HasMaxLength(128) + * ); * * See the documentation for more: * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs index c8142b8088..58999b2b0d 100644 --- a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs +++ b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs @@ -31,7 +31,7 @@ namespace Acme.BookStore.EntityFrameworkCore { public override void PreConfigureServices(ServiceConfigurationContext context) { - BookStoreEntityExtensions.Configure(); + BookStoreEfCoreEntityExtensionMappings.Configure(); } public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityExtensions.cs b/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEfCoreEntityExtensionMappings.cs similarity index 51% rename from samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityExtensions.cs rename to samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEfCoreEntityExtensionMappings.cs index b7a3871453..d4358d0b66 100644 --- a/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityExtensions.cs +++ b/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEfCoreEntityExtensionMappings.cs @@ -1,9 +1,9 @@ -using Volo.Abp.EntityFrameworkCore.Extensions; +using Volo.Abp.ObjectExtending; using Volo.Abp.Threading; namespace Acme.BookStore.BookManagement.EntityFrameworkCore { - public static class BookManagementEntityExtensions + public static class BookManagementEfCoreEntityExtensionMappings { private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); @@ -14,14 +14,18 @@ namespace Acme.BookStore.BookManagement.EntityFrameworkCore /* You can configure entity extension properties for the * entities defined in the used modules. * + * The properties defined here becomes table fields. + * If you want to use the ExtraProperties dictionary of the entity + * instead of creating a new field, then define the property in the + * MyProjectNameDomainObjectExtensions class. + * * Example: * - * EntityExtensionManager.AddProperty( - * "MyProperty", - * b => - * { - * b.HasMaxLength(128); - * }); + * ObjectExtensionManager.Instance + * .MapEfCoreProperty( + * "MyProperty", + * b => b.HasMaxLength(128) + * ); * * See the documentation for more: * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities diff --git a/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityFrameworkCoreModule.cs b/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityFrameworkCoreModule.cs index 3734551e73..758486bf7e 100644 --- a/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityFrameworkCoreModule.cs +++ b/samples/BookStore-Modular/modules/book-management/src/Acme.BookStore.BookManagement.EntityFrameworkCore/EntityFrameworkCore/BookManagementEntityFrameworkCoreModule.cs @@ -12,7 +12,7 @@ namespace Acme.BookStore.BookManagement.EntityFrameworkCore { public override void PreConfigureServices(ServiceConfigurationContext context) { - BookManagementEntityExtensions.Configure(); + BookManagementEfCoreEntityExtensionMappings.Configure(); } public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/samples/BookStore/src/Acme.BookStore.Domain/Users/AppUser.cs b/samples/BookStore/src/Acme.BookStore.Domain/Users/AppUser.cs index 37f67ba60a..b746e68052 100644 --- a/samples/BookStore/src/Acme.BookStore.Domain/Users/AppUser.cs +++ b/samples/BookStore/src/Acme.BookStore.Domain/Users/AppUser.cs @@ -46,9 +46,9 @@ namespace Acme.BookStore.Users * * If you add a property and using the EF Core, remember these; * - * 1. update BookStoreDbContext.OnModelCreating + * 1. Update BookStoreDbContext.OnModelCreating * to configure the mapping for your new property - * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * 2. Update BookStoreEfCoreEntityExtensionMappings to extend the IdentityUser entity * and add your new property to the migration. * 3. Use the Add-Migration to add a new database migration. * 4. Run the .DbMigrator project (or use the Update-Database command) to apply diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs index cf8a18e468..52307142f4 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/BookStoreMigrationsDbContextFactory.cs @@ -11,7 +11,7 @@ namespace Acme.BookStore.EntityFrameworkCore { public BookStoreMigrationsDbContext CreateDbContext(string[] args) { - BookStoreEntityExtensions.Configure(); + BookStoreEfCoreEntityExtensionMappings.Configure(); var configuration = BuildConfiguration(); diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs index 5849e12137..c2df7ee6d0 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreDbContext.cs @@ -50,7 +50,7 @@ namespace Acme.BookStore.EntityFrameworkCore b.ConfigureAbpUser(); /* Configure mappings for your additional properties - * Also see the BookStoreEntityExtensions class + * Also see the BookStoreEfCoreEntityExtensionMappings class */ }); diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEfCoreEntityExtensionMappings.cs similarity index 50% rename from samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs rename to samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEfCoreEntityExtensionMappings.cs index 771390698f..cadfa73ccd 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEfCoreEntityExtensionMappings.cs @@ -1,10 +1,10 @@ -using Volo.Abp.EntityFrameworkCore.Extensions; -using Volo.Abp.Identity; +using Volo.Abp.Identity; +using Volo.Abp.ObjectExtending; using Volo.Abp.Threading; namespace Acme.BookStore.EntityFrameworkCore { - public static class BookStoreEntityExtensions + public static class BookStoreEfCoreEntityExtensionMappings { private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); @@ -15,14 +15,18 @@ namespace Acme.BookStore.EntityFrameworkCore /* You can configure entity extension properties for the * entities defined in the used modules. * + * The properties defined here becomes table fields. + * If you want to use the ExtraProperties dictionary of the entity + * instead of creating a new field, then define the property in the + * MyProjectNameDomainObjectExtensions class. + * * Example: * - * EntityExtensionManager.AddProperty( - * "MyProperty", - * b => - * { - * b.HasMaxLength(128); - * }); + * ObjectExtensionManager.Instance + * .MapEfCoreProperty( + * "MyProperty", + * b => b.HasMaxLength(128) + * ); * * See the documentation for more: * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities diff --git a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs index be5f6039b4..3faeb576da 100644 --- a/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs +++ b/samples/BookStore/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityFrameworkCoreModule.cs @@ -29,7 +29,7 @@ namespace Acme.BookStore.EntityFrameworkCore { public override void PreConfigureServices(ServiceConfigurationContext context) { - BookStoreEntityExtensions.Configure(); + BookStoreEfCoreEntityExtensionMappings.Configure(); } public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/samples/DashboardDemo/src/DashboardDemo.Domain/Users/AppUser.cs b/samples/DashboardDemo/src/DashboardDemo.Domain/Users/AppUser.cs index cfad912abe..43f40fed20 100644 --- a/samples/DashboardDemo/src/DashboardDemo.Domain/Users/AppUser.cs +++ b/samples/DashboardDemo/src/DashboardDemo.Domain/Users/AppUser.cs @@ -46,9 +46,9 @@ namespace DashboardDemo.Users * * If you add a property and using the EF Core, remember these; * - * 1. update BookStoreDbContext.OnModelCreating + * 1. Update BookStoreDbContext.OnModelCreating * to configure the mapping for your new property - * 2. Update BookStoreEntityExtensions to extend the IdentityUser entity + * 2. Update DashboardDemoEfCoreEntityExtensionMappings to extend the IdentityUser entity * and add your new property to the migration. * 3. Use the Add-Migration to add a new database migration. * 4. Run the .DbMigrator project (or use the Update-Database command) to apply diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContextFactory.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContextFactory.cs index 10ce7b7da1..b9f1526ad4 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContextFactory.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore.DbMigrations/EntityFrameworkCore/DashboardDemoMigrationsDbContextFactory.cs @@ -11,7 +11,7 @@ namespace DashboardDemo.EntityFrameworkCore { public DashboardDemoMigrationsDbContext CreateDbContext(string[] args) { - DashboardDemoEntityExtensions.Configure(); + DashboardDemoEfCoreEntityExtensionMappings.Configure(); var configuration = BuildConfiguration(); diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContext.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContext.cs index 33c1ddb82d..a5d0bdea2e 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContext.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoDbContext.cs @@ -48,7 +48,7 @@ namespace DashboardDemo.EntityFrameworkCore b.ConfigureAbpUser(); /* Configure mappings for your additional properties - * Also see the DashboardDemoEntityExtensions class + * Also see the DashboardDemoEfCoreEntityExtensionMappings class */ }); diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityExtensions.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEfCoreEntityExtensionMappings.cs similarity index 50% rename from samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityExtensions.cs rename to samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEfCoreEntityExtensionMappings.cs index b41ccb8ac8..2e5ce4d0bf 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityExtensions.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEfCoreEntityExtensionMappings.cs @@ -1,10 +1,10 @@ -using Volo.Abp.EntityFrameworkCore.Extensions; -using Volo.Abp.Identity; +using Volo.Abp.Identity; +using Volo.Abp.ObjectExtending; using Volo.Abp.Threading; namespace DashboardDemo.EntityFrameworkCore { - public static class DashboardDemoEntityExtensions + public static class DashboardDemoEfCoreEntityExtensionMappings { private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); @@ -15,14 +15,18 @@ namespace DashboardDemo.EntityFrameworkCore /* You can configure entity extension properties for the * entities defined in the used modules. * + * The properties defined here becomes table fields. + * If you want to use the ExtraProperties dictionary of the entity + * instead of creating a new field, then define the property in the + * MyProjectNameDomainObjectExtensions class. + * * Example: * - * EntityExtensionManager.AddProperty( - * "MyProperty", - * b => - * { - * b.HasMaxLength(128); - * }); + * ObjectExtensionManager.Instance + * .MapEfCoreProperty( + * "MyProperty", + * b => b.HasMaxLength(128) + * ); * * See the documentation for more: * https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities diff --git a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityFrameworkCoreModule.cs b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityFrameworkCoreModule.cs index b183c49446..043cf10197 100644 --- a/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityFrameworkCoreModule.cs +++ b/samples/DashboardDemo/src/DashboardDemo.EntityFrameworkCore/EntityFrameworkCore/DashboardDemoEntityFrameworkCoreModule.cs @@ -29,7 +29,7 @@ namespace DashboardDemo.EntityFrameworkCore { public override void PreConfigureServices(ServiceConfigurationContext context) { - DashboardDemoEntityExtensions.Configure(); + DashboardDemoEfCoreEntityExtensionMappings.Configure(); } public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/Users/AppUser.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/Users/AppUser.cs index cd5c8dbb3d..1b5b3996ee 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/Users/AppUser.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/Users/AppUser.cs @@ -46,7 +46,7 @@ namespace MyCompanyName.MyProjectName.Users * * If you add a property and using the EF Core, remember these; * - * 1. update MyProjectNameDbContext.OnModelCreating + * 1. Update MyProjectNameDbContext.OnModelCreating * to configure the mapping for your new property * 2. Update MyProjectNameEfCoreEntityExtensionMappings to extend the IdentityUser entity * and add your new property to the migration. From 4a83c2db832b698f478074290fb20b1687699abf Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 30 Mar 2020 10:39:07 +0800 Subject: [PATCH 22/68] Rename BookStoreEntityExtensions to BookStoreEfCoreEntityExtensionMappings. --- ...ityExtensions.cs => BookStoreEfCoreEntityExtensionMappings.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/{BookStoreEntityExtensions.cs => BookStoreEfCoreEntityExtensionMappings.cs} (100%) diff --git a/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs b/samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEfCoreEntityExtensionMappings.cs similarity index 100% rename from samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEntityExtensions.cs rename to samples/BookStore-Modular/application/src/Acme.BookStore.EntityFrameworkCore/EntityFrameworkCore/BookStoreEfCoreEntityExtensionMappings.cs From a05cf07cf42f469dd3e794b6bd63eb3d7b321dfc Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 30 Mar 2020 11:41:32 +0800 Subject: [PATCH 23/68] Set ExternalProviders and EnableLocalLogin properties in the page's OnPost method. Resolve #3400 --- .../IdentityServerSupportedLoginModel.cs | 19 ++++------- .../Pages/Account/Login.cshtml.cs | 33 ++++++++++++------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index 179e18246a..e194c1544d 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -68,18 +68,11 @@ namespace Volo.Abp.Account.Web.Pages.Account return Page(); } - var schemes = await SchemeProvider.GetAllSchemesAsync(); - - var providers = schemes - .Where(x => x.DisplayName != null || x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) - .Select(x => new ExternalProviderModel - { - DisplayName = x.DisplayName, - AuthenticationScheme = x.Name - }) - .ToList(); + var providers = await GetExternalProviders(); + ExternalProviders = providers.ToList(); EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); + if (context?.ClientId != null) { var client = await ClientStore.FindEnabledClientByIdAsync(context.ClientId); @@ -94,8 +87,6 @@ namespace Volo.Abp.Account.Web.Pages.Account } } - ExternalProviders = providers.ToArray(); - if (IsExternalLoginOnly) { return await base.OnPostExternalLogin(providers.First().AuthenticationScheme); @@ -124,6 +115,10 @@ namespace Volo.Abp.Account.Web.Pages.Account ValidateModel(); + ExternalProviders = await GetExternalProviders(); + + EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); + await ReplaceEmailToUsernameOfInputIfNeeds(); var result = await SignInManager.PasswordSignInAsync( diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 014088983a..5f15df99b4 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -63,21 +63,10 @@ namespace Volo.Abp.Account.Web.Pages.Account { LoginInput = new LoginInputModel(); - var schemes = await SchemeProvider.GetAllSchemesAsync(); - - var providers = schemes - .Where(x => x.DisplayName != null || x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) - .Select(x => new ExternalProviderModel - { - DisplayName = x.DisplayName, - AuthenticationScheme = x.Name - }) - .ToList(); + ExternalProviders = await GetExternalProviders(); EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); - ExternalProviders = providers.ToArray(); - if (IsExternalLoginOnly) { //return await ExternalLogin(vm.ExternalLoginScheme, returnUrl); @@ -94,6 +83,10 @@ namespace Volo.Abp.Account.Web.Pages.Account ValidateModel(); + ExternalProviders = await GetExternalProviders(); + + EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); + await ReplaceEmailToUsernameOfInputIfNeeds(); var result = await SignInManager.PasswordSignInAsync( @@ -140,6 +133,22 @@ namespace Volo.Abp.Account.Web.Pages.Account return RedirectSafely(ReturnUrl, ReturnUrlHash); } + protected virtual async Task> GetExternalProviders() + { + var schemes = await SchemeProvider.GetAllSchemesAsync(); + + var providers = schemes + .Where(x => x.DisplayName != null || x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) + .Select(x => new ExternalProviderModel + { + DisplayName = x.DisplayName, + AuthenticationScheme = x.Name + }) + .ToList(); + + return providers.ToList(); + } + [UnitOfWork] public virtual async Task OnPostExternalLogin(string provider) { From 3f26e2b8ac6f7c952c6aced41e99faf82b42df2e Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 30 Mar 2020 14:48:31 +0800 Subject: [PATCH 24/68] Refactor. --- .../src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 5f15df99b4..24e7732c9c 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -137,7 +137,7 @@ namespace Volo.Abp.Account.Web.Pages.Account { var schemes = await SchemeProvider.GetAllSchemesAsync(); - var providers = schemes + return schemes .Where(x => x.DisplayName != null || x.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) .Select(x => new ExternalProviderModel { @@ -145,8 +145,6 @@ namespace Volo.Abp.Account.Web.Pages.Account AuthenticationScheme = x.Name }) .ToList(); - - return providers.ToList(); } [UnitOfWork] From 686614ab7f51c0755692dfd89422e1471626f8c1 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 1 Apr 2020 15:58:29 +0800 Subject: [PATCH 25/68] Add builderAction parameter to the BuildConfiguration method. Resolve #3429 --- .../Extensions/Configuration/ConfigurationHelper.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs index 44b75d1adb..82d393eef3 100644 --- a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs @@ -6,7 +6,8 @@ namespace Microsoft.Extensions.Configuration public static class ConfigurationHelper { public static IConfigurationRoot BuildConfiguration( - AbpConfigurationBuilderOptions options = null) + AbpConfigurationBuilderOptions options = null, + Action builderAction = null) { options = options ?? new AbpConfigurationBuilderOptions(); @@ -43,6 +44,8 @@ namespace Microsoft.Extensions.Configuration builder = builder.AddCommandLine(options.CommandLineArgs); } + builderAction?.Invoke(builder); + return builder.Build(); } } From b19519ece2df150a22998a141c772eb399d79682 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 1 Apr 2020 20:51:00 +0300 Subject: [PATCH 26/68] feat(core): add dom strategy --- .../core/src/lib/strategies/dom.strategy.ts | 28 +++++++++++ .../packages/core/src/lib/strategies/index.ts | 1 + .../core/src/lib/tests/dom.strategy.spec.ts | 49 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/strategies/dom.strategy.ts create mode 100644 npm/ng-packs/packages/core/src/lib/strategies/index.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/strategies/dom.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/dom.strategy.ts new file mode 100644 index 0000000000..4fbe18d235 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/strategies/dom.strategy.ts @@ -0,0 +1,28 @@ +export class DomStrategy { + constructor( + public target: HTMLElement = document.head, + public position: InsertPosition = 'beforeend', + ) {} + + insertElement(element: T) { + this.target.insertAdjacentElement(this.position, element); + } +} + +export const DOM_STRATEGY = { + AfterElement(element: HTMLElement) { + return new DomStrategy(element, 'afterend'); + }, + AppendToBody() { + return new DomStrategy(document.body, 'beforeend'); + }, + AppendToHead() { + return new DomStrategy(document.head, 'beforeend'); + }, + BeforeElement(element: HTMLElement) { + return new DomStrategy(element, 'beforebegin'); + }, + PrependToHead() { + return new DomStrategy(document.head, 'afterbegin'); + }, +}; diff --git a/npm/ng-packs/packages/core/src/lib/strategies/index.ts b/npm/ng-packs/packages/core/src/lib/strategies/index.ts new file mode 100644 index 0000000000..2506a819de --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/strategies/index.ts @@ -0,0 +1 @@ +export * from './dom.strategy'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts new file mode 100644 index 0000000000..7e5f264f2b --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts @@ -0,0 +1,49 @@ +import { DomStrategy, DOM_STRATEGY } from '../strategies'; + +describe('DomStrategy', () => { + describe('#insertElement', () => { + it('should append element to head by default', () => { + const strategy = new DomStrategy(); + const element = document.createElement('script'); + strategy.insertElement(element); + + expect(document.head.lastChild).toBe(element); + }); + + it('should append element to body when body is given as target', () => { + const strategy = new DomStrategy(document.body); + const element = document.createElement('script'); + strategy.insertElement(element); + + expect(document.body.lastChild).toBe(element); + }); + + it('should prepend to head when position is given as "afterbegin"', () => { + const strategy = new DomStrategy(undefined, 'afterbegin'); + const element = document.createElement('script'); + strategy.insertElement(element); + + expect(document.head.firstChild).toBe(element); + }); + }); +}); + +describe('DOM_STRATEGY', () => { + let div = document.createElement('DIV'); + + beforeEach(() => { + document.body.innerHTML = ''; + document.body.appendChild(div); + }); + + test.each` + name | target | position + ${'AfterElement'} | ${div} | ${'afterend'} + ${'AppendToBody'} | ${document.body} | ${'beforeend'} + ${'AppendToHead'} | ${document.head} | ${'beforeend'} + ${'BeforeElement'} | ${div} | ${'beforebegin'} + ${'PrependToHead'} | ${document.head} | ${'afterbegin'} + `('should successfully map $name to CrossOriginStrategy', ({ name, target, position }) => { + expect(DOM_STRATEGY[name](target)).toEqual(new DomStrategy(target, position)); + }); +}); From cb51ca7fc14660db52281a6556bfcfdb70309f98 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 1 Apr 2020 20:52:02 +0300 Subject: [PATCH 27/68] feat(core): add cross-origin strategy --- .../lib/strategies/cross-origin.strategy.ts | 17 +++++++++ .../packages/core/src/lib/strategies/index.ts | 1 + .../lib/tests/cross-origin.strategy.spec.ts | 38 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/strategies/cross-origin.strategy.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/cross-origin.strategy.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/strategies/cross-origin.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/cross-origin.strategy.ts new file mode 100644 index 0000000000..c56d7a3d54 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/strategies/cross-origin.strategy.ts @@ -0,0 +1,17 @@ +export class CrossOriginStrategy { + constructor(public crossorigin: 'anonymous' | 'use-credentials', public integrity?: string) {} + + setCrossOrigin(element: T) { + if (this.integrity) element.setAttribute('integrity', this.integrity); + element.setAttribute('crossorigin', this.crossorigin); + } +} + +export const CROSS_ORIGIN_STRATEGY = { + Anonymous(integrity?: string) { + return new CrossOriginStrategy('anonymous', integrity); + }, + UseCredentials(integrity?: string) { + return new CrossOriginStrategy('use-credentials', integrity); + }, +}; diff --git a/npm/ng-packs/packages/core/src/lib/strategies/index.ts b/npm/ng-packs/packages/core/src/lib/strategies/index.ts index 2506a819de..d1a367e138 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/index.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/index.ts @@ -1 +1,2 @@ +export * from './cross-origin.strategy'; export * from './dom.strategy'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/cross-origin.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/cross-origin.strategy.spec.ts new file mode 100644 index 0000000000..25cc6f0c93 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/cross-origin.strategy.spec.ts @@ -0,0 +1,38 @@ +import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from '../strategies'; +import { uuid } from '../utils'; + +describe('CrossOriginStrategy', () => { + describe('#setCrossOrigin', () => { + it('should set crossorigin attribute', () => { + const strategy = new CrossOriginStrategy('use-credentials'); + const element = document.createElement('link'); + strategy.setCrossOrigin(element); + + expect(element.crossOrigin).toBe('use-credentials'); + }); + + it('should set integrity attribute when given', () => { + const integrity = uuid(); + const strategy = new CrossOriginStrategy('anonymous', integrity); + const element = document.createElement('link'); + strategy.setCrossOrigin(element); + + expect(element.crossOrigin).toBe('anonymous'); + expect(element.getAttribute('integrity')).toBe(integrity); + }); + }); +}); + +describe('CROSS_ORIGIN_STRATEGY', () => { + test.each` + name | integrity | crossOrigin + ${'Anonymous'} | ${undefined} | ${'anonymous'} + ${'Anonymous'} | ${uuid()} | ${'anonymous'} + ${'UseCredentials'} | ${undefined} | ${'use-credentials'} + ${'UseCredentials'} | ${uuid()} | ${'use-credentials'} + `('should successfully map $name to CrossOriginStrategy', ({ name, integrity, crossOrigin }) => { + expect(CROSS_ORIGIN_STRATEGY[name](integrity)).toEqual( + new CrossOriginStrategy(crossOrigin, integrity), + ); + }); +}); From b55bc42779912c395856a7ea8d1d1725c9ee86ea Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 1 Apr 2020 20:53:30 +0300 Subject: [PATCH 28/68] feat(core): add fromLazyLoad utility function --- .../src/lib/tests/lazy-load-utils.spec.ts | 105 ++++++++++++++++++ .../packages/core/src/lib/utils/index.ts | 1 + .../core/src/lib/utils/lazy-load-utils.ts | 54 +++++++++ 3 files changed, 160 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts new file mode 100644 index 0000000000..657afeead1 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts @@ -0,0 +1,105 @@ +import { DomStrategy, DOM_STRATEGY } from '../strategies'; +import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy'; +import { uuid } from '../utils'; +import { fromLazyLoad } from '../utils/lazy-load-utils'; + +describe('Lazy Load Utils', () => { + describe('#fromLazyLoad', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should append to head by default', () => { + const element = document.createElement('link'); + const spy = jest.spyOn(document.head, 'insertAdjacentElement'); + + fromLazyLoad(element); + expect(spy).toHaveBeenCalledWith('beforeend', element); + }); + + it('should allow setting a dom strategy', () => { + const element = document.createElement('link'); + const spy = jest.spyOn(document.head, 'insertAdjacentElement'); + + fromLazyLoad(element, DOM_STRATEGY.PrependToHead()); + expect(spy).toHaveBeenCalledWith('afterbegin', element); + }); + + it('should set crossorigin to "anonymous" by default', () => { + const element = document.createElement('link'); + + fromLazyLoad(element); + + expect(element.crossOrigin).toBe('anonymous'); + }); + + it('should allow setting a crossorigin strategy', () => { + const element = document.createElement('link'); + + const integrity = uuid(); + + fromLazyLoad(element, undefined, CROSS_ORIGIN_STRATEGY.UseCredentials(integrity)); + + expect(element.crossOrigin).toBe('use-credentials'); + expect(element.getAttribute('integrity')).toBe(integrity); + }); + + it('should emit error event on fail and clear callbacks', done => { + const error = new CustomEvent('error'); + const parentNode = { removeChild: jest.fn() }; + const element = ({ parentNode } as any) as HTMLLinkElement; + + fromLazyLoad( + element, + { + insertElement(el: HTMLLinkElement) { + expect(el).toBe(element); + + setTimeout(() => { + el.onerror(error); + }, 0); + }, + } as DomStrategy, + { + setCrossOrigin(el: HTMLLinkElement) {}, + } as CrossOriginStrategy, + ).subscribe({ + error: value => { + expect(value).toBe(error); + expect(parentNode.removeChild).toHaveBeenCalledWith(element); + expect(element.onerror).toBeNull(); + done(); + }, + }); + }); + + it('should emit load event on success and clear callbacks', done => { + const success = new CustomEvent('load'); + const parentNode = { removeChild: jest.fn() }; + const element = ({ parentNode } as any) as HTMLLinkElement; + + fromLazyLoad( + element, + { + insertElement(el: HTMLLinkElement) { + expect(el).toBe(element); + + setTimeout(() => { + el.onload(success); + }, 0); + }, + } as DomStrategy, + { + setCrossOrigin(el: HTMLLinkElement) {}, + } as CrossOriginStrategy, + ).subscribe({ + next: value => { + expect(value).toBe(success); + expect(parentNode.removeChild).not.toHaveBeenCalled(); + expect(element.onload).toBeNull(); + done(); + }, + }); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/utils/index.ts b/npm/ng-packs/packages/core/src/lib/utils/index.ts index 0043152ada..a0404f7568 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/index.ts @@ -1,5 +1,6 @@ export * from './common-utils'; export * from './generator-utils'; export * from './initial-utils'; +export * from './lazy-load-utils'; export * from './route-utils'; export * from './rxjs-utils'; diff --git a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts new file mode 100644 index 0000000000..5aee3bc715 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts @@ -0,0 +1,54 @@ +import { Observable, Observer } from 'rxjs'; +import { + CrossOriginStrategy, + CROSS_ORIGIN_STRATEGY, + DomStrategy, + DOM_STRATEGY, +} from '../strategies'; + +export function fromLazyLoad( + element: HTMLScriptElement | HTMLLinkElement, + domStrategy: DomStrategy = DOM_STRATEGY.AppendToHead(), + crossOriginStrategy: CrossOriginStrategy = CROSS_ORIGIN_STRATEGY.Anonymous(), +): Observable { + crossOriginStrategy.setCrossOrigin(element); + domStrategy.insertElement(element); + + return Observable.create((observer: Observer) => { + element.onload = event => { + clearCallbacks(element); + observer.next(event); + observer.complete(); + }; + + const handleError = createErrorHandler(observer, element); + + element.onerror = handleError; + element.onabort = handleError; + element.onemptied = handleError; + element.onstalled = handleError; + element.onsuspend = handleError; + + return () => { + clearCallbacks(element); + observer.complete(); + }; + }); +} + +function createErrorHandler(observer: Observer, element: HTMLElement) { + return function(event: Event | string) { + clearCallbacks(element); + element.parentNode.removeChild(element); + observer.error(event); + }; +} + +function clearCallbacks(element: HTMLElement) { + element.onload = null; + element.onerror = null; + element.onabort = null; + element.onemptied = null; + element.onstalled = null; + element.onsuspend = null; +} From 79b080671843e18eb30163594877b5a27b4768bf Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 1 Apr 2020 20:53:59 +0300 Subject: [PATCH 29/68] feat(core): add loading strategies --- .../packages/core/src/lib/strategies/index.ts | 1 + .../src/lib/strategies/loading.strategy.ts | 68 ++++++++++++++ .../src/lib/tests/loading.strategy.spec.ts | 90 +++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/strategies/index.ts b/npm/ng-packs/packages/core/src/lib/strategies/index.ts index d1a367e138..915887f92a 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/index.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/index.ts @@ -1,2 +1,3 @@ export * from './cross-origin.strategy'; export * from './dom.strategy'; +export * from './loading.strategy'; diff --git a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts new file mode 100644 index 0000000000..eae23a0c7f --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts @@ -0,0 +1,68 @@ +import { Observable, of } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { fromLazyLoad } from '../utils'; +import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from './cross-origin.strategy'; +import { DomStrategy, DOM_STRATEGY } from './dom.strategy'; + +export abstract class LoadingStrategy { + constructor( + public path: string, + protected domStrategy: DomStrategy = DOM_STRATEGY.AppendToHead(), + protected crossOriginStrategy: CrossOriginStrategy = CROSS_ORIGIN_STRATEGY.Anonymous(), + ) {} + + abstract createElement(): T; + + createStream(): Observable { + return of(null).pipe( + switchMap(() => + fromLazyLoad(this.createElement(), this.domStrategy, this.crossOriginStrategy), + ), + ); + } +} + +export class ScriptLoadingStrategy extends LoadingStrategy { + constructor(src: string, domStrategy?: DomStrategy, crossOriginStrategy?: CrossOriginStrategy) { + super(src, domStrategy, crossOriginStrategy); + } + + createElement(): HTMLScriptElement { + const element = document.createElement('script'); + element.src = this.path; + + return element; + } +} + +export class StyleLoadingStrategy extends LoadingStrategy { + constructor(href: string, domStrategy?: DomStrategy, crossOriginStrategy?: CrossOriginStrategy) { + super(href, domStrategy, crossOriginStrategy); + } + + createElement(): HTMLLinkElement { + const element = document.createElement('link'); + element.rel = 'stylesheet'; + element.href = this.path; + + return element; + } +} + +export const LOADING_STRATEGY = { + AppendAnonymousScriptToBody(src: string) { + return new ScriptLoadingStrategy(src, DOM_STRATEGY.AppendToBody()); + }, + AppendAnonymousScriptToHead(src: string) { + return new ScriptLoadingStrategy(src); + }, + AppendAnonymousStyleToHead(src: string) { + return new StyleLoadingStrategy(src); + }, + PrependAnonymousScriptToHead(src: string) { + return new ScriptLoadingStrategy(src, DOM_STRATEGY.PrependToHead()); + }, + PrependAnonymousStyleToHead(src: string) { + return new StyleLoadingStrategy(src, DOM_STRATEGY.PrependToHead()); + }, +}; diff --git a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts new file mode 100644 index 0000000000..9260cd8948 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts @@ -0,0 +1,90 @@ +import { + CROSS_ORIGIN_STRATEGY, + DOM_STRATEGY, + LOADING_STRATEGY, + ScriptLoadingStrategy, + StyleLoadingStrategy, +} from '../strategies'; + +const path = 'http://example.com/'; + +describe('ScriptLoadingStrategy', () => { + describe('#createElement', () => { + it('should return a script element with src attribute', () => { + const strategy = new ScriptLoadingStrategy(path); + const element = strategy.createElement(); + + expect(element.tagName).toBe('SCRIPT'); + expect(element.src).toBe(path); + }); + }); + + describe('#createStream', () => { + it('should use given dom and cross-origin strategies', done => { + const domStrategy = DOM_STRATEGY.PrependToHead(); + const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); + + domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => { + setTimeout(() => { + el.onload(new CustomEvent('success', { detail: el.crossOrigin })); + }, 0); + }) as any; + + const strategy = new ScriptLoadingStrategy(path, domStrategy, crossOriginStrategy); + + strategy.createStream().subscribe(event => { + expect(event.detail).toBe('use-credentials'); + done(); + }); + }); + }); +}); + +describe('StyleLoadingStrategy', () => { + describe('#createElement', () => { + it('should return a style element with href and rel attributes', () => { + const strategy = new StyleLoadingStrategy(path); + const element = strategy.createElement(); + + expect(element.tagName).toBe('LINK'); + expect(element.href).toBe(path); + expect(element.rel).toBe('stylesheet'); + }); + }); + + describe('#createStream', () => { + it('should use given dom and cross-origin strategies', done => { + const domStrategy = DOM_STRATEGY.PrependToHead(); + const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); + + domStrategy.insertElement = jest.fn((el: HTMLLinkElement) => { + setTimeout(() => { + el.onload(new CustomEvent('success', { detail: el.crossOrigin })); + }, 0); + }) as any; + + const strategy = new StyleLoadingStrategy(path, domStrategy, crossOriginStrategy); + + strategy.createStream().subscribe(event => { + expect(event.detail).toBe('use-credentials'); + done(); + }); + }); + }); +}); + +describe('LOADING_STRATEGY', () => { + test.each` + name | Strategy | domStrategy + ${'AppendAnonymousScriptToBody'} | ${ScriptLoadingStrategy} | ${'AppendToBody'} + ${'AppendAnonymousScriptToHead'} | ${ScriptLoadingStrategy} | ${'AppendToHead'} + ${'AppendAnonymousStyleToHead'} | ${StyleLoadingStrategy} | ${'AppendToHead'} + ${'PrependAnonymousScriptToHead'} | ${ScriptLoadingStrategy} | ${'PrependToHead'} + ${'PrependAnonymousStyleToHead'} | ${StyleLoadingStrategy} | ${'PrependToHead'} + `( + 'should successfully map $name to $Strategy.name with $domStrategy dom strategy', + ({ name, Strategy, domStrategy }) => { + expect(LOADING_STRATEGY[name](path)).toEqual(new Strategy(path, DOM_STRATEGY[domStrategy]())); + }, + ); +}); From 4cc3a272ebe8a207d2cb4896195970c241feed9c Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 13:20:08 +0300 Subject: [PATCH 30/68] feat(core): add content security strategy --- .../strategies/content-security.strategy.ts | 32 +++++++++++++ .../packages/core/src/lib/strategies/index.ts | 1 + .../src/lib/strategies/loading.strategy.ts | 27 ++++++++--- .../tests/content-security.strategy.spec.ts | 41 +++++++++++++++++ .../src/lib/tests/lazy-load-utils.spec.ts | 45 +++++++++++++++++-- .../src/lib/tests/loading.strategy.spec.ts | 43 +++++++++++++++--- .../core/src/lib/utils/lazy-load-utils.ts | 4 ++ 7 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/content-security.strategy.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts new file mode 100644 index 0000000000..54016b5836 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts @@ -0,0 +1,32 @@ +export abstract class ContentSecurityStrategy { + constructor(public nonce?: string) {} + + abstract applyCSP(element: HTMLScriptElement | HTMLStyleElement): void; +} + +export class StrictContentSecurityStrategy extends ContentSecurityStrategy { + constructor(nonce: string) { + super(nonce); + } + + applyCSP(element: HTMLScriptElement | HTMLStyleElement) { + element.setAttribute('nonce', this.nonce); + } +} + +export class LooseContentSecurityStrategy extends ContentSecurityStrategy { + constructor() { + super(); + } + + applyCSP(_: HTMLScriptElement | HTMLStyleElement) {} +} + +export const CONTENT_SECURITY_STRATEGY = { + Loose() { + return new LooseContentSecurityStrategy(); + }, + Strict(nonce: string) { + return new StrictContentSecurityStrategy(nonce); + }, +}; diff --git a/npm/ng-packs/packages/core/src/lib/strategies/index.ts b/npm/ng-packs/packages/core/src/lib/strategies/index.ts index 915887f92a..93904af549 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/index.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/index.ts @@ -1,3 +1,4 @@ +export * from './content-security.strategy'; export * from './cross-origin.strategy'; export * from './dom.strategy'; export * from './loading.strategy'; diff --git a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts index eae23a0c7f..5c8bd485ca 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts @@ -1,6 +1,7 @@ import { Observable, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { fromLazyLoad } from '../utils'; +import { ContentSecurityStrategy, CONTENT_SECURITY_STRATEGY } from './content-security.strategy'; import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from './cross-origin.strategy'; import { DomStrategy, DOM_STRATEGY } from './dom.strategy'; @@ -9,6 +10,7 @@ export abstract class LoadingStrategy(): Observable { return of(null).pipe( switchMap(() => - fromLazyLoad(this.createElement(), this.domStrategy, this.crossOriginStrategy), + fromLazyLoad( + this.createElement(), + this.domStrategy, + this.crossOriginStrategy, + this.contentSecurityStrategy, + ), ), ); } } export class ScriptLoadingStrategy extends LoadingStrategy { - constructor(src: string, domStrategy?: DomStrategy, crossOriginStrategy?: CrossOriginStrategy) { - super(src, domStrategy, crossOriginStrategy); + constructor( + src: string, + domStrategy?: DomStrategy, + crossOriginStrategy?: CrossOriginStrategy, + contentSecurityStrategy?: ContentSecurityStrategy, + ) { + super(src, domStrategy, crossOriginStrategy, contentSecurityStrategy); } createElement(): HTMLScriptElement { @@ -36,8 +48,13 @@ export class ScriptLoadingStrategy extends LoadingStrategy { } export class StyleLoadingStrategy extends LoadingStrategy { - constructor(href: string, domStrategy?: DomStrategy, crossOriginStrategy?: CrossOriginStrategy) { - super(href, domStrategy, crossOriginStrategy); + constructor( + href: string, + domStrategy?: DomStrategy, + crossOriginStrategy?: CrossOriginStrategy, + contentSecurityStrategy?: ContentSecurityStrategy, + ) { + super(href, domStrategy, crossOriginStrategy, contentSecurityStrategy); } createElement(): HTMLLinkElement { diff --git a/npm/ng-packs/packages/core/src/lib/tests/content-security.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content-security.strategy.spec.ts new file mode 100644 index 0000000000..06db799ca0 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/content-security.strategy.spec.ts @@ -0,0 +1,41 @@ +import { + CONTENT_SECURITY_STRATEGY, + LooseContentSecurityStrategy, + StrictContentSecurityStrategy, +} from '../strategies'; +import { uuid } from '../utils'; + +describe('LooseContentSecurityStrategy', () => { + describe('#applyCSP', () => { + it('should not set nonce attribute', () => { + const strategy = new LooseContentSecurityStrategy(); + const element = document.createElement('link'); + strategy.applyCSP(element); + + expect(element.getAttribute('nonce')).toBeNull(); + }); + }); +}); + +describe('StrictContentSecurityStrategy', () => { + describe('#applyCSP', () => { + it('should set nonce attribute', () => { + const nonce = uuid(); + const strategy = new StrictContentSecurityStrategy(nonce); + const element = document.createElement('link'); + strategy.applyCSP(element); + + expect(element.getAttribute('nonce')).toBe(nonce); + }); + }); +}); + +describe('CONTENT_SECURITY_STRATEGY', () => { + test.each` + name | Strategy | nonce + ${'Loose'} | ${LooseContentSecurityStrategy} | ${undefined} + ${'Strict'} | ${StrictContentSecurityStrategy} | ${uuid()} + `('should successfully map $name to $Strategy.name', ({ name, Strategy, nonce }) => { + expect(CONTENT_SECURITY_STRATEGY[name](nonce)).toEqual(new Strategy(nonce)); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts index 657afeead1..9f1212b78c 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts @@ -1,4 +1,9 @@ -import { DomStrategy, DOM_STRATEGY } from '../strategies'; +import { + ContentSecurityStrategy, + CONTENT_SECURITY_STRATEGY, + DomStrategy, + DOM_STRATEGY, +} from '../strategies'; import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy'; import { uuid } from '../utils'; import { fromLazyLoad } from '../utils/lazy-load-utils'; @@ -33,7 +38,15 @@ describe('Lazy Load Utils', () => { expect(element.crossOrigin).toBe('anonymous'); }); - it('should allow setting a crossorigin strategy', () => { + it('should not set integrity by default', () => { + const element = document.createElement('link'); + + fromLazyLoad(element); + + expect(element.getAttribute('integrity')).toBeNull(); + }); + + it('should allow setting a cross-origin strategy', () => { const element = document.createElement('link'); const integrity = uuid(); @@ -44,6 +57,24 @@ describe('Lazy Load Utils', () => { expect(element.getAttribute('integrity')).toBe(integrity); }); + it('should not set nonce by default', () => { + const element = document.createElement('link'); + + fromLazyLoad(element); + + expect(element.getAttribute('nonce')).toBeNull(); + }); + + it('should allow setting a content security strategy', () => { + const element = document.createElement('link'); + + const nonce = uuid(); + + fromLazyLoad(element, undefined, undefined, CONTENT_SECURITY_STRATEGY.Strict(nonce)); + + expect(element.getAttribute('nonce')).toBe(nonce); + }); + it('should emit error event on fail and clear callbacks', done => { const error = new CustomEvent('error'); const parentNode = { removeChild: jest.fn() }; @@ -61,8 +92,11 @@ describe('Lazy Load Utils', () => { }, } as DomStrategy, { - setCrossOrigin(el: HTMLLinkElement) {}, + setCrossOrigin(_: HTMLLinkElement) {}, } as CrossOriginStrategy, + { + applyCSP(_: HTMLLinkElement) {}, + } as ContentSecurityStrategy, ).subscribe({ error: value => { expect(value).toBe(error); @@ -90,8 +124,11 @@ describe('Lazy Load Utils', () => { }, } as DomStrategy, { - setCrossOrigin(el: HTMLLinkElement) {}, + setCrossOrigin(_: HTMLLinkElement) {}, } as CrossOriginStrategy, + { + applyCSP(_: HTMLLinkElement) {}, + } as ContentSecurityStrategy, ).subscribe({ next: value => { expect(value).toBe(success); diff --git a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts index 9260cd8948..745b858ff3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts @@ -1,12 +1,15 @@ import { + CONTENT_SECURITY_STRATEGY, CROSS_ORIGIN_STRATEGY, DOM_STRATEGY, LOADING_STRATEGY, ScriptLoadingStrategy, StyleLoadingStrategy, } from '../strategies'; +import { uuid } from '../utils'; const path = 'http://example.com/'; +const nonce = uuid(); describe('ScriptLoadingStrategy', () => { describe('#createElement', () => { @@ -23,17 +26,31 @@ describe('ScriptLoadingStrategy', () => { it('should use given dom and cross-origin strategies', done => { const domStrategy = DOM_STRATEGY.PrependToHead(); const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); + const contentSecurityStrategy = CONTENT_SECURITY_STRATEGY.Strict(nonce); domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => { setTimeout(() => { - el.onload(new CustomEvent('success', { detail: el.crossOrigin })); + el.onload( + new CustomEvent('success', { + detail: { + crossOrigin: el.crossOrigin, + nonce: el.getAttribute('nonce'), + }, + }), + ); }, 0); }) as any; - const strategy = new ScriptLoadingStrategy(path, domStrategy, crossOriginStrategy); + const strategy = new ScriptLoadingStrategy( + path, + domStrategy, + crossOriginStrategy, + contentSecurityStrategy, + ); strategy.createStream().subscribe(event => { - expect(event.detail).toBe('use-credentials'); + expect(event.detail.crossOrigin).toBe('use-credentials'); + expect(event.detail.nonce).toBe(nonce); done(); }); }); @@ -56,17 +73,31 @@ describe('StyleLoadingStrategy', () => { it('should use given dom and cross-origin strategies', done => { const domStrategy = DOM_STRATEGY.PrependToHead(); const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); + const contentSecurityStrategy = CONTENT_SECURITY_STRATEGY.Strict(nonce); domStrategy.insertElement = jest.fn((el: HTMLLinkElement) => { setTimeout(() => { - el.onload(new CustomEvent('success', { detail: el.crossOrigin })); + el.onload( + new CustomEvent('success', { + detail: { + crossOrigin: el.crossOrigin, + nonce: el.getAttribute('nonce'), + }, + }), + ); }, 0); }) as any; - const strategy = new StyleLoadingStrategy(path, domStrategy, crossOriginStrategy); + const strategy = new StyleLoadingStrategy( + path, + domStrategy, + crossOriginStrategy, + contentSecurityStrategy, + ); strategy.createStream().subscribe(event => { - expect(event.detail).toBe('use-credentials'); + expect(event.detail.crossOrigin).toBe('use-credentials'); + expect(event.detail.nonce).toBe(nonce); done(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts index 5aee3bc715..598db332f8 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts @@ -1,5 +1,7 @@ import { Observable, Observer } from 'rxjs'; import { + ContentSecurityStrategy, + CONTENT_SECURITY_STRATEGY, CrossOriginStrategy, CROSS_ORIGIN_STRATEGY, DomStrategy, @@ -10,8 +12,10 @@ export function fromLazyLoad( element: HTMLScriptElement | HTMLLinkElement, domStrategy: DomStrategy = DOM_STRATEGY.AppendToHead(), crossOriginStrategy: CrossOriginStrategy = CROSS_ORIGIN_STRATEGY.Anonymous(), + contentSecurityStrategy: ContentSecurityStrategy = CONTENT_SECURITY_STRATEGY.Loose(), ): Observable { crossOriginStrategy.setCrossOrigin(element); + contentSecurityStrategy.applyCSP(element); domStrategy.insertElement(element); return Observable.create((observer: Observer) => { From 221c78fa0df2d14a8b7714fcdaa6e3d4018c3913 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 14:13:00 +0300 Subject: [PATCH 31/68] feat(core): add new lazy load service --- .../src/lib/services/lazy-load.service.ts | 42 ++++++++++- .../src/lib/tests/lazy-load.service.spec.ts | 74 +++++++++++++++++-- 2 files changed, 106 insertions(+), 10 deletions(-) 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 f047db97be..f9c3cbeb55 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 @@ -1,20 +1,56 @@ import { Injectable } from '@angular/core'; -import { Observable, ReplaySubject, throwError } from 'rxjs'; +import { concat, Observable, of, ReplaySubject, throwError } from 'rxjs'; +import { delay, retryWhen, shareReplay, take, tap } from 'rxjs/operators'; +import { LoadingStrategy } from '../strategies'; import { uuid } from '../utils'; @Injectable({ providedIn: 'root', }) export class LazyLoadService { + readonly loaded = new Set(); + loadedLibraries: { [url: string]: ReplaySubject } = {}; + load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable; load( urlOrUrls: string | string[], type: 'script' | 'style', - content: string = '', + content?: string, + targetQuery?: string, + position?: InsertPosition, + ): Observable; + load( + strategyOrUrl: LoadingStrategy | string | string[], + retryTimesOrType?: number | 'script' | 'style', + retryDelayOrContent?: number | string, targetQuery: string = 'body', position: InsertPosition = 'beforeend', - ): Observable { + ): Observable { + if (strategyOrUrl instanceof LoadingStrategy) { + const strategy = strategyOrUrl; + const retryTimes = retryTimesOrType as number; + const retryDelay = retryDelayOrContent as number; + + if (this.loaded.has(strategy.path)) return of(new CustomEvent('load')); + + return strategy.createStream().pipe( + retryWhen(error$ => + concat( + error$.pipe(delay(retryDelay), take(retryTimes)), + throwError(new CustomEvent('error')), + ), + ), + tap(() => this.loaded.add(strategy.path)), + delay(100), + shareReplay({ bufferSize: 1, refCount: true }), + ); + } + + let urlOrUrls = strategyOrUrl; + const content = retryDelayOrContent as string; + const type = retryTimesOrType as 'script' | 'style'; + if (!urlOrUrls && !content) { return throwError('Should pass url or content'); } else if (!urlOrUrls && content) { diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts index a29b60974e..3596da2591 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts @@ -1,9 +1,65 @@ import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { of, throwError } from 'rxjs'; +import { catchError, switchMap } from 'rxjs/operators'; import { LazyLoadService } from '../services/lazy-load.service'; -import { catchError } from 'rxjs/operators'; -import { of } from 'rxjs'; +import { ScriptLoadingStrategy } from '../strategies'; describe('LazyLoadService', () => { + describe('#load', () => { + const service = new LazyLoadService(); + const strategy = new ScriptLoadingStrategy('http://example.com/'); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should emit an error event if not loaded', done => { + const counter = jest.fn(); + jest.spyOn(strategy, 'createStream').mockReturnValueOnce( + of(null).pipe( + switchMap(() => { + counter(); + return throwError('THIS WILL NOT BE THE FINAL ERROR'); + }), + ), + ); + + service.load(strategy, 5, 0).subscribe({ + error: errorEvent => { + expect(errorEvent).toEqual(new CustomEvent('error')); + expect(counter).toHaveBeenCalledTimes(6); + expect(service.loaded.has(strategy.path)).toBe(false); + done(); + }, + }); + }); + + it('should emit a load event if loaded', done => { + const loadEvent = new CustomEvent('load'); + jest.spyOn(strategy, 'createStream').mockReturnValue(of(loadEvent)); + + service.load(strategy).subscribe({ + next: event => { + expect(event).toBe(loadEvent); + expect(service.loaded.has(strategy.path)).toBe(true); + done(); + }, + }); + }); + + it('should emit a custom load event if loaded if resource is loaded before', done => { + const loadEvent = new CustomEvent('load'); + service.loaded.add(strategy.path); + + service.load(strategy).subscribe(event => { + expect(event).toEqual(loadEvent); + done(); + }); + }); + }); +}); + +describe('LazyLoadService (Deprecated)', () => { let spectator: SpectatorService; let service: LazyLoadService; const scriptElement = document.createElement('script'); @@ -25,15 +81,17 @@ describe('LazyLoadService', () => { spy.mockReturnValue(scriptElement); service.load('https://abp.io', 'script', 'test').subscribe(res => { - expect(document.querySelector('script[src="https://abp.io"][type="text/javascript"]').textContent).toMatch( - 'test', - ); + expect( + document.querySelector('script[src="https://abp.io"][type="text/javascript"]').textContent, + ).toMatch('test'); }); scriptElement.onload(null); service.load('https://abp.io', 'script', 'test').subscribe(res => { - expect(document.querySelectorAll('script[src="https://abp.io"][type="text/javascript"]')).toHaveLength(1); + expect( + document.querySelectorAll('script[src="https://abp.io"][type="text/javascript"]'), + ).toHaveLength(1); done(); }); }); @@ -59,7 +117,9 @@ describe('LazyLoadService', () => { test('should load an link element', done => { service.load('https://abp.io', 'style').subscribe(res => { - expect(document.querySelector('link[type="text/css"][rel="stylesheet"][href="https://abp.io"]')).toBeTruthy(); + expect( + document.querySelector('link[type="text/css"][rel="stylesheet"][href="https://abp.io"]'), + ).toBeTruthy(); done(); }); From 4acc763fdcc50d276b39bfe1e53d0591ac177255 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 14:18:58 +0300 Subject: [PATCH 32/68] refactor(core): import directly from strategy files --- .../packages/core/src/lib/utils/lazy-load-utils.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts index 598db332f8..b4c4c3f1ce 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts @@ -2,11 +2,9 @@ import { Observable, Observer } from 'rxjs'; import { ContentSecurityStrategy, CONTENT_SECURITY_STRATEGY, - CrossOriginStrategy, - CROSS_ORIGIN_STRATEGY, - DomStrategy, - DOM_STRATEGY, -} from '../strategies'; +} from '../strategies/content-security.strategy'; +import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy'; +import { DomStrategy, DOM_STRATEGY } from '../strategies/dom.strategy'; export function fromLazyLoad( element: HTMLScriptElement | HTMLLinkElement, From 6ddca15a5f3d1474d2802231b547997fba34c6eb Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 14:28:14 +0300 Subject: [PATCH 33/68] feat(core): make strategies publicly available --- npm/ng-packs/packages/core/src/public-api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/public-api.ts b/npm/ng-packs/packages/core/src/public-api.ts index 3513427c2d..901536c85d 100644 --- a/npm/ng-packs/packages/core/src/public-api.ts +++ b/npm/ng-packs/packages/core/src/public-api.ts @@ -7,6 +7,7 @@ export * from './lib/abstracts'; export * from './lib/actions'; export * from './lib/components'; export * from './lib/constants'; +export * from './lib/core.module'; export * from './lib/directives'; export * from './lib/enums'; export * from './lib/guards'; @@ -16,7 +17,6 @@ export * from './lib/pipes'; export * from './lib/plugins'; export * from './lib/services'; export * from './lib/states'; +export * from './lib/strategies'; export * from './lib/tokens'; export * from './lib/utils'; - -export * from './lib/core.module'; From 181b732b24ada30fe21bd03db26e68a7a54ec597 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 14:45:44 +0300 Subject: [PATCH 34/68] fix(core): resolve lint issues --- .../packages/core/src/lib/strategies/loading.strategy.ts | 4 ++-- .../packages/core/src/lib/tests/dom.strategy.spec.ts | 2 +- npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts index 5c8bd485ca..113ab9a853 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts @@ -15,10 +15,10 @@ export abstract class LoadingStrategy(): Observable { + createStream(): Observable { return of(null).pipe( switchMap(() => - fromLazyLoad( + fromLazyLoad( this.createElement(), this.domStrategy, this.crossOriginStrategy, diff --git a/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts index 7e5f264f2b..e82eac52ad 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts @@ -29,7 +29,7 @@ describe('DomStrategy', () => { }); describe('DOM_STRATEGY', () => { - let div = document.createElement('DIV'); + const div = document.createElement('DIV'); beforeEach(() => { document.body.innerHTML = ''; diff --git a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts index b4c4c3f1ce..09b4ec4e4f 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts @@ -16,8 +16,8 @@ export function fromLazyLoad( contentSecurityStrategy.applyCSP(element); domStrategy.insertElement(element); - return Observable.create((observer: Observer) => { - element.onload = event => { + return new Observable((observer: Observer) => { + element.onload = (event: T) => { clearCallbacks(element); observer.next(event); observer.complete(); @@ -39,6 +39,7 @@ export function fromLazyLoad( } function createErrorHandler(observer: Observer, element: HTMLElement) { + /* tslint:disable-next-line:only-arrow-functions */ return function(event: Event | string) { clearCallbacks(element); element.parentNode.removeChild(element); From 54428be03f9a7958b6c77ec991c7f2c0922eacfe Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 15:33:55 +0300 Subject: [PATCH 35/68] fix(core): add default values to load method --- .../packages/core/src/lib/services/lazy-load.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 f9c3cbeb55..b7400c3193 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 @@ -29,8 +29,8 @@ export class LazyLoadService { ): Observable { if (strategyOrUrl instanceof LoadingStrategy) { const strategy = strategyOrUrl; - const retryTimes = retryTimesOrType as number; - const retryDelay = retryDelayOrContent as number; + const retryTimes = (retryTimesOrType as number) || 2; + const retryDelay = (retryDelayOrContent as number) || 1000; if (this.loaded.has(strategy.path)) return of(new CustomEvent('load')); @@ -48,7 +48,7 @@ export class LazyLoadService { } let urlOrUrls = strategyOrUrl; - const content = retryDelayOrContent as string; + const content = (retryDelayOrContent as string) || ''; const type = retryTimesOrType as 'script' | 'style'; if (!urlOrUrls && !content) { From ae6475eb4b819c4955424ada3edae1aaf84abdaa Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 15:43:54 +0300 Subject: [PATCH 36/68] feat(core): enable 0 retries and 0 delay retries --- .../packages/core/src/lib/services/lazy-load.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b7400c3193..5e605d8c5d 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 @@ -29,8 +29,8 @@ export class LazyLoadService { ): Observable { if (strategyOrUrl instanceof LoadingStrategy) { const strategy = strategyOrUrl; - const retryTimes = (retryTimesOrType as number) || 2; - const retryDelay = (retryDelayOrContent as number) || 1000; + const retryTimes = typeof retryTimesOrType === 'number' ? retryTimesOrType : 2; + const retryDelay = typeof retryDelayOrContent === 'number' ? retryDelayOrContent : 1000; if (this.loaded.has(strategy.path)) return of(new CustomEvent('load')); From 21095025a97f6db9d8344a774d322ab600c3c1c7 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 16:11:57 +0300 Subject: [PATCH 37/68] feat(core): add optional integrity in loading strategies --- .../src/lib/strategies/loading.strategy.ts | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts index 113ab9a853..2882c3f0a6 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts @@ -67,19 +67,39 @@ export class StyleLoadingStrategy extends LoadingStrategy { } export const LOADING_STRATEGY = { - AppendAnonymousScriptToBody(src: string) { - return new ScriptLoadingStrategy(src, DOM_STRATEGY.AppendToBody()); + AppendAnonymousScriptToBody(src: string, integrity?: string) { + return new ScriptLoadingStrategy( + src, + DOM_STRATEGY.AppendToBody(), + CROSS_ORIGIN_STRATEGY.Anonymous(integrity), + ); }, - AppendAnonymousScriptToHead(src: string) { - return new ScriptLoadingStrategy(src); + AppendAnonymousScriptToHead(src: string, integrity?: string) { + return new ScriptLoadingStrategy( + src, + DOM_STRATEGY.AppendToHead(), + CROSS_ORIGIN_STRATEGY.Anonymous(integrity), + ); }, - AppendAnonymousStyleToHead(src: string) { - return new StyleLoadingStrategy(src); + AppendAnonymousStyleToHead(src: string, integrity?: string) { + return new StyleLoadingStrategy( + src, + DOM_STRATEGY.AppendToHead(), + CROSS_ORIGIN_STRATEGY.Anonymous(integrity), + ); }, - PrependAnonymousScriptToHead(src: string) { - return new ScriptLoadingStrategy(src, DOM_STRATEGY.PrependToHead()); + PrependAnonymousScriptToHead(src: string, integrity?: string) { + return new ScriptLoadingStrategy( + src, + DOM_STRATEGY.PrependToHead(), + CROSS_ORIGIN_STRATEGY.Anonymous(integrity), + ); }, - PrependAnonymousStyleToHead(src: string) { - return new StyleLoadingStrategy(src, DOM_STRATEGY.PrependToHead()); + PrependAnonymousStyleToHead(src: string, integrity?: string) { + return new StyleLoadingStrategy( + src, + DOM_STRATEGY.PrependToHead(), + CROSS_ORIGIN_STRATEGY.Anonymous(integrity), + ); }, }; From 7da8011eeeeed041bdef9c25e591fefe3ff759cd Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 19:59:20 +0300 Subject: [PATCH 38/68] refactor(core): separate lazy load & content insertion --- .../src/lib/strategies/loading.strategy.ts | 27 +++------------- .../src/lib/tests/lazy-load-utils.spec.ts | 31 +------------------ .../src/lib/tests/loading.strategy.spec.ts | 23 ++------------ .../core/src/lib/utils/lazy-load-utils.ts | 6 ---- 4 files changed, 8 insertions(+), 79 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts index 2882c3f0a6..be89a751ed 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/loading.strategy.ts @@ -1,7 +1,6 @@ import { Observable, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { fromLazyLoad } from '../utils'; -import { ContentSecurityStrategy, CONTENT_SECURITY_STRATEGY } from './content-security.strategy'; import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from './cross-origin.strategy'; import { DomStrategy, DOM_STRATEGY } from './dom.strategy'; @@ -10,7 +9,6 @@ export abstract class LoadingStrategy(): Observable { return of(null).pipe( switchMap(() => - fromLazyLoad( - this.createElement(), - this.domStrategy, - this.crossOriginStrategy, - this.contentSecurityStrategy, - ), + fromLazyLoad(this.createElement(), this.domStrategy, this.crossOriginStrategy), ), ); } } export class ScriptLoadingStrategy extends LoadingStrategy { - constructor( - src: string, - domStrategy?: DomStrategy, - crossOriginStrategy?: CrossOriginStrategy, - contentSecurityStrategy?: ContentSecurityStrategy, - ) { - super(src, domStrategy, crossOriginStrategy, contentSecurityStrategy); + constructor(src: string, domStrategy?: DomStrategy, crossOriginStrategy?: CrossOriginStrategy) { + super(src, domStrategy, crossOriginStrategy); } createElement(): HTMLScriptElement { @@ -48,13 +36,8 @@ export class ScriptLoadingStrategy extends LoadingStrategy { } export class StyleLoadingStrategy extends LoadingStrategy { - constructor( - href: string, - domStrategy?: DomStrategy, - crossOriginStrategy?: CrossOriginStrategy, - contentSecurityStrategy?: ContentSecurityStrategy, - ) { - super(href, domStrategy, crossOriginStrategy, contentSecurityStrategy); + constructor(href: string, domStrategy?: DomStrategy, crossOriginStrategy?: CrossOriginStrategy) { + super(href, domStrategy, crossOriginStrategy); } createElement(): HTMLLinkElement { diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts index 9f1212b78c..7afb343e80 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts @@ -1,9 +1,4 @@ -import { - ContentSecurityStrategy, - CONTENT_SECURITY_STRATEGY, - DomStrategy, - DOM_STRATEGY, -} from '../strategies'; +import { DomStrategy, DOM_STRATEGY } from '../strategies'; import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy'; import { uuid } from '../utils'; import { fromLazyLoad } from '../utils/lazy-load-utils'; @@ -57,24 +52,6 @@ describe('Lazy Load Utils', () => { expect(element.getAttribute('integrity')).toBe(integrity); }); - it('should not set nonce by default', () => { - const element = document.createElement('link'); - - fromLazyLoad(element); - - expect(element.getAttribute('nonce')).toBeNull(); - }); - - it('should allow setting a content security strategy', () => { - const element = document.createElement('link'); - - const nonce = uuid(); - - fromLazyLoad(element, undefined, undefined, CONTENT_SECURITY_STRATEGY.Strict(nonce)); - - expect(element.getAttribute('nonce')).toBe(nonce); - }); - it('should emit error event on fail and clear callbacks', done => { const error = new CustomEvent('error'); const parentNode = { removeChild: jest.fn() }; @@ -94,9 +71,6 @@ describe('Lazy Load Utils', () => { { setCrossOrigin(_: HTMLLinkElement) {}, } as CrossOriginStrategy, - { - applyCSP(_: HTMLLinkElement) {}, - } as ContentSecurityStrategy, ).subscribe({ error: value => { expect(value).toBe(error); @@ -126,9 +100,6 @@ describe('Lazy Load Utils', () => { { setCrossOrigin(_: HTMLLinkElement) {}, } as CrossOriginStrategy, - { - applyCSP(_: HTMLLinkElement) {}, - } as ContentSecurityStrategy, ).subscribe({ next: value => { expect(value).toBe(success); diff --git a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts index 745b858ff3..4b950cf533 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts @@ -1,15 +1,12 @@ import { - CONTENT_SECURITY_STRATEGY, CROSS_ORIGIN_STRATEGY, DOM_STRATEGY, LOADING_STRATEGY, ScriptLoadingStrategy, StyleLoadingStrategy, } from '../strategies'; -import { uuid } from '../utils'; const path = 'http://example.com/'; -const nonce = uuid(); describe('ScriptLoadingStrategy', () => { describe('#createElement', () => { @@ -26,7 +23,6 @@ describe('ScriptLoadingStrategy', () => { it('should use given dom and cross-origin strategies', done => { const domStrategy = DOM_STRATEGY.PrependToHead(); const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); - const contentSecurityStrategy = CONTENT_SECURITY_STRATEGY.Strict(nonce); domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => { setTimeout(() => { @@ -34,23 +30,16 @@ describe('ScriptLoadingStrategy', () => { new CustomEvent('success', { detail: { crossOrigin: el.crossOrigin, - nonce: el.getAttribute('nonce'), }, }), ); }, 0); }) as any; - const strategy = new ScriptLoadingStrategy( - path, - domStrategy, - crossOriginStrategy, - contentSecurityStrategy, - ); + const strategy = new ScriptLoadingStrategy(path, domStrategy, crossOriginStrategy); strategy.createStream().subscribe(event => { expect(event.detail.crossOrigin).toBe('use-credentials'); - expect(event.detail.nonce).toBe(nonce); done(); }); }); @@ -73,7 +62,6 @@ describe('StyleLoadingStrategy', () => { it('should use given dom and cross-origin strategies', done => { const domStrategy = DOM_STRATEGY.PrependToHead(); const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); - const contentSecurityStrategy = CONTENT_SECURITY_STRATEGY.Strict(nonce); domStrategy.insertElement = jest.fn((el: HTMLLinkElement) => { setTimeout(() => { @@ -81,23 +69,16 @@ describe('StyleLoadingStrategy', () => { new CustomEvent('success', { detail: { crossOrigin: el.crossOrigin, - nonce: el.getAttribute('nonce'), }, }), ); }, 0); }) as any; - const strategy = new StyleLoadingStrategy( - path, - domStrategy, - crossOriginStrategy, - contentSecurityStrategy, - ); + const strategy = new StyleLoadingStrategy(path, domStrategy, crossOriginStrategy); strategy.createStream().subscribe(event => { expect(event.detail.crossOrigin).toBe('use-credentials'); - expect(event.detail.nonce).toBe(nonce); done(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts index 09b4ec4e4f..f602bf06ef 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/lazy-load-utils.ts @@ -1,8 +1,4 @@ import { Observable, Observer } from 'rxjs'; -import { - ContentSecurityStrategy, - CONTENT_SECURITY_STRATEGY, -} from '../strategies/content-security.strategy'; import { CrossOriginStrategy, CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy'; import { DomStrategy, DOM_STRATEGY } from '../strategies/dom.strategy'; @@ -10,10 +6,8 @@ export function fromLazyLoad( element: HTMLScriptElement | HTMLLinkElement, domStrategy: DomStrategy = DOM_STRATEGY.AppendToHead(), crossOriginStrategy: CrossOriginStrategy = CROSS_ORIGIN_STRATEGY.Anonymous(), - contentSecurityStrategy: ContentSecurityStrategy = CONTENT_SECURITY_STRATEGY.Loose(), ): Observable { crossOriginStrategy.setCrossOrigin(element); - contentSecurityStrategy.applyCSP(element); domStrategy.insertElement(element); return new Observable((observer: Observer) => { From 2b50598a9163bfcc7dac72d464c859f8ec70a9ff Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 20:00:19 +0300 Subject: [PATCH 39/68] feat(core): rename strategies based on security perspective --- .../strategies/content-security.strategy.ts | 12 ++++++------ .../tests/content-security.strategy.spec.ts | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts b/npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts index 54016b5836..2875916ef2 100644 --- a/npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts +++ b/npm/ng-packs/packages/core/src/lib/strategies/content-security.strategy.ts @@ -4,7 +4,7 @@ export abstract class ContentSecurityStrategy { abstract applyCSP(element: HTMLScriptElement | HTMLStyleElement): void; } -export class StrictContentSecurityStrategy extends ContentSecurityStrategy { +export class LooseContentSecurityStrategy extends ContentSecurityStrategy { constructor(nonce: string) { super(nonce); } @@ -14,7 +14,7 @@ export class StrictContentSecurityStrategy extends ContentSecurityStrategy { } } -export class LooseContentSecurityStrategy extends ContentSecurityStrategy { +export class StrictContentSecurityStrategy extends ContentSecurityStrategy { constructor() { super(); } @@ -23,10 +23,10 @@ export class LooseContentSecurityStrategy extends ContentSecurityStrategy { } export const CONTENT_SECURITY_STRATEGY = { - Loose() { - return new LooseContentSecurityStrategy(); + Loose(nonce: string) { + return new LooseContentSecurityStrategy(nonce); }, - Strict(nonce: string) { - return new StrictContentSecurityStrategy(nonce); + Strict() { + return new StrictContentSecurityStrategy(); }, }; diff --git a/npm/ng-packs/packages/core/src/lib/tests/content-security.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content-security.strategy.spec.ts index 06db799ca0..c617e35893 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/content-security.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/content-security.strategy.spec.ts @@ -7,25 +7,25 @@ import { uuid } from '../utils'; describe('LooseContentSecurityStrategy', () => { describe('#applyCSP', () => { - it('should not set nonce attribute', () => { - const strategy = new LooseContentSecurityStrategy(); + it('should set nonce attribute', () => { + const nonce = uuid(); + const strategy = new LooseContentSecurityStrategy(nonce); const element = document.createElement('link'); strategy.applyCSP(element); - expect(element.getAttribute('nonce')).toBeNull(); + expect(element.getAttribute('nonce')).toBe(nonce); }); }); }); describe('StrictContentSecurityStrategy', () => { describe('#applyCSP', () => { - it('should set nonce attribute', () => { - const nonce = uuid(); - const strategy = new StrictContentSecurityStrategy(nonce); + it('should not set nonce attribute', () => { + const strategy = new StrictContentSecurityStrategy(); const element = document.createElement('link'); strategy.applyCSP(element); - expect(element.getAttribute('nonce')).toBe(nonce); + expect(element.getAttribute('nonce')).toBeNull(); }); }); }); @@ -33,8 +33,8 @@ describe('StrictContentSecurityStrategy', () => { describe('CONTENT_SECURITY_STRATEGY', () => { test.each` name | Strategy | nonce - ${'Loose'} | ${LooseContentSecurityStrategy} | ${undefined} - ${'Strict'} | ${StrictContentSecurityStrategy} | ${uuid()} + ${'Loose'} | ${LooseContentSecurityStrategy} | ${uuid()} + ${'Strict'} | ${StrictContentSecurityStrategy} | ${undefined} `('should successfully map $name to $Strategy.name', ({ name, Strategy, nonce }) => { expect(CONTENT_SECURITY_STRATEGY[name](nonce)).toEqual(new Strategy(nonce)); }); From b15ea010a9c183fff29737e77d7ef8480829cea0 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 2 Apr 2020 20:01:15 +0300 Subject: [PATCH 40/68] docs(core): add how content security strategies work --- .../UI/Angular/Content-Security-Strategy.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/en/UI/Angular/Content-Security-Strategy.md 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..96418a04cb --- /dev/null +++ b/docs/en/UI/Angular/Content-Security-Strategy.md @@ -0,0 +1,53 @@ +# ContentSecurityStrategy + +`ContentSecurityStrategy` is an abstract class exposed by @abp/ng.core package. Its instances help you mark inline script 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(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(element: HTMLScriptElement | HTMLStyleElement): void + +This method maps the aforementioned properties to the given `element`. + + + +## LooseContentSecurityPolicy + +`LooseContentSecurityPolicy` is a class that extends `ContentSecurityStrategy`. It required `nonce` and marks given `