diff --git a/docs/en/CLI.md b/docs/en/CLI.md index 06df019f78..57bda25004 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -46,6 +46,8 @@ abp new Acme.BookStore * `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios. * `angular`: Angular. There are some additional options for this template: * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side. + * `none`: Without UI. There are some additional options for this template: + * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side. * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: * `ef`: Entity Framework Core. * `mongodb`: MongoDB. diff --git a/docs/en/Modules/Setting-Management.md b/docs/en/Modules/Setting-Management.md index 7a4f2a729b..39e8f8a0df 100644 --- a/docs/en/Modules/Setting-Management.md +++ b/docs/en/Modules/Setting-Management.md @@ -73,9 +73,10 @@ Setting values are cached using the [distributed cache](../Caching.md) system. A ## Setting Management Providers -Setting Management module is extensible, just like the [setting system](../Settings.md). You can extend it by defining setting management providers. There are four pre-built setting management providers registered by the order below: +Setting Management module is extensible, just like the [setting system](../Settings.md). You can extend it by defining setting management providers. There are 5 pre-built setting management providers registered by the order below: * `DefaultValueSettingManagementProvider`: Gets the value from the default value of the setting definition. It can not set the default value since default values are hard-coded on the setting definition. +* `ConfigurationSettingManagementProvider`: Gets the value from the [IConfiguration service](Configuration.md). It can not set the configuration value because it is not possible to change the configuration values on runtime. * `GlobalSettingManagementProvider`: Gets or sets the global (system-wide) value for a setting. * `TenantSettingManagementProvider`: Gets or sets the setting value for a tenant. * `UserSettingManagementProvider`: Gets the setting value for a user. diff --git a/docs/en/Settings.md b/docs/en/Settings.md index 28b1b370ee..5a5616a71a 100644 --- a/docs/en/Settings.md +++ b/docs/en/Settings.md @@ -106,9 +106,10 @@ Setting system is extensible, you can extend it by defining setting value provid `ISettingProvider` uses the setting value providers to obtain a setting value. It fallbacks to the next value provider if a value provider can not get the setting value. -There are four pre-built setting value providers registered by the order below: +There are 5 pre-built setting value providers registered by the order below: * `DefaultValueSettingValueProvider`: Gets the value from the default value of the setting definition, if set (see the SettingDefinition section above). +* `ConfigurationSettingValueProvider`: Gets the value from the [IConfiguration service](Configuration.md). * `GlobalSettingValueProvider`: Gets the global (system-wide) value for a setting, if set. * `TenantSettingValueProvider`: Gets the setting value for the current tenant, if set (see the [multi-tenancy](Multi-Tenancy.md) document). * `UserSettingValueProvider`: Gets the setting value for the current user, if set (see the [current user](CurrentUser.md) document). @@ -144,6 +145,7 @@ public class CustomSettingValueProvider : SettingValueProvider Every provider should have a unique Name (which is "Custom" here). Built-in providers use the given names: * `DefaultValueSettingValueProvider`: "**D**". +* `ConfigurationSettingValueProvider`: "**C**". * `GlobalSettingValueProvider`: "**G**". * `TenantSettingValueProvider`: "**T**". * `UserSettingValueProvider`: "**U**". @@ -175,4 +177,4 @@ You can replace this service in the dependency injection system to customize the The core setting system is pretty independent and doesn't make any assumption about how you manage (change) the setting values. Even the default `ISettingStore` implementation is the `NullSettingStore` which returns null for all setting values. -The setting management module completes it (and implements `ISettingStore`) by managing setting values in a database. See the [Setting Management Module document](Modules/Setting-Management.md) for more. \ No newline at end of file +The setting management module completes it (and implements `ISettingStore`) by managing setting values in a database. See the [Setting Management Module document](Modules/Setting-Management.md) for more. diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 767d971b28..f5f230d9f2 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -40,19 +40,21 @@ abp new Acme.BookStore #### Options * `--template` 或者 `-t`: 指定模板. 默认的模板是 `app`,会生成web项目.可用的模板有: - * `app` (default): [应用程序模板](Startup-Templates/Application.md)。 其他选项: - * `--ui` 或者 `-u`: 指定ui框架。默认`mvc`框架。其他选项: - * `mvc`: ASP.NET Core MVC。此模板的其他选项: - * `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的。如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景。 + * `app` (default): [应用程序模板](Startup-Templates/Application.md). 其他选项: + * `--ui` 或者 `-u`: 指定ui框架.默认`mvc`框架.其他选项: + * `mvc`: ASP.NET Core MVC.此模板的其他选项: + * `--tiered`: 创建分层解决方案,Web和Http Api层在物理上是分开的.如果未指定会创建一个分层的解决方案,此解决方案没有那么复杂,适合大多数场景. * `angular`: Angular. 这个模板还有一些额外的选项: - * `--separate-identity-server`: Separates the identity server application from the API host application. If not specified, you will have a single endpoint in the server side. - * `--database-provider` 或者 `-d`: 指定数据库提供程序。默认是 `ef`。其他选项: + * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. + * `none`: 无UI. 这个模板还有一些额外的选项: + * `--separate-identity-server`: 将Identity Server应用程序与API host应用程序分开. 如果未指定,则服务器端将只有一个端点. + * `--database-provider` 或者 `-d`: 指定数据库提供程序.默认是 `ef`.其他选项: * `ef`: Entity Framework Core. * `mongodb`: MongoDB. * `module`: [Module template](Startup-Templates/Module.md). 其他选项: - * `--no-ui`: 不包含UI。仅创建服务模块(也称为微服务 - 没有UI)。 -* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录。 -* `--version` 或者 `-v`: 指定ABP和模板的版本。它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本。大多数情况下,您会希望使用最新的版本。 + * `--no-ui`: 不包含UI.仅创建服务模块(也称为微服务 - 没有UI). +* `--output-folder` 或者 `-o`: 指定输出文件夹,默认是当前目录. +* `--version` 或者 `-v`: 指定ABP和模板的版本.它可以是 [release tag](https://github.com/abpframework/abp/releases) 或者 [branch name](https://github.com/abpframework/abp/branches). 如果没有指定,则使用最新版本.大多数情况下,您会希望使用最新的版本. ### add-package diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 3bebf3103a..4d4f6f797e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -194,6 +194,8 @@ namespace Volo.Abp.Cli.Commands var optionValue = commandLineArgs.Options.GetOrNull(Options.UiFramework.Short, Options.UiFramework.Long); switch (optionValue) { + case "none": + return UiFramework.None; case "mvc": return UiFramework.Mvc; case "angular": diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs index eab34d141b..1ea2760b1c 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs @@ -3,7 +3,8 @@ public enum UiFramework { NotSpecified = 0, - Mvc = 1, - Angular = 2 + None = 1, + Mvc = 2, + Angular = 3 } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFrameworkExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFrameworkExtensions.cs index 09eb0c474c..9161c9c63e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFrameworkExtensions.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFrameworkExtensions.cs @@ -6,6 +6,7 @@ { switch (uiFramework) { + case UiFramework.None: return "none"; case UiFramework.Mvc: return "mvc"; case UiFramework.Angular: return "angular"; case UiFramework.NotSpecified: return "NotSpecified"; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs index 8278a29b81..1ca4a7f287 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Volo.Abp.Cli.ProjectBuilding.Building; using Volo.Abp.Cli.ProjectBuilding.Building.Steps; @@ -52,50 +53,85 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App } } - private void DeleteUnrelatedProjects(ProjectBuildContext context, List steps) + private static void DeleteUnrelatedProjects(ProjectBuildContext context, List steps) { - if (context.BuildArgs.UiFramework == UiFramework.Mvc) + switch (context.BuildArgs.UiFramework) + { + case UiFramework.None: + ConfigureWithoutUi(context, steps); + break; + + case UiFramework.Angular: + ConfigureWithAngularUi(context, steps); + break; + + case UiFramework.Mvc: + case UiFramework.NotSpecified: + ConfigureWithMvcUi(context, steps); + break; + } + + if (context.BuildArgs.UiFramework != UiFramework.Angular) { - if (context.BuildArgs.ExtraProperties.ContainsKey("tiered")) - { - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web")); - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests")); - steps.Add(new AppTemplateProjectRenameStep("MyCompanyName.MyProjectName.Web.Host", "MyCompanyName.MyProjectName.Web")); - } - else - { - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host")); - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host")); - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer")); - steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44303")); - } + steps.Add(new RemoveFolderStep("/angular")); + } + } + private static void ConfigureWithoutUi(ProjectBuildContext context, List steps) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests")); + + if (context.BuildArgs.ExtraProperties.ContainsKey("separate-identity-server")) + { steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds")); } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer")); + steps.Add(new AppTemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305")); + } + } - if (context.BuildArgs.UiFramework != UiFramework.Mvc) + private static void ConfigureWithMvcUi(ProjectBuildContext context, List steps) + { + if (context.BuildArgs.ExtraProperties.ContainsKey("tiered")) { steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web")); - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host")); steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests")); - - if (context.BuildArgs.ExtraProperties.ContainsKey("separate-identity-server")) - { - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds")); - steps.Add(new AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep()); - } - else - { - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host")); - steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer")); - steps.Add(new AppTemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host")); - steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305")); - } + steps.Add(new AppTemplateProjectRenameStep("MyCompanyName.MyProjectName.Web.Host", "MyCompanyName.MyProjectName.Web")); + } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer")); + steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44303")); } - if (context.BuildArgs.UiFramework != UiFramework.Angular) + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds")); + } + + private static void ConfigureWithAngularUi(ProjectBuildContext context, List steps) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests")); + + if (context.BuildArgs.ExtraProperties.ContainsKey("separate-identity-server")) { - steps.Add(new RemoveFolderStep("/angular")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds")); + steps.Add(new AngularEnvironmentFilePortChangeForSeparatedIdentityServersStep()); + } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer")); + steps.Add(new AppTemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305")); } } @@ -114,12 +150,12 @@ namespace Volo.Abp.Cli.ProjectBuilding.Templates.App ); } - private void UpdateNuGetConfig(ProjectBuildContext context, List steps) + private static void UpdateNuGetConfig(ProjectBuildContext context, List steps) { steps.Add(new UpdateNuGetConfigStep("/aspnet-core/NuGet.Config")); } - private void CleanupFolderHierarchy(ProjectBuildContext context, List steps) + private static void CleanupFolderHierarchy(ProjectBuildContext context, List steps) { if (context.BuildArgs.UiFramework == UiFramework.Mvc) { diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs index 48a85dd7d9..716d8dd726 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/AbpSettingsModule.cs @@ -25,6 +25,7 @@ namespace Volo.Abp.Settings Configure(options => { options.ValueProviders.Add(); + options.ValueProviders.Add(); options.ValueProviders.Add(); options.ValueProviders.Add(); options.ValueProviders.Add(); diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ConfigurationSettingValueProvider.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ConfigurationSettingValueProvider.cs new file mode 100644 index 0000000000..70f2a6c346 --- /dev/null +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/ConfigurationSettingValueProvider.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Settings +{ + public class ConfigurationSettingValueProvider : ISettingValueProvider, ITransientDependency + { + public const string ConfigurationNamePrefix = "Settings:"; + + public const string ProviderName = "C"; + + public string Name => ProviderName; + + protected IConfiguration Configuration { get; } + + public ConfigurationSettingValueProvider(IConfiguration configuration) + { + Configuration = configuration; + } + + public virtual Task GetOrNullAsync(SettingDefinition setting) + { + return Task.FromResult(Configuration[ConfigurationNamePrefix + setting.Name]); + } + } +} \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs index 237b226b07..64715d6196 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs @@ -18,6 +18,7 @@ namespace Volo.Abp.SettingManagement Configure(options => { options.Providers.Add(); + options.Providers.Add(); options.Providers.Add(); options.Providers.Add(); options.Providers.Add(); diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ConfigurationSettingManagementProvider.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ConfigurationSettingManagementProvider.cs new file mode 100644 index 0000000000..c2f7654fd8 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ConfigurationSettingManagementProvider.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; + +namespace Volo.Abp.SettingManagement +{ + public class ConfigurationSettingManagementProvider : ISettingManagementProvider, ITransientDependency + { + public string Name => ConfigurationSettingValueProvider.ProviderName; + + protected IConfiguration Configuration { get; } + + public ConfigurationSettingManagementProvider(IConfiguration configuration) + { + Configuration = configuration; + } + + public Task GetOrNullAsync(SettingDefinition setting, string providerKey) + { + return Task.FromResult(Configuration[ConfigurationSettingValueProvider.ConfigurationNamePrefix + setting.Name]); + } + + public Task SetAsync(SettingDefinition setting, string value, string providerKey) + { + throw new AbpException($"Can not set a setting value to the application configuration."); + } + + public Task ClearAsync(SettingDefinition setting, string providerKey) + { + throw new AbpException($"Can not set a setting value to the application configuration."); + } + } +} \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ConfigurationValueSettingManagerExtensions.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ConfigurationValueSettingManagerExtensions.cs new file mode 100644 index 0000000000..2c1af4b107 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/ConfigurationValueSettingManagerExtensions.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Settings; + +namespace Volo.Abp.SettingManagement +{ + public static class ConfigurationValueSettingManagerExtensions + { + public static Task GetOrNullConfigurationAsync(this ISettingManager settingManager, [NotNull] string name, bool fallback = true) + { + return settingManager.GetOrNullAsync(name, ConfigurationSettingValueProvider.ProviderName, null, fallback); + } + + public static Task> GetAllConfigurationAsync(this ISettingManager settingManager, bool fallback = true) + { + return settingManager.GetAllAsync(ConfigurationSettingValueProvider.ProviderName, null, fallback); + } + } +} \ No newline at end of file diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/DefaultValueSettingManagementProvider_Tests.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/DefaultValueSettingManagementProvider_Tests.cs index d19f587a78..20263a73c9 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/DefaultValueSettingManagementProvider_Tests.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/DefaultValueSettingManagementProvider_Tests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; using Shouldly; using Volo.Abp.Settings; using Xunit;