From 453771ba31c9e1a40a9730a66f9009cea3862b89 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 1 Sep 2020 23:14:15 +0800 Subject: [PATCH 01/14] Introduce DynamicRangeAttribute --- .../AbpValidationAttributeAdapterProvider.cs | 9 ++- .../DynamicRangeAttributeAdapter.cs | 46 +++++++++++++ .../Abp/Validation/DynamicRangeAttribute.cs | 64 +++++++++++++++++++ .../Validation/ValidationTestController.cs | 36 +++++++++-- .../ValidationTestController_Tests.cs | 19 ++++-- 5 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs index c9b7cb21b3..e5131592d0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs @@ -27,8 +27,13 @@ namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations { return new DynamicMaxLengthAttributeAdapter((DynamicMaxLengthAttribute) attribute, stringLocalizer); } - + + if (type == typeof(DynamicRangeAttribute)) + { + return new DynamicRangeAttributeAdapter((DynamicRangeAttribute) attribute, stringLocalizer); + } + return _defaultAdapter.GetAttributeAdapter(attribute, stringLocalizer); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs new file mode 100644 index 0000000000..f084c559ad --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs @@ -0,0 +1,46 @@ +using System; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using Volo.Abp.Validation; + +namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations +{ + public class DynamicRangeAttributeAdapter : AttributeAdapterBase + { + private readonly string _max; + private readonly string _min; + + public DynamicRangeAttributeAdapter( + DynamicRangeAttribute attribute, + IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + _min = Attribute.Minimum.ToString()?.ToString(CultureInfo.InvariantCulture); + _max = Attribute.Maximum.ToString()?.ToString(CultureInfo.InvariantCulture); + } + + public override void AddValidation(ClientModelValidationContext context) + { + Check.NotNull(context, nameof(context)); + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-length", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-length-min", _min); + MergeAttribute(context.Attributes, "data-val-length-max", _max); + } + + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + Check.NotNull(validationContext, nameof(validationContext)); + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.Minimum, + Attribute.Maximum + ); + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs new file mode 100644 index 0000000000..2836987302 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs @@ -0,0 +1,64 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Reflection; +using JetBrains.Annotations; + +namespace Volo.Abp.Validation +{ + public class DynamicRangeAttribute : RangeAttribute + { + private static readonly FieldInfo MaximumField; + private static readonly FieldInfo MinimumField; + + static DynamicRangeAttribute() + { + MaximumField = typeof(RangeAttribute).GetField( + "k__BackingField", + BindingFlags.Instance | BindingFlags.NonPublic + ); + Debug.Assert(MaximumField != null, nameof(MaximumField) + " != null"); + + MinimumField = typeof(RangeAttribute).GetField( + "k__BackingField", + BindingFlags.Instance | BindingFlags.NonPublic + ); + Debug.Assert(MinimumField != null, nameof(MinimumField) + " != null"); + } + + /// A type to get the values of the properties + /// The name of the public static property for the + /// The name of the public static property for the + /// The type of the range parameters. Must implement IComparable. + public DynamicRangeAttribute( + [NotNull] Type sourceType, + [NotNull] Type operandType, + [CanBeNull] string minimumPropertyName, + [CanBeNull] string maximumPropertyName + ) + : base(operandType, string.Empty, string.Empty) + { + Check.NotNull(sourceType, nameof(sourceType)); + + if (minimumPropertyName != null) + { + var minimumProperty = sourceType.GetProperty( + minimumPropertyName, + BindingFlags.Static | BindingFlags.Public + ); + Debug.Assert(minimumProperty != null, nameof(minimumProperty) + " != null"); + MinimumField.SetValue(this, minimumProperty.GetValue(null)); + } + + if (maximumPropertyName != null) + { + var maximumProperty = sourceType.GetProperty( + maximumPropertyName, + BindingFlags.Static | BindingFlags.Public + ); + Debug.Assert(maximumProperty != null, nameof(maximumProperty) + " != null"); + MaximumField.SetValue(this, maximumProperty.GetValue(null)); + } + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs index 6ce47c4679..de0e24002f 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; @@ -32,7 +33,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation { return Content("ModelState.IsValid: " + ModelState.IsValid.ToString().ToLowerInvariant()); } - + [HttpGet] [Route("object-result-action-dynamic-length")] public Task ObjectResultActionDynamicLength(ValidationDynamicTestModel model) @@ -47,7 +48,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation [StringLength(5, MinimumLength = 2)] public string Value1 { get; set; } } - + public class ValidationDynamicTestModel { [DynamicStringLength(typeof(Consts), nameof(Consts.MaxValue1Length), nameof(Consts.MinValue1Length))] @@ -55,18 +56,41 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation [DynamicMaxLength(typeof(Consts), nameof(Consts.MaxValue2Length))] public string Value2 { get; set; } - + [DynamicMaxLength(typeof(Consts), nameof(Consts.MaxValue3Length))] public int[] Value3 { get; set; } - + + [DynamicRange(typeof(Consts), typeof(int), nameof(Consts.MinValue1), nameof(Consts.MaxValue1))] + public int Value4 { get; set; } + + [DynamicRange(typeof(Consts), typeof(double), nameof(Consts.MinValue2), nameof(Consts.MaxValue2))] + public double Value5 { get; set; } + + [DynamicRange(typeof(Consts), typeof(DateTime), nameof(Consts.MinValue3), nameof(Consts.MaxValue3))] + public DateTime Value6 { get; set; } + public static class Consts { public static int MinValue1Length { get; set; } = 2; public static int MaxValue1Length { get; set; } = 7; public static int MaxValue2Length { get; set; } = 4; - + public static int MaxValue3Length { get; set; } = 2; + + + public static int MinValue1 { get; set; } = 1; + + public static int MaxValue1 { get; set; } = 5; + + + public static double MinValue2 { get; set; } = 1.2; + + public static double MaxValue2 { get; set; } = 7.2; + + public static string MinValue3 { get; set; } = "1/2/2004"; + + public static string MaxValue3 { get; set; } = "3/4/2004"; } } @@ -83,4 +107,4 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation } } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs index 9e22784ba7..715e55799a 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs @@ -61,9 +61,9 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation [Fact] public async Task Should_Validate_Dynamic_Length_Object_Result_Success() { - var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action-dynamic-length?value1=hello&value3[0]=53&value3[1]=54"); + var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action-dynamic-length?value1=hello&value3[0]=53&value3[1]=54&value4=4&value5=1.5&value6=2004-02-04"); result.ShouldBe("hello"); - + } [Fact] @@ -71,15 +71,24 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation { var result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=a", HttpStatusCode.BadRequest); //value1 has min string length of 2 chars. result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); - + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=12345678", HttpStatusCode.BadRequest); //value1 has max string length of 7 chars. result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); - + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=123458&value2=12345", HttpStatusCode.BadRequest); //value2 has max length of 5 chars. result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); - + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value3[2]=55&value3[3]=56", HttpStatusCode.BadRequest); //value3 has max length of 2. result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); + + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value[4]=10", HttpStatusCode.BadRequest); //value4 has max num of 5. + result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); + + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value4=2&value5=1.1", HttpStatusCode.BadRequest); //value4 has min num of 1.2. + result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); + + result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value4=2&value5=1.2&value6=2004-05-04", HttpStatusCode.BadRequest); //value4 has max date of 3/4/2004. + result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); } } } From a2743fd3cd953aee3dd75924d47cb6fed8038de4 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 2 Sep 2020 10:03:54 +0800 Subject: [PATCH 02/14] Improved --- .../DataAnnotations/DynamicRangeAttributeAdapter.cs | 10 +++++----- .../Volo/Abp/Validation/DynamicRangeAttribute.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs index f084c559ad..d3344c20f2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicRangeAttributeAdapter.cs @@ -17,8 +17,8 @@ namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer) { - _min = Attribute.Minimum.ToString()?.ToString(CultureInfo.InvariantCulture); - _max = Attribute.Maximum.ToString()?.ToString(CultureInfo.InvariantCulture); + _min = Convert.ToString(Attribute.Minimum,CultureInfo.InvariantCulture); + _max = Convert.ToString(Attribute.Maximum,CultureInfo.InvariantCulture); } public override void AddValidation(ClientModelValidationContext context) @@ -26,9 +26,9 @@ namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations Check.NotNull(context, nameof(context)); MergeAttribute(context.Attributes, "data-val", "true"); - MergeAttribute(context.Attributes, "data-val-length", GetErrorMessage(context)); - MergeAttribute(context.Attributes, "data-val-length-min", _min); - MergeAttribute(context.Attributes, "data-val-length-max", _max); + MergeAttribute(context.Attributes, "data-val-range", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-range-min", _min); + MergeAttribute(context.Attributes, "data-val-range-max", _max); } public override string GetErrorMessage(ModelValidationContextBase validationContext) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs index 2836987302..49c91f593e 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicRangeAttribute.cs @@ -27,9 +27,9 @@ namespace Volo.Abp.Validation } /// A type to get the values of the properties + /// The type of the range parameters. Must implement IComparable. /// The name of the public static property for the /// The name of the public static property for the - /// The type of the range parameters. Must implement IComparable. public DynamicRangeAttribute( [NotNull] Type sourceType, [NotNull] Type operandType, From b44247615d252913aeb1d28ab4934cf5cd66c521 Mon Sep 17 00:00:00 2001 From: wakuflair Date: Wed, 2 Sep 2020 11:28:11 +0800 Subject: [PATCH 03/14] pass requestData parameter to inputAction function --- .../datatables/datatables-extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index 9a2052b267..abb10d48a4 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -231,7 +231,7 @@ (function () { datatables.createAjax = function (serverMethod, inputAction) { return function (requestData, callback, settings) { - var input = inputAction ? inputAction() : {}; + var input = inputAction ? inputAction(requestData) : {}; //Paging if (settings.oInit.paging) { From 38827fb699e2537babca607dbaa64bedd4cee6d3 Mon Sep 17 00:00:00 2001 From: Yuhang Ji Date: Wed, 2 Sep 2020 13:06:03 +0800 Subject: [PATCH 04/14] Update framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js Co-authored-by: maliming <6908465+maliming@users.noreply.github.com> --- .../datatables/datatables-extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index abb10d48a4..8d85f6f6d8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -231,7 +231,7 @@ (function () { datatables.createAjax = function (serverMethod, inputAction) { return function (requestData, callback, settings) { - var input = inputAction ? inputAction(requestData) : {}; + var input = inputAction ? inputAction(requestData, settings) : {}; //Paging if (settings.oInit.paging) { From 22204a2f479dbcef37db707de367a5b17f46a3d6 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 3 Sep 2020 17:45:32 +0300 Subject: [PATCH 05/14] feat: add interpolate utility fn to replace placeholders --- .../core/src/lib/tests/string-utils.spec.ts | 26 ++++++++++++++++++- .../core/src/lib/utils/string-utils.ts | 6 +++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts index a0642e43ee..c2f7a3c601 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts @@ -1,4 +1,4 @@ -import { createTokenParser } from '../utils/string-utils'; +import { createTokenParser, interpolate } from '../utils/string-utils'; describe('String Utils', () => { describe('#createTokenParser', () => { @@ -19,4 +19,28 @@ describe('String Utils', () => { }, ); }); + + describe('#interpolate', () => { + test.each` + text | params | expected + ${'This is {0} and {1} example.'} | ${['foo', 'bar']} | ${'This is foo and bar example.'} + ${'This is {1} and {0} example.'} | ${['foo', 'bar']} | ${'This is bar and foo example.'} + ${'This is {0} and {0} example.'} | ${['foo', 'bar']} | ${'This is foo and foo example.'} + ${'This is {1} and {1} example.'} | ${['foo', 'bar']} | ${'This is bar and bar example.'} + ${'This is "{0}" and "{1}" example.'} | ${['foo', 'bar']} | ${'This is foo and bar example.'} + ${"This is '{1}' and '{0}' example."} | ${['foo', 'bar']} | ${'This is bar and foo example.'} + ${'This is { 0 } and {0} example.'} | ${['foo', 'bar']} | ${'This is foo and foo example.'} + ${'This is {1} and { 1 } example.'} | ${['foo', 'bar']} | ${'This is bar and bar example.'} + ${'This is {0}, {3}, {1}, and {2} example.'} | ${['foo', 'bar', 'baz', 'qux']} | ${'This is foo, qux, bar, and baz example.'} + ${'This is {0} with 0 example.'} | ${['foo']} | ${'This is foo with 0 example.'} + ${'This is {0} and {1} example.'} | ${['foo']} | ${'This is foo and {1} example.'} + ${'This is {0} and {1} example.'} | ${[]} | ${'This is {0} and {1} example.'} + ${'This is {0} example.'} | ${[null]} | ${'This is {0} example.'} + `( + 'should return $expected when text is $text and params are $params', + ({ text, params, expected }) => { + expect(interpolate(text, params)).toBe(expected); + }, + ); + }); }); diff --git a/npm/ng-packs/packages/core/src/lib/utils/string-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/string-utils.ts index 00db697325..7404dceab7 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/string-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/string-utils.ts @@ -15,3 +15,9 @@ export function createTokenParser(format: string) { }, {} as Record); }; } + +export function interpolate(text: string, params: string[]) { + return text + .replace(/(['"]?\{\s*(\d+)\s*\}['"]?)/g, (_, match, digit) => params[digit] ?? match) + .replace(/\s+/g, ' '); +} From 28371a3ba429d53fadc37040bcfa436a5ebca1c8 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Thu, 3 Sep 2020 17:47:06 +0300 Subject: [PATCH 06/14] fix: use interpolate to replace localization tokens --- .../packages/core/src/lib/states/config.state.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/states/config.state.ts b/npm/ng-packs/packages/core/src/lib/states/config.state.ts index 2aaf7a3bc2..ec9ccd6749 100644 --- a/npm/ng-packs/packages/core/src/lib/states/config.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/config.state.ts @@ -9,6 +9,7 @@ import { RestOccurError } from '../actions/rest.actions'; import { SetLanguage } from '../actions/session.actions'; import { ApplicationConfiguration } from '../models/application-configuration'; import { Config } from '../models/config'; +import { interpolate } from '../utils/string-utils'; import { SessionState } from './session.state'; @State({ @@ -180,14 +181,12 @@ export class ConfigState { return defaultValue || sourceKey; } + // [TODO]: next line should be removed in v3.2, breaking change!!! interpolateParams = interpolateParams.filter(params => params != null); - if (localization && interpolateParams && interpolateParams.length) { - interpolateParams.forEach(param => { - localization = localization.replace(/[\'\"]?\{[\d]+\}[\'\"]?/, param); - }); - } + if (localization) localization = interpolate(localization, interpolateParams); if (typeof localization !== 'string') localization = ''; + return localization || defaultValue || (key as string); }); From 1bc7f7f8a9559d7f61a9dffe36e0dffe96f5d357 Mon Sep 17 00:00:00 2001 From: Bunyamin Coskuner Date: Fri, 4 Sep 2020 13:48:15 +0300 Subject: [PATCH 07/14] docs: fix bold environment word --- docs/en/UI/Angular/Environment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/UI/Angular/Environment.md b/docs/en/UI/Angular/Environment.md index 86978cfa16..402e110f94 100644 --- a/docs/en/UI/Angular/Environment.md +++ b/docs/en/UI/Angular/Environment.md @@ -1,6 +1,6 @@ # Environment -Every application needs some ** environment ** variables. In Angular world, this is usually managed by `environment.ts`, `environment.prod.ts` and so on. It is the same for ABP as well. +Every application needs some **environment** variables. In Angular world, this is usually managed by `environment.ts`, `environment.prod.ts` and so on. It is the same for ABP as well. Current `Environment` configuration holds sub config classes as follows: From f540ed503f5a7c140b9b5abd71dbb3a66cd0e79e Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Fri, 4 Sep 2020 14:00:56 +0300 Subject: [PATCH 08/14] fix: skip property if ref is same as interface ref --- npm/ng-packs/packages/schematics/src/utils/model.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/schematics/src/utils/model.ts b/npm/ng-packs/packages/schematics/src/utils/model.ts index 2ac5949050..0e472e64d3 100644 --- a/npm/ng-packs/packages/schematics/src/utils/model.ts +++ b/npm/ng-packs/packages/schematics/src/utils/model.ts @@ -133,7 +133,11 @@ export function createImportRefToInterfaceReducerCreator(params: ModelGeneratorP return _interface.properties .reduce((refs, prop) => { - prop.refs.forEach(type => !types[type]?.isEnum && refs.push(type)); + prop.refs.forEach(type => { + if (types[type]?.isEnum || type === _interface.ref) return; + refs.push(type); + }); + return refs; }, []) .concat(base ? parseGenerics(typeDef.baseType!).toGenerics() : []) From 38b1943b6055a1614b21b724a7e4018bc068c97f Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Sat, 5 Sep 2020 22:11:24 +0800 Subject: [PATCH 09/14] Add CS0436 to common.props. Resolve #4778 --- common.props | 2 +- common.test.props | 2 +- modules/cms-kit/common.props | 2 +- modules/identityserver/common.props | 2 +- templates/app/aspnet-core/common.props | 2 +- templates/console/common.props | 2 +- templates/module/aspnet-core/common.props | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common.props b/common.props index 890cdff1e6..c9f4a9656a 100644 --- a/common.props +++ b/common.props @@ -2,7 +2,7 @@ latest 3.2.0 - $(NoWarn);CS1591 + $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ LICENSE.md diff --git a/common.test.props b/common.test.props index e90103e606..233afe8282 100644 --- a/common.test.props +++ b/common.test.props @@ -1,7 +1,7 @@ latest - $(NoWarn);CS1591 + $(NoWarn);CS1591;CS0436 true false false diff --git a/modules/cms-kit/common.props b/modules/cms-kit/common.props index 0e2bf35066..420d9c492c 100644 --- a/modules/cms-kit/common.props +++ b/modules/cms-kit/common.props @@ -2,7 +2,7 @@ latest 0.1.0 - $(NoWarn);CS1591 + $(NoWarn);CS1591;CS0436 module diff --git a/modules/identityserver/common.props b/modules/identityserver/common.props index a9aaa3c07a..7f5a705307 100644 --- a/modules/identityserver/common.props +++ b/modules/identityserver/common.props @@ -2,7 +2,7 @@ latest 0.3.0 - $(NoWarn);CS1591 + $(NoWarn);CS1591;CS0436 http://www.aspnetboilerplate.com/images/abp_nupkg.png http://abp.io git diff --git a/templates/app/aspnet-core/common.props b/templates/app/aspnet-core/common.props index 6caad60263..fbef88367b 100644 --- a/templates/app/aspnet-core/common.props +++ b/templates/app/aspnet-core/common.props @@ -2,7 +2,7 @@ latest 1.0.0 - $(NoWarn);CS1591 + $(NoWarn);CS1591;CS0436 app \ No newline at end of file diff --git a/templates/console/common.props b/templates/console/common.props index 4adb69f0fe..233f41c1ba 100644 --- a/templates/console/common.props +++ b/templates/console/common.props @@ -2,7 +2,7 @@ latest 0.1.0 - $(NoWarn);CS1591 + $(NoWarn);CS1591;CS0436 diff --git a/templates/module/aspnet-core/common.props b/templates/module/aspnet-core/common.props index 0e2bf35066..420d9c492c 100644 --- a/templates/module/aspnet-core/common.props +++ b/templates/module/aspnet-core/common.props @@ -2,7 +2,7 @@ latest 0.1.0 - $(NoWarn);CS1591 + $(NoWarn);CS1591;CS0436 module From 60453f12f56a3441a0433e7ea9c45aff7dc65fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 6 Sep 2020 14:41:49 +0300 Subject: [PATCH 10/14] Update the Angular-Service-Proxies post for the 3.1 final release. --- .../POST.md | 92 ++++++++++++------- 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md b/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md index d16540872f..09df3a2119 100644 --- a/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md +++ b/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/POST.md @@ -1,8 +1,8 @@ # Introducing the Angular Service Proxy Generation -Angular Service Proxy System **generates TypeScript services and models** to consume your backend HTTP APIs developed using the ABP Framework. So, you **don't manually create** models for your server side DTOs and perform raw HTTP calls to the server. +ABP Angular Service Proxy System **generates TypeScript services and models** to consume your backend HTTP APIs developed using the ABP Framework. So, you **don't manually create** models for your server side DTOs and perform raw HTTP calls to the server. -ABP Framework has introduced the **new** Angular Service Proxy Generation system with the version 3.1. While this feature was available since the [v2.3](https://blog.abp.io/abp/ABP-Framework-v2_3_0-Has-Been-Released), it was not well covering some scenarios, like inheritance and generic types and had some known problems. **With the v3.1, we've re-written** it using the [Angular Schematics](https://angular.io/guide/schematics) system. Now, it is much more stable and feature rich. +ABP Framework has introduced the **new** Angular Service Proxy Generation system with the **version 3.1**. While this feature was available since the [v2.3](https://blog.abp.io/abp/ABP-Framework-v2_3_0-Has-Been-Released), it was not well covering some scenarios, like inheritance and generic types and had some known problems. **With the v3.1, we've re-written** it using the [Angular Schematics](https://angular.io/guide/schematics) system. Now, it is much more stable and feature rich. This post introduces the service proxy generation system and highlights some important features. @@ -47,6 +47,15 @@ apis: { `Acme.BookStore` should be replaced by the root namespace of your .NET project. This ensures to not create unnecessary nested folders while creating the service proxy code. This value is `AngularProxyDemo` for the example solution explained below. +* Finally, add the following paths to the `tsconfig.base.json` to have a shortcut while importing proxies: + +```json +"paths": { + "@proxy": ["src/app/proxy/index.ts"], + "@proxy/*": ["src/app/proxy/*"] +} +``` + ## Basic Usage ### Project Creation @@ -139,21 +148,23 @@ abp generate-proxy It should produce an output like the following: ````bash -CREATE src/app/shared/models/books/index.ts (142 bytes) -CREATE src/app/shared/services/books/book.service.ts (437 bytes) ... +CREATE src/app/proxy/books/book.service.ts (446 bytes) +CREATE src/app/proxy/books/models.ts (148 bytes) +CREATE src/app/proxy/books/index.ts (57 bytes) +CREATE src/app/proxy/index.ts (33 bytes) ```` > `generate-proxy` command can take some some optional parameters for advanced scenarios (like [modular development](https://docs.abp.io/en/abp/latest/Module-Development-Basics)). You can take a look at the [documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies). -It basically creates two files; +#### The Generated Code -src/app/shared/services/books/**book.service.ts**: This is the service that can be injected and used to get the list of books; +`src/app/proxy/books/book.service.ts`: This is the service that can be injected and used to get the list of books; -````typescript +````js +import type { BookDto } from './models'; import { RestService } from '@abp/ng.core'; import { Injectable } from '@angular/core'; -import type { BookDto } from '../../models/book'; @Injectable({ providedIn: 'root', @@ -170,12 +181,11 @@ export class BookService { constructor(private restService: RestService) {} } - ```` -src/app/shared/models/books/**index.ts**: This file contains the modal classes corresponding to the DTOs defined in the server side; +`src/app/proxy/books/models.ts`: This file contains the modal classes corresponding to the DTOs defined in the server side; -````typescript +````js import type { EntityDto } from '@abp/ng.core'; export interface BookDto extends EntityDto { @@ -184,7 +194,21 @@ export interface BookDto extends EntityDto { } ```` -You can now inject the `BookService` into any Angular component and use the `getList()` method to get the list of books. +> There are a few more files have been generated to help you import the types easier. + +#### How to Import + +You can now import the `BookService` into any Angular component and use the `getList()` method to get the list of books. + +````js +import { BookService, BookDto } from '../proxy/books'; +```` + +You can also use the `@proxy` as a shortcut of the proxy folder: + +````js +import { BookService, BookDto } from '@proxy/books'; +```` ### About the Generated Code @@ -235,27 +259,20 @@ namespace AngularProxyDemo.Books } ``` -Let's re-run the `generate-proxy` command to see the result: - -```` -abp generate-proxy -```` - -The output of this command will be like the following: +Let's re-run the `generate-proxy` command: ````bash -UPDATE src/app/shared/services/books/book.service.ts (660 bytes) -UPDATE src/app/shared/models/books/index.ts (217 bytes) +abp generate-proxy ```` -It tells us two files have been updated. Let's see the changes; +This command will re-generate the proxies by updating some files. Let's see some of the changes; **book.service.ts** -````typescript +````js +import type { BookDto, BookUpdateDto } from './models'; import { RestService } from '@abp/ng.core'; import { Injectable } from '@angular/core'; -import type { BookDto, BookUpdateDto } from '../../models/books'; @Injectable({ providedIn: 'root', @@ -284,7 +301,7 @@ export class BookService { `update` function has been added to the `BookService` that gets an `id` and a `BookUpdateDto` as the parameters. -**index.ts** +**models.ts** ````typescript import type { EntityDto } from '@abp/ng.core'; @@ -358,14 +375,14 @@ namespace AngularProxyDemo.Orders } ```` -When I run the `abp generate-proxy` command again, I see two new files have been created. +When I run the `abp generate-proxy` command again, I see there are some created and updated files. Let's see some important ones; -src/app/shared/services/orders/**order.service.ts** +`src/app/proxy/orders/order.service.ts` -````typescript +````js +import type { OrderCreateDto } from './models'; import { RestService } from '@abp/ng.core'; import { Injectable } from '@angular/core'; -import type { OrderCreateDto } from '../../models/orders'; @Injectable({ providedIn: 'root', @@ -385,16 +402,19 @@ export class OrderService { } ```` -src/app/shared/models/orders/**index.ts** +`src/app/proxy/orders/models.ts` ````typescript -import type { GenericDetailDto } from '../../models/orders'; +export interface GenericDetailDto { + productId: string; + count: TCount; +} export interface OrderCreateDto { customerId: string; creationTime: string; details: OrderDetailDto[]; - extraProperties: string | object; + extraProperties: Record; } export interface OrderDetailDto extends GenericDetailDto { @@ -402,4 +422,10 @@ export interface OrderDetailDto extends GenericDetailDto { } ```` -NOTE: 3.1.0-rc2 was generating the code above, which is wrong. It will be fixed in the next RC versions. \ No newline at end of file +## Conclusion + +`abp generate-proxy` is a very handy command that creates all the necessary code to consume your ABP based backend HTTP APIs. It generates a clean code that is well aligned to the backend services and benefits from the power of TypeScript (by using generics, inheritance...). + +## The Documentation + +See [the documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies) for details of the Angular Service Proxy Generation. \ No newline at end of file From 57a90ad5eb25793f0be12ca169ef35ae41ee3eec Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 6 Sep 2020 23:01:00 +0800 Subject: [PATCH 11/14] Update Chinese documents. --- .../Authentication/Social-External-Logins.md | 9 +- docs/zh-Hans/CLI.md | 189 +++++++++++++----- docs/zh-Hans/Contribution/Index.md | 28 +-- ...-Application-Modules-Extending-Entities.md | 5 +- docs/zh-Hans/Entities.md | 6 + .../Entity-Framework-Core-Migrations.md | 5 +- docs/zh-Hans/Entity-Framework-Core.md | 5 +- .../Getting-Started-Console-Application.md | 3 - docs/zh-Hans/Global-Features.md | 3 + docs/zh-Hans/How-To/Index.md | 9 - docs/zh-Hans/Local-Event-Bus.md | 2 + docs/zh-Hans/MailKit.md | 1 + docs/zh-Hans/Module-Development-Basics.md | 2 +- docs/zh-Hans/Moodule-Entity-Extensions.md | 3 + docs/zh-Hans/Nightly-Builds.md | 32 +-- docs/zh-Hans/Previews.md | 1 + docs/zh-Hans/Repositories.md | 6 +- docs/zh-Hans/Samples/Index.md | 27 ++- docs/zh-Hans/Startup-Templates/Application.md | 14 ++ docs/zh-Hans/UI/Angular/Environment.md | 1 + docs/zh-Hans/UI/Angular/Migration-Guide-v3.md | 2 +- docs/zh-Hans/UI/Angular/Multi-Tenancy.md | 1 + .../UI/Angular/Permission-Management.md | 4 +- docs/zh-Hans/UI/Angular/Toaster-Service.md | 8 +- .../Customization-User-Interface.md | 2 +- docs/zh-Hans/Upgrading.md | 1 + docs/zh-Hans/Virtual-File-System.md | 105 ++++++---- docs/zh-Hans/docs-nav.json | 52 +++-- docs/zh-Hans/images/replace-email-layout.png | Bin 0 -> 31878 bytes .../zh-Hans/images/solution-files-non-mvc.png | Bin 5658 -> 3043 bytes 30 files changed, 332 insertions(+), 194 deletions(-) delete mode 100644 docs/zh-Hans/Getting-Started-Console-Application.md create mode 100644 docs/zh-Hans/Global-Features.md delete mode 100644 docs/zh-Hans/How-To/Index.md create mode 100644 docs/zh-Hans/MailKit.md create mode 100644 docs/zh-Hans/Moodule-Entity-Extensions.md create mode 100644 docs/zh-Hans/Previews.md create mode 100644 docs/zh-Hans/UI/Angular/Environment.md create mode 100644 docs/zh-Hans/UI/Angular/Multi-Tenancy.md create mode 100644 docs/zh-Hans/Upgrading.md create mode 100644 docs/zh-Hans/images/replace-email-layout.png diff --git a/docs/zh-Hans/Authentication/Social-External-Logins.md b/docs/zh-Hans/Authentication/Social-External-Logins.md index 3d599a962d..55ab97374c 100644 --- a/docs/zh-Hans/Authentication/Social-External-Logins.md +++ b/docs/zh-Hans/Authentication/Social-External-Logins.md @@ -1,10 +1,9 @@ # 社交/外部登录 -## ASP.NET Core MVC / Razor Pages UI [帐户模块](../Modules/Account.md)已配置为开箱即用的处理社交或外部登录. 你可以按照ASP.NET Core文档向你的应用程序添加社交/外部登录提供程序. -### 示例: Facebook 认证 +## 示例: Facebook 认证 按照[ASP.NET Core Facebook集成文档](https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/social/facebook-logins)向你应用程序添加Facebook登录. @@ -27,4 +26,8 @@ context.Services.AddAuthentication() }); ```` -> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密. \ No newline at end of file +> 最佳实践是使用 `appsettings.json` 或ASP.NET Core用户机密系统来存储你的凭据,而不是像这样硬编码值. 请参阅[微软](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/facebook-logins)文档了解如何使用用户机密. + +## Angular UI + +从v3.1开始,Angular UI使用授权码流程(作为最佳实践)通过重定向到MVC UI登录页面来对用户进行身份验证. 因此,即使你使用的是Angular UI,社交/外部登录集成也与上面说明的相同.并且可以开箱即用. \ No newline at end of file diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 82a72ec7ed..20afc1b50f 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -16,6 +16,12 @@ dotnet tool install -g Volo.Abp.Cli dotnet tool update -g Volo.Abp.Cli ```` +## 全局选项 + +虽然每个命令可能都有一组选项,但有些全局选项可以与任何命令一起使用: + +* `--skip-cli-version-check`: 跳过检查最新版本的ABP CLI. 如果没有指定,它会检查最新版本,如果检查到ABP CLI的新版本,会显示一条警告消息. + ## Commands 这里是所有可用的命令列表: @@ -25,7 +31,9 @@ dotnet tool update -g Volo.Abp.Cli * **`update`**:自动更新的ABP解决方案ABP相关的NuGet和NPM包. * **`add-package`**: 添加ABP包到项目. * **`add-module`**: 添加[应用模块](https://docs.abp.io/en/abp/latest/Modules/Index)到解决方案. -* **`generate-proxy`**: 生成客户端代理以使用服务器上的HTTP API端点. +* **`generate-proxy`**: 生成客户端代理以使用HTTP API端点. +* **`remove-proxy`**: 移除以前生成的客户端代理. +* **`switch-to-preview`**: 切换到ABP框架的最新预览版本。 * **`switch-to-preview`**: 切换解决方案所有ABP相关包为[夜间构建](Nightly-Builds.md)版本. * **`switch-to-stable`**: 切换解决方案所有ABP相关包为最新的稳定版本. * **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程. @@ -90,12 +98,33 @@ abp new Acme.BookStore * **`console`**: [Console template](Startup-Templates/Console.md). * `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. * `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,你会希望使用最新的版本. -* `--preview`: 使用最新的预发行版本 (仅在未指定 `--version` 且最新稳定版本之后至少有一个预发行版时). +* `--preview`: 使用最新的预览版本. * `--template-source` 或者 `-ts`: 指定自定义模板源用于生成项目,可以使用本地源和网络源(例如 `D:\local-templat` 或 `https://.../my-template-file.zip`). * `--create-solution-folder` 或者 `-csf`: 指定项目是在输出文件夹中的新文件夹中还是直接在输出文件夹中. * `--connection-string` 或者 `-cs`: 重写所有 `appsettings.json` 文件的默认连接字符串. 默认连接字符串是 `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. 默认的数据库提供程序是 `SQL Server`. 如果你使用EF Core但需要更改DBMS,可以按[这里所述](Entity-Framework-Core-Other-DBMS.md)进行更改(创建解决方案之后). * `--local-framework-ref --abp-path`: 使用对项目的本地引用,而不是替换为NuGet包引用. +### update + +更新所有ABP相关的包可能会很繁琐,框架和模块都有很多包. 此命令自动将解决方案或项目中所有ABP相关的包更新到最新版本. + +用法: + +````bash +abp update [options] +```` + +* 如果你的文件夹中有.sln文件,运行命令会将解决方案中所有项目ABP相关的包更新到最新版本. +* 如果你的文件夹中有.csproj文件,运行命令会将项目中所有ABP相关的包更新到最新版本. + +#### Options + +* `--npm`: 仅更新NPM包 +* `--nuget`: 仅更新的NuGet包 +* `--solution-path` 或 `-sp`: 指定解决方案路径/目录. 默认使用当前目录 +* `--solution-name` 或 `-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件. +*`--check-all`: 分别检查每个包的新版本. 默认是 `false`. + ### add-package 通过以下方式将ABP包添加到项目中 @@ -150,105 +179,157 @@ abp add-module Volo.Blogging * `-sp` 或 `--startup-project`: 启动项目的项目文件夹的相对路径. 默认值是当前文件夹. * `--with-source-code`: 添加模块的源代码,而不是NuGet/NPM软件包. -### update +### generate-proxy -更新所有ABP相关的包可能会很繁琐,框架和模块都有很多包. 此命令自动将解决方案或项目中所有ABP相关的包更新到最新版本. +为您的HTTP API生成Angular服务代理,简化从客户端使用服务的成本. 在运行此命令之前,你的host必须启动正在运行. 用法: ````bash -abp update [options] +abp generate-proxy ```` -* 如果你的文件夹中有.sln文件,运行命令会将解决方案中所有项目ABP相关的包更新到最新版本. -* 如果你的文件夹中有.csproj文件,运行命令会将项目中所有ABP相关的包更新到最新版本. - #### Options -* `--include-previews` 或 `-p`: 将预览版, 测试版本 和 rc 包 同时更新到最新版本. -* `--npm`: 仅更新NPM包 -* `--nuget`: 仅更新的NuGet包 -* `--solution-path` 或 `-sp`: 指定解决方案路径/目录. 默认使用当前目录 -* `--solution-name` 或 `-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件. -*`--check-all`: 分别检查每个包的新版本. 默认是 `false`. - -### 切换到每晚构建(预览)包 +* `--module` 或 `-m`: 指定要为其生成代理的后端模块的名称. 默认值: `app`. +* `--api-name` 或 `-a`: 在 `/src/environments/environment.ts` 中定义的API端点名称。. 默认值: `default`. +* `--source` 或 `-s`: 指定解析根名称空间和API定义URL的Angular项目名称. 默认值: `defaultProject` +* `--target` 或 `-t`: 指定放置生成的代码的Angular项目名称. 默认值: `defaultProject`. +* `--prompt` 或 `-p`: 在命令行提示符下询问选项(未指定的选项). -想要切换到ABP框架的最新**每晚构建**预览版可以使用此命令. +> 参阅 [Angular服务代理文档](UI/Angular/Service-Proxies.md) 了解更多. -用法: +### remove-proxy -````bash -abp switch-to-nightly [options] -```` +从Angular应用程序中删除以前生成的代理代码. 在运行此命令之前,你的host必须启动正在运行. -你也可以使用切换回最新稳定版本: +This can be especially useful when you generate proxies for multiple modules before and need to remove one of them later. + +Usage: ````bash -abp switch-to-stable [options] +abp remove-proxy ```` #### Options -`--solution-directory` 或 `-sd`: 指定解决方案文件夹. 解决方案应该在指定文件夹或子文件夹中. 如果未指定,默认为当前目录. +* `--module` 或 `-m`: 指定要为其生成代理的后端模块的名称. 默认值: `app`. +* `--api-name` 或 `-a`: 在 `/src/environments/environment.ts` 中定义的API端点名称。. 默认值: `default`. +* `--source` 或 `-s`: 指定解析根名称空间和API定义URL的Angular项目名称. 默认值: `defaultProject` +* `--target` 或 `-t`: 指定放置生成的代码的Angular项目名称. 默认值: `defaultProject`. +* `--prompt` 或 `-p`: 在命令行提示符下询问选项(未指定的选项). -### login +> 参阅 [Angular服务代理文档](UI/Angular/Service-Proxies.md) 了解更多. -CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录 +### switch-to-preview -```bash -abp login -``` +你可以使用此命令将项目切换到ABP框架的最新预览版本. -```bash -abp login -p -``` - -请注意,新的登录将终止先前的会话并创建一个新的会话. - -### logout +用法: -通过从计算机中删除会话令牌来注销. +````bash +abp switch-to-preview [options] +```` -``` -abp logout -``` +#### Options -### generate-proxy +* `--solution-directory` 或 `-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录. + +### switch-to-nightly -为你的HTTP API生成客户端代码,简化客户端使用服务的成本. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行. +想要切换到ABP框架的最新[每晚构建](Nightly-Builds.md)预览版可以使用此命令. 用法: ````bash -abp generate-proxy [options] +abp switch-to-nightly [options] ```` #### Options -* `--apiUrl` 或者 `-a`:指定HTTP API的根URL. 如果未指定这个选项,默认使用你Angular应用程序的`environment.ts`文件API URL. 在运行 `generate-proxy` 命令之前,你的host必须启动正在运行. -* `--ui` 或者 `-u`: 指定UI框架,默认框架是angular.当前只有angular一个选项, 但我们会通过更改CLI增加新的选项. 尽请关注! -* `--module` 或者 `-m`:指定模块名. 默认模块名称为app. 如果你想所有模块,你可以指定 `--module all` 命令. +`--solution-directory` 或 `-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录. -示例: +### switch-to-stable + +如果你使用的是ABP框架预览包(包括每晚构建),可以使用此命令切换回最新的稳定版本. + +用法: ````bash -abp generate-proxy --apiUrl https://localhost:44305 --ui angular --module all +abp switch-to-stable [options] ```` +#### Options -### help +`--solution-directory` 或 `-sd`: 指定目录. 解决方案应该在该目录或其子目录中. 如果未指定默认为当前目录. -CLI的基本用法信息. +### translate -用法: + +源代码控制存储库中有多个JSON[本地化](Localization.md)文件时,用于简化翻译[本地化](Localization.md)文件的过程. + +* 该命令将基于参考文化创建一个统一的json文件 +* 它搜索当前目录和所有子目录中的所有本地化"JSON"文件(递归). 然后创建一个包含所有需要翻译的条目的文件(默认情况下名为 "abp-translation.json"). +* 翻译了此文件中的条目后,你就可以使用 `--apply` 命令将更改应用于原始本地化文件. + +> 该命令的主要目的是翻译ABP框架本地化文件(因为[abp仓库](https://github.com/abpframework/abp)包括数十个要在不同目录中转换的本地化文件). + +#### 创建翻译文件 + +第一步是创建统一的翻译文件: ````bash -abp help [命令名] +abp translate -c [options] ```` 示例: ````bash -abp help # 显示常规帮助. -abp help new # 显示有关 "New" 命令的帮助. +abp translate -c de-DE ```` + +该命令为 `de-DE` (德语)文化创建了统一的翻译文件. + +##### 附加选项 + +* `--reference-culture` 或 `-r`: 默认值 `en`. 指定参考文化. +* `--output` 或 `-o`: 输出文件名. 默认值 `abp-translation.json`. +* `--all-values` 或 `-all`: 包括所有要翻译的键. 默认情况下,统一翻译文件仅包含目标文化的缺失文本. 如果你可能需要修改之前已经翻译的值,请指定此参数. + +#### 应用更改 + + +翻译完统一翻译文件中的条目后,你可以使用 `--apply` 参数将更改应用于原始本地化文件: + +````bash +abp translate --apply # apply all changes +abp translate -a # shortcut for --apply +```` + +然后,检查源代码控制系统上的更改,以确保它已更改了正确的文件. 如果你翻译了ABP框架资源, 请发送 "Pull Request". 提前感谢你的贡献. + +##### 附加选项 + +* `--file` 或 `-f`: 默认值: `abp-translation.json`. 翻译文件(仅在之前使用过 `--output` 选项时使用). + +### login + +CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录 + +```bash +abp login # Allows you to enter your password hidden +abp login -p # Specify the password as a parameter (password is visible) +abp login --organization # If you have multiple organizations, you need set your active organization +abp login -p -o # You can enter both your password and organization in the same command +``` + +> 当使用-p参数,请注意,因为你的密码是可见的. 它对于CI / CD自动化管道很有用. + +请注意,新的登录将终止先前的会话并创建一个新的会话. + +### logout + +通过从计算机中删除会话令牌来注销. + +``` +abp logout +``` diff --git a/docs/zh-Hans/Contribution/Index.md b/docs/zh-Hans/Contribution/Index.md index f3bafe9293..7b1c3544ef 100644 --- a/docs/zh-Hans/Contribution/Index.md +++ b/docs/zh-Hans/Contribution/Index.md @@ -1,8 +1,12 @@ -## 贡献指南 +# 贡献指南 ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南旨在帮助任何想要为项目做出贡献的人. -### 贡献代码 +## community.abp.io + +如果你可编写文章或关于ASP框架和ASP.NET Core的 "如何" 指南,请提交你的文章到[community.abp.io](https://community.abp.io/)网站. + +## 贡献代码 你可以将Pull request(拉取请求)发送到Github存储库. @@ -12,15 +16,15 @@ ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南 在进行任何更改之前,请在[Github问题](https://github.com/abpframework/abp/issues)上进行讨论. 通过这种方式, 其他开发人员将不会处理同一个问题, 你的PR将有更好的机会被接受. -#### Bug修复 & 增强功能 +### Bug修复 & 增强功能 你可能希望修复已知Bug或处理计划的增强功能. 请参阅Github上的[问题列表](https://github.com/abpframework/abp/issues). -#### 功能请求 +### 功能请求 如果你对框架或模块有功能的想法, 请在Github上[创建一个问题](https://github.com/abpframework/abp/issues/new)或参加现有的讨论. 如果它被社区所接受你就可以实现它. -### 文档翻译 +## 文档翻译 你可能希望将完整的[文档](https://abp.io/documents/)(包括本文)翻译成你的母语. 请按照下列步骤操作: @@ -37,13 +41,13 @@ ABP是[开源](https://github.com/abpframework)和社区驱动项目. 本指南 完成了这些基本的翻译后,将添加一种新的语言 -### 资源本地化 +## 资源本地化 ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自己的应用程序创建本地化用户界面. 除此之外,框架和预构建模块已经本地化了文本.请参阅[Volo.Abp.UI包的本地化文本](https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json). -#### 使用 "abp translate" 命令 +### 使用 "abp translate" 命令 这是推荐的方法,因为它会自动查找所有缺少的文本的特定文化,让你在一个地方翻译. @@ -54,14 +58,10 @@ ABP框架具有灵活的[本地化系统](../Localization.md). 你可以为自 * 一旦你完成了翻译,使用 `abp translate -a` 命令应用更改到相关的文件. * 在GitHub上发送PR. -#### 手动翻译 - -如果你想更改特定的资源文件,你可以自己找到这个文件进行必要的更改(或为你的语言创建新文件),并在GitHub上发送PR。 - -### 博客文章和教程 +### 手动翻译 -如果你发布了一些ABP框架的教程或博客帖子, 请通知我们(通过创建[Github问题](https://github.com/abpframework/abp/issues)), 我们可能会在官方文档中添加指向你的教程或博客帖子的链接和在[推特](https://twitter.com/abpframework)上公布. +如果你想更改特定的资源文件,你可以自己找到这个文件进行必要的更改(或为你的语言创建新文件),并在GitHub上发送PR. -### Bug 报告 +## Bug 报告 如果你发现任何Bug, 请[在Github存储库上创建一个问题](https://github.com/abpframework/abp/issues/new). \ No newline at end of file diff --git a/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md b/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md index 3e5ff3f6cd..6c895f71d2 100644 --- a/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md +++ b/docs/zh-Hans/Customizing-Application-Modules-Extending-Entities.md @@ -43,7 +43,10 @@ return user.GetProperty("Title"); ObjectExtensionManager.Instance .MapEfCoreProperty( "SocialSecurityNumber", - b => { b.HasMaxLength(32); } + (entityBuilder, propertyBuilder) => + { + propertyBuilder.HasMaxLength(32); + } ); ```` diff --git a/docs/zh-Hans/Entities.md b/docs/zh-Hans/Entities.md index 190088753f..23ca3e73f0 100644 --- a/docs/zh-Hans/Entities.md +++ b/docs/zh-Hans/Entities.md @@ -226,6 +226,12 @@ ABP框架不强制你应用任何DDD规则或模式.但是,当你准备应用的 虽然这种聚合根并不常见(也不建议使用),但实际上可以按照与上面提到的跟实体相同的方式定义复合键.在这种情况下,要使用非泛型的`AggregateRoot`基类. +### BasicAggregateRoot类 + +`AggregateRoot` 类实现了 `IHasExtraProperties` 和 `IHasConcurrencyStamp` 接口,这为派生类带来了两个属性. `IHasExtraProperties` 使实体可扩展(请参见下面的 *额外的属性*部分) 和 `IHasConcurrencyStamp` 添加了由ABP框架管理的 `ConcurrencyStamp` 属性实现[乐观并发](https://docs.microsoft.com/zh-cn/ef/core/saving/concurrency). 在大多数情况下,这些是聚合根需要的功能. + +但是,如果你不需要这些功能,你的聚合根可以继承 `BasicAggregateRoot`(或`BasicAggregateRoot`). + ## 基类和接口的审计属性 有一些属性,像`CreationTime`,`CreatorId`,`LastModificationTime`...在所有应用中都很常见. ABP框架提供了一些接口和基类来**标准化**这些属性,并**自动设置它们的值**. diff --git a/docs/zh-Hans/Entity-Framework-Core-Migrations.md b/docs/zh-Hans/Entity-Framework-Core-Migrations.md index 876d98721b..c6f1086540 100644 --- a/docs/zh-Hans/Entity-Framework-Core-Migrations.md +++ b/docs/zh-Hans/Entity-Framework-Core-Migrations.md @@ -414,7 +414,10 @@ public static class MyProjectNameEntityExtensions ObjectExtensionManager.Instance .MapEfCoreProperty( "Title", - builder => { builder.HasMaxLength(64); } + (entityBuilder, propertyBuilder) => + { + propertyBuilder.HasMaxLength(128); + } ); }); } diff --git a/docs/zh-Hans/Entity-Framework-Core.md b/docs/zh-Hans/Entity-Framework-Core.md index 5ae6ce1070..211ec4d65f 100644 --- a/docs/zh-Hans/Entity-Framework-Core.md +++ b/docs/zh-Hans/Entity-Framework-Core.md @@ -325,7 +325,10 @@ public class BookService ObjectExtensionManager.Instance .MapEfCoreProperty( "Title", - builder => { builder.HasMaxLength(64); } + (entityBuilder, propertyBuilder) => + { + propertyBuilder.HasMaxLength(64); + } ); ```` diff --git a/docs/zh-Hans/Getting-Started-Console-Application.md b/docs/zh-Hans/Getting-Started-Console-Application.md deleted file mode 100644 index 1bf48cefd4..0000000000 --- a/docs/zh-Hans/Getting-Started-Console-Application.md +++ /dev/null @@ -1,3 +0,0 @@ -## 在控制台应用中使用ABP - -ABP提供了控制台应用程序启动模板. 参阅[控制台应用程序启动模板]文档了解更多信息. \ No newline at end of file diff --git a/docs/zh-Hans/Global-Features.md b/docs/zh-Hans/Global-Features.md new file mode 100644 index 0000000000..776d4e0718 --- /dev/null +++ b/docs/zh-Hans/Global-Features.md @@ -0,0 +1,3 @@ +# Global Features + +TODO... \ No newline at end of file diff --git a/docs/zh-Hans/How-To/Index.md b/docs/zh-Hans/How-To/Index.md deleted file mode 100644 index 3190ced7ca..0000000000 --- a/docs/zh-Hans/How-To/Index.md +++ /dev/null @@ -1,9 +0,0 @@ -# "如何" 指南 - -本部分包含一些常见问题的 "如何" 指南. 尽管其中是一些常见的开发任务和ABP并不直接相关,但我们认为有一些具体的示例可以直接与基于ABP的应用程序一起使用. - -## Authentication - -* [如何为MVC / Razor页面应用程序自定义登录页面](Customize-Login-Page-MVC.md) -* [如何对MVC / Razor页面应用程序使用Azure Active Directory身份验证](Azure-Active-Directory-Authentication-MVC.md) -* [如何为ABP应用程序定制SignIn Manager](Customize-SignIn-Manager.md) \ No newline at end of file diff --git a/docs/zh-Hans/Local-Event-Bus.md b/docs/zh-Hans/Local-Event-Bus.md index 44e21d0384..3a955e90d7 100644 --- a/docs/zh-Hans/Local-Event-Bus.md +++ b/docs/zh-Hans/Local-Event-Bus.md @@ -156,6 +156,8 @@ namespace AbpDemo > 事件处理程序类必须注册到依赖注入(DI),示例中使用了 `ITransientDependency`. 参阅[DI文档](Dependency-Injection.md)了解更多选项. +如果您执行**数据库操作**并在事件处理程序中使用[仓储](Repositories.md),那么您可能需要创建一个[工作单元](Unit-Of-Work.md),因为一些存储库方法需要在**活动的工作单元**中工作. 确保处理方法设置为 `virtual`,并为该方法添加一个 `[UnitOfWork]` attribute. 或者手动使用 `IUnitOfWorkManager` 创建一个工作单元范围. + ## 事务和异常行为 当一个事件发布,订阅的事件处理程序将立即执行.所以; diff --git a/docs/zh-Hans/MailKit.md b/docs/zh-Hans/MailKit.md new file mode 100644 index 0000000000..cb82a640d5 --- /dev/null +++ b/docs/zh-Hans/MailKit.md @@ -0,0 +1 @@ +TODO... \ No newline at end of file diff --git a/docs/zh-Hans/Module-Development-Basics.md b/docs/zh-Hans/Module-Development-Basics.md index e43c57d0b2..1e1e9a42cd 100644 --- a/docs/zh-Hans/Module-Development-Basics.md +++ b/docs/zh-Hans/Module-Development-Basics.md @@ -1,4 +1,4 @@ -## 模块开发 +## 模块化 ### 介绍 diff --git a/docs/zh-Hans/Moodule-Entity-Extensions.md b/docs/zh-Hans/Moodule-Entity-Extensions.md new file mode 100644 index 0000000000..6e8c73c2a2 --- /dev/null +++ b/docs/zh-Hans/Moodule-Entity-Extensions.md @@ -0,0 +1,3 @@ +# Module Entity Extensions + +参阅 https://docs.abp.io/en/commercial/latest/guides/module-entity-extensions (文档会在近期完成). \ No newline at end of file diff --git a/docs/zh-Hans/Nightly-Builds.md b/docs/zh-Hans/Nightly-Builds.md index 020d69e02e..14a5b8ad8c 100644 --- a/docs/zh-Hans/Nightly-Builds.md +++ b/docs/zh-Hans/Nightly-Builds.md @@ -2,40 +2,18 @@ 所有框架和模块包每晚都部署到MyGet. 因此你可以使用或测试最新的代码,而无需等待下一个版本. -## 在Visual Studio配置 +## 安装和卸载每晚预览包 -> 需要Visual Studio 2017以上 - -1. 在VS中打开: `工具 > 选项 > NuGet 包管理器 > 程序包源` -2. 单击绿色的`+`图标 -3. 在底部输入名称(ABP Nightly)和并粘贴URL(`https://www.myget.org/F/abp-nightly/api/v3/index.json`)到源上. - ![night-build-add-nuget-source](images/night-build-add-nuget-source.png) -3. 单击`更新`按钮 -4. 点击`确定`按钮保存 - -## 安装包 - -现在, 你可以从**管理NuGet包** 或 **程序包管理器控制台** 将预览/夜间程序包安装到你的项目中. - -![night-build-add-nuget-package](images/night-build-add-nuget-package.png) - -1. 在nuget浏览中,选择"包括预发行版". -2. 将包源更改为`全部`. -3. 搜索nuget包. 你将看到包的预发布格式为`(VERSION)-preview(DATE)` (如本示例中的**v0.16.0-preview20190401**). -4. 你可以单击`安装`按钮将包添加到项目中. - -## 安装和卸载预览NPM包 - -预览NPM包的最新版本可以通过在应用程序的根文件夹命令运行命令安装: +可以通过在应用程序的根文件夹中运行以下命令安装最新版本的夜间预览软件包: ```bash -abp switch-to-preview --npm +abp switch-to-nightly ``` -如果你正在使用ABP框架预览包,你可以使用此命令切换回稳定版本: +如果你正在使用ABP框架每晚预览包,你可以使用此命令切换回稳定版本: ```bash -abp switch-to-stable --npm +abp switch-to-stable ``` 参阅 [ABP CLI 文档](./CLI.md) 了解更多信息. \ No newline at end of file diff --git a/docs/zh-Hans/Previews.md b/docs/zh-Hans/Previews.md new file mode 100644 index 0000000000..cb82a640d5 --- /dev/null +++ b/docs/zh-Hans/Previews.md @@ -0,0 +1 @@ +TODO... \ No newline at end of file diff --git a/docs/zh-Hans/Repositories.md b/docs/zh-Hans/Repositories.md index ee48186985..79186b79da 100644 --- a/docs/zh-Hans/Repositories.md +++ b/docs/zh-Hans/Repositories.md @@ -6,7 +6,11 @@ ## 通用(泛型)仓储 -ABP为每个聚合根或实体提供了 **默认的通用(泛型)仓储** . 你可以在服务中[注入](Dependency-Injection.md) `IRepository` 使用标准的**CRUD**操作. 用法示例: +ABP为每个聚合根或实体提供了 **默认的通用(泛型)仓储** . 你可以在服务中[注入](Dependency-Injection.md) `IRepository` 使用标准的**CRUD**操作. + +> 数据库提供程序层应正确配置为能够使用默认的通用存储库. 如果你已经使用启动模板创建了项目,则这些配置 **已经完成**了. 如果不是,请参考数据库提供程序文档([EF Core](Entity-Framework-Core.md) / [MongoDB](MongoDB.md))进行配置. + +**默认通用仓储用法示例:** ````C# public class PersonAppService : ApplicationService diff --git a/docs/zh-Hans/Samples/Index.md b/docs/zh-Hans/Samples/Index.md index 94e5392b51..db2e680883 100644 --- a/docs/zh-Hans/Samples/Index.md +++ b/docs/zh-Hans/Samples/Index.md @@ -15,17 +15,14 @@ 一个简单的CRUD应用程序,展示了使用ABP框架开发应用程序的基本原理. 使用不同的技术实现了相同的示例: * **Book Store: Razor Pages UI & Entity Framework Core** - - * [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC) - * [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore) + * [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=MVC&DB=EF) + * [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) * **Book Store: Angular UI & MongoDB** - - * [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG) + * [教程](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=NG&DB=Mongo) * [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) * **Book Store: Modular application (Razor Pages UI & EF Core)** - * [源码](https://github.com/abpframework/abp-samples/tree/master/BookStore-Modular) 如果没有Razor Pages & MongoDB 结合,但你可以检查两个文档来理解它,因为DB和UI不会互相影响. @@ -33,11 +30,14 @@ ### 其他示例 * **Entity Framework 迁移**: 演示如何将应用程序拆分为多个数据库的解决方案. 每个数据库包含不同的模块. - * [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) + * [源码](https://github.com/abpframework/abp-samples/tree/master/EfCoreMigrationDemo) * [EF Core数据库迁移文档](../Entity-Framework-Core-Migrations.md) * **SignalR Demo**: A simple chat application that allows to send and receive messages among authenticated users. * [源码](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo) * [SignalR 集成文档](../SignalR-Integration.md) +* **分布式架构中的实时消息** (使用 SingalR & RabbitMQ) + * [源码](https://github.com/abpframework/abp-samples/tree/master/SignalRTieredDemo) + * [文章](https://community.abp.io/articles/real-time-messaging-in-a-distributed-architecture-using-abp-framework-singalr-rabbitmq-daf47e17) * **Dashboard Demo**: 一个简单的应用程序,展示了如何在ASP.NET Core MVC UI中使用widget系统. * [源码](https://github.com/abpframework/abp-samples/tree/master/DashboardDemo) * [Widget 文档](../UI/AspNetCore/Widgets.md) @@ -50,15 +50,20 @@ * [文本模板文档](../Text-Templating.md) * **存储过程 Demo**: 演示如何以最佳实践使用存储过程,数据库视图和函数. * [源码](https://github.com/abpframework/abp-samples/tree/master/StoredProcedureDemo) +* **无密码认证**: 演示如何添加自定义令牌提供者使用链接验证用户身份,而不是输入密码. + * [源码](https://github.com/abpframework/abp-samples/tree/master/PasswordlessAuthentication) + * [文章](https://community.abp.io/articles/implementing-passwordless-authentication-with-asp.net-core-identity-c25l8koj) * **自定义认证**: 如何为ASP.NET Core MVC / Razor Pages应用程序自定义身份验证的解决方案. * [源码](https://github.com/abpframework/abp-samples/tree/master/Authentication-Customization) - * 相关 "[How To](../How-To/Index.md)" 文档: - * [Azure Active Directory 认证](../How-To/Azure-Active-Directory-Authentication-MVC.md) - * [自定义登录页面](../How-To/Customize-Login-Page-MVC.md) - * [自定义 SignIn Manager](../How-To/Customize-SignIn-Manager.md) + * 相关文章: + * [Azure Active Directory 认证](https://community.abp.io/articles/how-to-use-the-azure-active-directory-authentication-for-mvc-razor-page-applications-4603b9cf) + * [自定义登录页面](https://community.abp.io/articles/how-to-customize-the-login-page-for-mvc-razor-page-applications-9a40f3cd) + * [自定义 SignIn Manager](https://community.abp.io/articles/how-to-customize-the-signin-manager-3e858753) * **空的ASP.NET Core应用程序**: 从基本的ASP.NET Core应用程序使用ABP框架. * [源码](https://github.com/abpframework/abp-samples/tree/master/BasicAspNetCoreApplication) * [文档](../Getting-Started-AspNetCore-Application.md) +* **GRPC Demo**: 演示如何将gRPC服务添加到基于ABP框架的Web应用程序以及如何从控制台应用程序使用它. + * [源码](https://github.com/abpframework/abp-samples/tree/master/GrpcDemo) * **空的控制台应用程序**: 从基本的控制台应用程序安装ABP框架. * [源码](https://github.com/abpframework/abp-samples/tree/master/BasicConsoleApplication) * [文档](../Getting-Started-Console-Application.md) \ No newline at end of file diff --git a/docs/zh-Hans/Startup-Templates/Application.md b/docs/zh-Hans/Startup-Templates/Application.md index 534a26d8fb..58287cef5a 100644 --- a/docs/zh-Hans/Startup-Templates/Application.md +++ b/docs/zh-Hans/Startup-Templates/Application.md @@ -59,6 +59,20 @@ abp new Acme.BookStore -d mongodb ### 指定移动应用程序框架 +此模板支持以下移动应用程序框架: + +- `react-native`: React Native + +使用 `-m` (或 `--mobile`) 选项指定移动应用程序框架: + +````bash +abp new Acme.BookStore -m react-native +```` + +如果未指定,不会创建移动应用程序. + +### 指定移动应用程序框架 + 该模板支持以下移动应用程序框架: - `react-native`: React Native diff --git a/docs/zh-Hans/UI/Angular/Environment.md b/docs/zh-Hans/UI/Angular/Environment.md new file mode 100644 index 0000000000..cb82a640d5 --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Environment.md @@ -0,0 +1 @@ +TODO... \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md b/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md index eb16c21e03..fd40affc55 100644 --- a/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md +++ b/docs/zh-Hans/UI/Angular/Migration-Guide-v3.md @@ -450,4 +450,4 @@ export function switchLogos(store: Store) { ## 下一步是什么? -* [服务代理](Service-Proxies.md) \ No newline at end of file +* [环境](./Environment.md) \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/Multi-Tenancy.md b/docs/zh-Hans/UI/Angular/Multi-Tenancy.md new file mode 100644 index 0000000000..cb82a640d5 --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Multi-Tenancy.md @@ -0,0 +1 @@ +TODO... \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/Permission-Management.md b/docs/zh-Hans/UI/Angular/Permission-Management.md index 7f28035105..08496f03fe 100644 --- a/docs/zh-Hans/UI/Angular/Permission-Management.md +++ b/docs/zh-Hans/UI/Angular/Permission-Management.md @@ -4,7 +4,7 @@ 你可以使用 `ConfigState` 的 `getGrantedPolicy` 选择器获取经过身份验证的用户的权限. -你可以从Store中获取权限的布尔值: +你可以获取权限的布尔值: ```js import { Store } from '@ngxs/store'; @@ -78,4 +78,4 @@ const routes: Routes = [ ## 下一步是什么? -* [确认弹层](./Confirmation-Service.md) \ No newline at end of file +* [多租户](./Multi-Tenancy.md) \ No newline at end of file diff --git a/docs/zh-Hans/UI/Angular/Toaster-Service.md b/docs/zh-Hans/UI/Angular/Toaster-Service.md index 045cf9f2cf..405e1b6400 100644 --- a/docs/zh-Hans/UI/Angular/Toaster-Service.md +++ b/docs/zh-Hans/UI/Angular/Toaster-Service.md @@ -24,7 +24,7 @@ class DemoComponent { ### 如何显示一个Toast Overlay ```js -this.toast.success('Message', 'Title'); +this.toaster.success('Message', 'Title'); ``` - `ToasterService` 方法接收三个参数,分别是 `message`, `title`, 和 `options`. @@ -70,9 +70,9 @@ const options: Partial = { 已打开的toast overlay可以通过手动调用 `remove` 方法传递指定的 toast `id`删除. ```js -const toastId = this.toast.success('Message', 'Title') +const toastId = this.toaster.success('Message', 'Title') -this.toast.remove(toastId); +this.toaster.remove(toastId); ``` ### 如何删除所有的Toasts @@ -80,7 +80,7 @@ this.toast.remove(toastId); 可以手动调用 `clear` 方法删除所有的已打开的toasts. ```js -this.toast.clear(); +this.toaster.clear(); ``` ## API diff --git a/docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md b/docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md index c4a90a9f1a..7daf6c5d4b 100644 --- a/docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md +++ b/docs/zh-Hans/UI/AspNetCore/Customization-User-Interface.md @@ -157,7 +157,7 @@ public class MyLoginModel : LoginModel ## 重写静态资源 -重写模块的静态资源(像JavaScript,Css或图片文件)是很简单的. 只需要在解决方案的相同路径创建文件,虚拟文件系统会自动处理它. +重写模块的静态资源(像JavaScript,Css或图片文件)是很简单的. 只需要在解决方案的相同路径创建文件,[虚拟文件系统](../../Virtual-File-System.md)会自动处理它. ## 操作捆绑 diff --git a/docs/zh-Hans/Upgrading.md b/docs/zh-Hans/Upgrading.md new file mode 100644 index 0000000000..cb82a640d5 --- /dev/null +++ b/docs/zh-Hans/Upgrading.md @@ -0,0 +1 @@ +TODO... \ No newline at end of file diff --git a/docs/zh-Hans/Virtual-File-System.md b/docs/zh-Hans/Virtual-File-System.md index 6952c4d5da..4fcf861000 100644 --- a/docs/zh-Hans/Virtual-File-System.md +++ b/docs/zh-Hans/Virtual-File-System.md @@ -74,53 +74,12 @@ Configure(options => * 你的项目有一个名为 `MyFiles` 的目录. * 你只想添加 `MyFiles` 目录到虚拟文件系统. -#### 在开发过程中处理嵌入式文件 - -将文件嵌入到模块程序集中并能够通过引用程序集(或添加nuget包)在另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难. - -假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以使更改生效. 显然, 这是非常耗时和乏味的. - -我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能. - -下面的示例展示了应用程序依赖于包含嵌入文件的模块("MyModule"), 并且应用程序可以在开发过程中直接使用模块的源代码. - -````C# -[DependsOn(typeof(MyModule))] -public class MyWebAppModule : AbpModule -{ - public override void ConfigureServices(ServiceConfigurationContext context) - { - var hostingEnvironment = context.Services.GetHostingEnvironment(); - - if (hostingEnvironment.IsDevelopment()) //only for development time - { - Configure(options => - { - options.FileSets.ReplaceEmbeddedByPhysical( - Path.Combine( - hostingEnvironment.ContentRootPath, - string.Format( - "..{0}MyModuleProject", - Path.DirectorySeparatorChar - ) - ) - ); - }); - } - } -} -```` - -上面的代码假设`MyWebAppModule`和`MyModule`是Visual Studio解决方案中的两个不同的项目, `MyWebAppModule`依赖于`MyModule`. - -> [应用程序启动模板]已经为本地化文件应用这个方法,所以当你更改一个本地化文件时,它会自动检测到更改. - ## IVirtualFileProvider 将文件嵌入到程序集中并注册到虚拟文件系统后,可以使用 `IVirtualFileProvider` 接口来获取文件或目录内容: ````C# -public class MyService +public class MyService : ITransientDependency { private readonly IVirtualFileProvider _virtualFileProvider; @@ -129,7 +88,7 @@ public class MyService _virtualFileProvider = virtualFileProvider; } - public void Foo() + public void Test() { //Getting a single file var file = _virtualFileProvider @@ -154,7 +113,9 @@ public class MyService #### 虚拟文件中间件 -虚拟文件中间件用于向客户端/浏览器提供嵌入式(js, css, image ...)文件, 就像 **wwwroot** 文件夹中的物理(静态)文件一样. 在静态文件中间件之后添加它, 如下所示: +虚拟文件中间件用于向客户端/浏览器提供嵌入式(js, css, image ...)文件, 就像 **wwwroot** 文件夹中的物理(静态)文件一样. 它同时涵盖了物理文件. + +在你ASP.NET Core中间件配置中替换 `app.UseStaticFiles()` 为 `app.UseVirtualFiles()`: ````C# app.UseVirtualFiles(); @@ -170,6 +131,62 @@ app.UseVirtualFiles(); * Pages * Views +* Components * Themes 这允许你可以在 `.cshtml` 文件附近添加 `.js`, `.css`... 文件,更易于开发和维护你的项目. + +#### 在开发过程中处理嵌入式文件 + +将文件嵌入到模块程序集中并能够通过引用程序集(或添加nuget包)在另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难. + +假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以使更改生效. 显然, 这是非常耗时和乏味的. + +我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能. + +下面的示例展示了应用程序依赖于包含嵌入文件的模块("MyModule"), 并且应用程序可以在开发过程中直接使用模块的源代码. + +````C# +[DependsOn(typeof(MyModule))] +public class MyWebAppModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + + if (hostingEnvironment.IsDevelopment()) //only for development time + { + Configure(options => + { + options.FileSets.ReplaceEmbeddedByPhysical( + Path.Combine( + hostingEnvironment.ContentRootPath, + string.Format( + "..{0}MyModuleProject", + Path.DirectorySeparatorChar + ) + ) + ); + }); + } + } +} +```` + +上面的代码假设`MyWebAppModule`和`MyModule`是Visual Studio解决方案中的两个不同的项目, `MyWebAppModule`依赖于`MyModule`. + +> [应用程序启动模板]已经为本地化文件应用这个方法,所以当你更改一个本地化文件时,它会自动检测到更改. + +## 替换/重写虚拟文件 + +虚拟文件系统在运行时创建一个统一的文件系统,其中实际的文件在开发时被分配到不同的模块中. + +如果两个模块将文件添加到相同的虚拟路径(如`my-path/my-file.css`),之后添加的模块将替换/替换前一个([模块依赖](Module-Development-Basics.md)顺序决定了添加文件的顺序). + +此功能允许你的应用程序可以覆盖/替换定义应用程序所使用的模块的任何虚拟文件. 这是ABP框架的基本可扩展性功能之一. + +因此,如果需要替换模块的文件,只需在模块/应用程序中完全相同的路径中创建该文件. + +### 物理文件 + +物理文件总是覆盖虚拟文件. 这意味着如果你把一个文件放在 `/wwwroot/my-folder/my-file.css`,它将覆盖虚拟文件系统相同位置的文件.因此你需要知道在模块中定义的文件路径来覆盖它们. \ No newline at end of file diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index df72d6e3df..5df3376507 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -4,21 +4,16 @@ "text": "入门", "items": [ { - "text": "从启动模板开始", + "text": "Web应用程序", "path": "Getting-Started.md" }, { - "text": "从空项目开始", - "items": [ - { - "text": "使用ASP.NET Core Web Application", - "path": "Getting-Started-AspNetCore-Application.md" - }, - { - "text": "使用Console Application", - "path": "Getting-Started-Console-Application.md" - } - ] + "text": "控制台应用程序", + "path": "Startup-Templates/Console.md" + }, + { + "text": "空Web应用程序t", + "path": "Getting-Started-AspNetCore-Application.md" } ] }, @@ -66,10 +61,6 @@ } ] }, - { - "text": "\"如何\" 指南", - "path": "How-To/Index.md" - }, { "text": "从ASP.NET Boilerplate迁移", "path": "AspNet-Boilerplate-Migration-Guide.md" @@ -201,6 +192,19 @@ "path": "Object-To-Object-Mapping.md" }, + { + "text": "邮件发送", + "items": [ + { + "text": "邮件发送系统", + "path": "Emailing.md" + }, + { + "text": "MailKit集成", + "path": "MailKit.md" + } + ] + }, { "text": "BLOB存储", "items": [ @@ -397,6 +401,10 @@ "text": "v2.x 到 v3 迁移指南", "path": "UI/Angular/Migration-Guide-v3.md" }, + { + "text": "环境", + "path": "UI/Angular/Environment.md" + }, { "text": "服务代理", "path": "UI/Angular/Service-Proxies.md" @@ -413,6 +421,10 @@ "text": "权限管理", "path": "UI/Angular/Permission-Management.md" }, + { + "text": "多租户", + "path": "UI/Angular/Multi-Tenancy.md" + }, { "text": "确认弹层", "path": "UI/Angular/Confirmation-Service.md" @@ -635,6 +647,10 @@ "text": "微服务架构", "path": "Microservice-Architecture.md" }, + { + "text": "预览版本", + "path": "Previews.md" + }, { "text": "每日构建", "path": "Nightly-Builds.md" @@ -643,6 +659,10 @@ "text": "路线图", "path": "Road-Map.md" }, + { + "text": "升级", + "path": "Upgrading.md" + }, { "text": "贡献指南", "path": "Contribution/Index.md" diff --git a/docs/zh-Hans/images/replace-email-layout.png b/docs/zh-Hans/images/replace-email-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..67db895f92a549bb4ddadf1606e6c97737cf5fd9 GIT binary patch literal 31878 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 literal 5658 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 From fe8417cce460a9a37929f493f494f71bed12f7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 6 Sep 2020 21:47:23 +0300 Subject: [PATCH 12/14] Add link to the article. --- docs/en/UI/Angular/Service-Proxies.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/UI/Angular/Service-Proxies.md b/docs/en/UI/Angular/Service-Proxies.md index 40fb18f45d..5466d64182 100644 --- a/docs/en/UI/Angular/Service-Proxies.md +++ b/docs/en/UI/Angular/Service-Proxies.md @@ -1,5 +1,7 @@ ## Service Proxies +> THIS DOCUMENT IS OUTDATED. IT IS BEING UPDATED. MEANWHILE, YOU CAN [SEE THIS ARTICLE](https://github.com/abpframework/abp/blob/dev/docs/en/Blog-Posts/2020-09-07%20Angular-Service-Proxies/POST.md) TO LEARN HOW TO USE THE ABP ANGULAR SERVICE PROXIES. + It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on the server side) and **model objects** (matches to [DTOs](../../Data-Transfer-Objects) in the server side). In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced: From 57697b59c016492bf1650e0f5e8c8d8c508fd8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sun, 6 Sep 2020 21:47:59 +0300 Subject: [PATCH 13/14] Delete abp-generate-proxy-output.png --- .../abp-generate-proxy-output.png | Bin 42515 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/abp-generate-proxy-output.png diff --git a/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/abp-generate-proxy-output.png b/docs/en/Blog-Posts/2020-09-07 Angular-Service-Proxies/abp-generate-proxy-output.png deleted file mode 100644 index 0a77d8e40fe256f7443fe81b4552d310414f31f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42515 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 Date: Mon, 7 Sep 2020 09:20:33 +0300 Subject: [PATCH 14/14] Update POST.md --- .../POST.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md index 2cb53e92fd..7bfd20637f 100644 --- a/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md +++ b/docs/en/Community-Articles/2020-08-31-Adding-User-Navigation-In-Suite/POST.md @@ -24,7 +24,7 @@ Then add a string property called `Title`, as an example property. ABP Suite needs a DTO for the target entity (user, in this case) in order to define a navigation property. -To do this, create a new folder called "Users" in `*.Application.Contracts` then add a new class called `AppUserDto` inherited from `IdentityUserDto`. +To do this, create a new folder called "Users" in `*.Application.Contracts` then add a new class called `AppUserDto` inherited from `IdentityUserDto`. ![create-appuserdto](create-appuserdto.jpg) @@ -40,13 +40,13 @@ CreateMap().Ignore(x => x.ExtraProperties); ### Define the Navigation Property -Get back to ABP Suite, open the **Navigation Properties** tab of the ABP Suite, click the **Add Navigation Property** button. Browse `AppUser.cs` in `*.Domain\Users` folder. Then choose the `Name` item as display property. Browse `AppUserDto.cs` in `*.Contracts\Users` folder. Choose `Users` from Collection Names dropdown. +Get back to ABP Suite, open the **Navigation Properties** tab of the ABP Suite, click the **Add Navigation Property** button. Browse `AppUser.cs` in `*.Domain\Users` folder. Then choose the `Name` item as display property. Browse `AppUserDto.cs` in `*.Contracts\Users` folder. Choose `Users` from Collection Names dropdown. ![add-user-navigation](add-user-navigation.jpg) ### Generate the Code! -That's it! Click **Save and generate** button to create your page. You'll see the following page if everything goes well. +That's it! Click **Save and generate** button to create your page. You'll see the following page if everything goes well. ![final-page](final-page.jpg) @@ -54,7 +54,7 @@ This is the new page that has been created by the ABP Suite. It can perform the **Picking Users from Look Up Table** -We used dropdown element to select a user from the user list. If you have a lot of users, then it's good to pick a user from a look up table. A look up table is a modal window that lets you filter data and pick one. To do this, get back to Suite and click **Edit** button of user navigation which is set as `AppUserId` name. Choose "Modal" from the "UI Pick Type" field. Then click **Save and generate** button to recreate your page. +We used dropdown element to select a user from the user list. If you have a lot of users, then it's good to pick a user from a look up table. A look up table is a modal window that lets you filter data and pick one. To do this, get back to Suite and click **Edit** button of user navigation which is set as `AppUserId` name. Choose "Modal" from the "UI Pick Type" field. Then click **Save and generate** button to recreate your page. ![ui-pick-type-modal](ui-pick-type-modal.jpg) @@ -64,18 +64,18 @@ After successful code generation, you'll see the the user can be picked from use ## About the ABP Commercial RC -This example has been implemented with **ABP Commercial 3.1.0-rc.3**. This is a RC version. If you want to install the CLI and Suite RC version follow the next steps: +This example has been implemented with **ABP Commercial 3.1.0**. If you have not installed the ABP CLI and ABP Suite, follow the next steps: -1- Uninstall the current version of the CLI and install the specific RC version: +1- Uninstall the current version of the CLI and install: ```bash -dotnet tool uninstall --global Volo.Abp.Cli && dotnet tool install --global Volo.Abp.Cli --version 3.1.0-rc.3 +dotnet tool install --global Volo.Abp.Cli --version 3.1.0 ``` -2- Uninstall the current version of the Suite and install the specific RC version: +2- Uninstall the current version of the Suite and install: ```bash -dotnet tool uninstall --global Volo.Abp.Suite && dotnet tool install -g Volo.Abp.Suite --version 3.1.0-rc.3 --add-source https://nuget.abp.io//v3/index.json +dotnet tool uninstall --global Volo.Abp.Suite && dotnet tool install -g Volo.Abp.Suite --version 3.1.0 --add-source https://nuget.abp.io//v3/index.json ``` Don't forget to replace the `` with your own key!