diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json index f461fe8bbd..d4e51eeadf 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Account/Localization/Resources/en.json @@ -1,7 +1,7 @@ { "culture": "en", "texts": { - "Account": "Account", + "Account": "ABP Account - Login & Register | ABP.IO", "Welcome": "Welcome", "UseOneOfTheFollowingLinksToContinue": "Use one of the following links to continue", "FrameworkHomePage": "Framework home page", diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index 2502dce76a..accf9a91b3 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -1,7 +1,7 @@ { "culture": "en", "texts": { - "GetStarted": "Get Started", + "GetStarted": "Get Started - Startup Templates", "Create": "Create", "NewProject": "New Project", "DirectDownload": "Direct Download", @@ -86,7 +86,7 @@ "BasedOnFamiliarToolsExplanation": "Built on and integrated to popular tools you already know. Low learning curve, easy adaptation, comfortable development.", "ORMIndependent": "ORM Independent", "ORMIndependentExplanation": "The core framework is ORM/database independent and can work with any data source. Entity Framework Core and MongoDB providers are already available.", - "Features": "Features", + "Features": "Explore the ABP Framework Features", "ABPCLI": "ABP CLI", "Modularity": "Modularity", "BootstrapTagHelpers": "Bootstrap Tag Helpers", diff --git a/docs/en/Application-Services.md b/docs/en/Application-Services.md index c860f0dd82..3fb52377b3 100644 --- a/docs/en/Application-Services.md +++ b/docs/en/Application-Services.md @@ -201,7 +201,7 @@ See the [authorization document](Authorization.md) for more. ## CRUD Application Services -If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from `CrudAppService`. +If you need to create a simple **CRUD application service** which has Create, Update, Delete and Get methods, you can use ABP's **base classes** to easily build your services. You can inherit from the `CrudAppService`. ### Example @@ -219,7 +219,9 @@ public interface IBookAppService : } ```` -* `ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface). +`ICrudAppService` has generic arguments to get the primary key type of the entity and the DTO types for the CRUD operations (it does not get the entity type since the entity type is not exposed to the clients use this interface). + +> Creating interface for an application service is a good practice, but not required by the ABP Framework. You can skip the interface part. `ICrudAppService` declares the following methods: @@ -292,6 +294,52 @@ public class BookAppService : `CrudAppService` implements all methods declared in the `ICrudAppService` interface. You can then add your own custom methods or override and customize base methods. +> `CrudAppService` has different versions gets different number of generic arguments. Use the one suitable for you. + +### AbstractKeyCrudAppService + +`CrudAppService` requires to have an Id property as the primary key of your entity. If you are using composite keys then you can not utilize it. + +`AbstractKeyCrudAppService` implements the same `ICrudAppService` interface, but this time without making assumption about your primary key. + +#### Example + +Assume that you have a `District` entity with `CityId` and `Name` as a composite primary key. Using `AbstractKeyCrudAppService` requires to implement `DeleteByIdAsync` and `GetEntityByIdAsync` methods yourself: + +````csharp +public class DistrictAppService + : AbstractKeyCrudAppService +{ + public DistrictAppService(IRepository repository) + : base(repository) + { + } + + protected override async Task DeleteByIdAsync(DistrictKey id) + { + await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name); + } + + protected override async Task GetEntityByIdAsync(DistrictKey id) + { + return await AsyncQueryableExecuter.FirstOrDefaultAsync( + Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name) + ); + } +} +```` + +This implementation requires you to create a class represents your composite key: + +````csharp +public class DistrictKey +{ + public Guid CityId { get; set; } + + public string Name { get; set; } +} +```` + ## Lifetime Lifetime of application services are [transient](Dependency-Injection.md) and they are automatically registered to the dependency injection system. diff --git a/docs/en/CLI.md b/docs/en/CLI.md index b4f97b6d8f..13110c5ae5 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -141,7 +141,7 @@ abp switch-to-preview [options] ```` #### Options -`--solution-path` or `-sp`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory. +`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory. ### switch-to-stable @@ -154,7 +154,7 @@ abp switch-to-stable [options] ```` #### Options -`--solution-path` or `-sp`: Specifies the solution (.sln) file path. If not specified, CLI tries to find a .sln file in the current directory. +`--solution-directory` or `-sd`: Specifies the directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory. ### login diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index 4ee77d065c..1e1e4e9f81 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -302,7 +302,7 @@ TODO:... Volo.Abp.AspNetCore.MultiTenancy package adds following tenant resolvers to determine current tenant from current web request (ordered by priority). These resolvers are added and work out of the box: -* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be stay as the first contributor for security**. +* **CurrentUserTenantResolveContributor**: Gets the tenant id from claims of the current user, if the current user has logged in. **This should always be the first contributor for security**. * **QueryStringTenantResolver**: Tries to find current tenant id from query string parameter. Parameter name is "__tenant" by default. * **RouteTenantResolver**: Tries to find current tenant id from route (URL path). Variable name is "__tenant" by default. So, if you defined a route with this variable, then it can determine the current tenant from the route. * **HeaderTenantResolver**: Tries to find current tenant id from HTTP header. Header name is "__tenant" by default. @@ -343,8 +343,10 @@ namespace MyCompany.MyProject { Configure(options => { - //Subdomain format: {0}.mydomain.com (adding as the highest priority resolver) - options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com")); + //Subdomain format: {0}.mydomain.com + //Adding as the second highest priority resolver after 'CurrentUserTenantResolveContributor' to + //ensure the user cannot impersonate a different tenant. + options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com")); }); //... @@ -355,7 +357,7 @@ namespace MyCompany.MyProject {0} is the the placeholder to determine current tenant's unique name. -Instead of ``options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));`` you can use this shortcut: +Instead of ``options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));`` you can use this shortcut: ````C# options.AddDomainTenantResolver("{0}.mydomain.com"); diff --git a/docs/zh-Hans/Multi-Tenancy.md b/docs/zh-Hans/Multi-Tenancy.md index 1cfa2996d2..8913c27ede 100644 --- a/docs/zh-Hans/Multi-Tenancy.md +++ b/docs/zh-Hans/Multi-Tenancy.md @@ -343,8 +343,8 @@ namespace MyCompany.MyProject { Configure(options => { - //子域名格式: {0}.mydomain.com (作为最高优先级解析器添加) - options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com")); + //子域名格式: {0}.mydomain.com (作为第二优先级解析器添加, 位于CurrentUserTenantResolveContributor之后) + options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com")); }); //... @@ -355,7 +355,7 @@ namespace MyCompany.MyProject {0}是用来确定当前租户唯一名称的占位符. -你可以使用下面的方法,代替``options.TenantResolvers.Insert(0, new DomainTenantResolver("{0}.mydomain.com"));``: +你可以使用下面的方法,代替``options.TenantResolvers.Insert(1, new DomainTenantResolver("{0}.mydomain.com"));``: ````C# options.AddDomainTenantResolver("{0}.mydomain.com"); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml index 13d438441d..9dd98b5fda 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml @@ -23,6 +23,7 @@ Layout = null; AbpAntiForgeryManager.SetCookie(); var containerClass = ViewBag.FluidLayout == true ? "container-fluid" : "container"; //TODO: Better and type-safe options + } @@ -37,6 +38,8 @@ @(ViewBag.Title == null ? BrandingProvider.AppName : ViewBag.Title) + + @await RenderSectionAsync("styles", false) @@ -54,32 +57,32 @@ @if (MultiTenancyOptions.Value.IsEnabled && - (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true)) - { -
-
-
-
- @MultiTenancyStringLocalizer["Tenant"]
-
- @if (CurrentTenant.Id == null) - { - - @MultiTenancyStringLocalizer["NotSelected"] - - } - else - { - @(CurrentTenant.Name ?? CurrentTenant.Id.Value.ToString()) - } -
-
-
- @MultiTenancyStringLocalizer["Switch"] + (TenantResolveResultAccessor.Result?.AppliedResolvers?.Contains(CookieTenantResolveContributor.ContributorName) == true)) + { +
+
+
+
+ @MultiTenancyStringLocalizer["Tenant"]
+
+ @if (CurrentTenant.Id == null) + { + + @MultiTenancyStringLocalizer["NotSelected"] + + } + else + { + @(CurrentTenant.Name ?? CurrentTenant.Id.Value.ToString()) + } +
+
+
-
} @(await Component.InvokeAsync()) @RenderBody() diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs index 9875ccd808..0254a6022b 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs @@ -12,6 +12,7 @@ using Volo.Abp.Application.Services; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Reflection; namespace Volo.Abp.AspNetCore.Mvc.Conventions @@ -81,7 +82,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions continue; } - if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType)) + if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType, includeEnums: true)) { if (CanUseFormBodyBinding(action, prm)) { @@ -94,7 +95,15 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions protected virtual bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter) { - if (_options.ConventionalControllers.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) + //We want to use "id" as path parameter, not body! + if (parameter.ParameterName == "id") + { + return false; + } + + if (_options.ConventionalControllers + .FormBodyBindingIgnoredTypes + .Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) { return false; } @@ -251,7 +260,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions if (!selector.ActionConstraints.OfType().Any()) { - selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] {httpMethod})); + selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod })); } } } @@ -295,9 +304,24 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions var url = $"api/{rootPath}/{controllerNameInUrl.ToCamelCase()}"; //Add {id} path if needed - if (action.Parameters.Any(p => p.ParameterName == "id")) + var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id"); + if (idParameterModel != null) { - url += "/{id}"; + if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true)) + { + url += "/{id}"; + } + else + { + var properties = idParameterModel + .ParameterType + .GetProperties(BindingFlags.Instance | BindingFlags.Public); + + foreach (var property in properties) + { + url += "/{" + property.Name + "}"; + } + } } //Add action name if needed @@ -341,7 +365,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions protected virtual string NormalizeUrlControllerName(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration) { - if(configuration?.UrlControllerNameNormalizer == null) + if (configuration?.UrlControllerNameNormalizer == null) { return controllerName; } @@ -364,7 +388,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions protected virtual bool IsEmptySelector(SelectorModel selector) { - return selector.AttributeRouteModel == null + return selector.AttributeRouteModel == null && selector.ActionConstraints.IsNullOrEmpty() && selector.EndpointMetadata.IsNullOrEmpty(); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs index 9f0862258f..883ac4db2f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchNightlyPreviewCommand.cs @@ -29,7 +29,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(" abp switch-to-preview [options]"); sb.AppendLine(""); sb.AppendLine("Options:"); - sb.AppendLine("-sp|--solution-path"); + sb.AppendLine("-sd|--solution-directory"); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs index 333583e96d..54db7bc082 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/SwitchStableCommand.cs @@ -29,7 +29,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(" abp switch-to-stable [options]"); sb.AppendLine(""); sb.AppendLine("Options:"); - sb.AppendLine("-sp|--solution-path"); + sb.AppendLine("-sd|--solution-directory"); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs index 3cdd658403..0b70ae6cca 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs @@ -95,21 +95,19 @@ namespace Volo.Abp.Cli.NuGet .OrResult(msg => !msg.IsSuccessStatusCode) .WaitAndRetryAsync(new[] { - TimeSpan.FromSeconds(1), - TimeSpan.FromSeconds(3), - TimeSpan.FromSeconds(7) + TimeSpan.FromSeconds(1) }, (responseMessage, timeSpan, retryCount, context) => { if (responseMessage.Exception != null) { - Logger.LogWarning( + Logger.LogDebug( $"{retryCount}. HTTP request attempt failed to {url} with an error: HTTP {(int)responseMessage.Result.StatusCode}-{responseMessage.Exception.Message}. " + $"Waiting {timeSpan.TotalSeconds} secs for the next try..."); } else if (responseMessage.Result != null) { - Logger.LogWarning( + Logger.LogDebug( $"{retryCount}. HTTP request attempt failed to {url} with an error: {(int)responseMessage.Result.StatusCode}-{responseMessage.Result.ReasonPhrase}. " + $"Waiting {timeSpan.TotalSeconds} secs for the next try..."); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs index e860a68ba8..f6c89be039 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/PackageSourceSwitcher.cs @@ -31,55 +31,74 @@ namespace Volo.Abp.Cli.ProjectModification { _packageSourceAdder.Add("ABP Nightly", "https://www.myget.org/F/abp-nightly/api/v3/index.json"); + var solutionPath = GetSolutionPath(commandLineArgs); + var solutionFolder = GetSolutionFolder(commandLineArgs); + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( - GetSolutionPath(commandLineArgs), + solutionPath, true); await _npmPackagesUpdater.Update( - Path.GetFileName(GetSolutionPath(commandLineArgs)), + solutionFolder, true); } public async Task SwitchToStable(CommandLineArgs commandLineArgs) { + var solutionPath = GetSolutionPath(commandLineArgs); + var solutionFolder = GetSolutionFolder(commandLineArgs); + await _nugetPackagesVersionUpdater.UpdateSolutionAsync( - GetSolutionPath(commandLineArgs), + solutionPath, false, true); await _npmPackagesUpdater.Update( - Path.GetFileName(GetSolutionPath(commandLineArgs)), - false, + solutionFolder, + false, true); } - private string GetSolutionPath(CommandLineArgs commandLineArgs) { - var solutionPath = commandLineArgs.Options.GetOrNull(Options.SolutionPath.Short, Options.SolutionPath.Long); + var directory = commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long) + ?? Directory.GetCurrentDirectory(); + + var solutionPath = Directory.GetFiles(directory, "*.sln").FirstOrDefault(); if (solutionPath == null) { - try - { - solutionPath = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.sln").Single(); - } - catch (Exception) + var subDirectories = Directory.GetDirectories(directory); + + foreach (var subDirectory in subDirectories) { - Logger.LogError("There is no solution or more that one solution in current directory."); - throw; + var slnInSubDirectory = Directory.GetFiles(subDirectory, "*.sln").FirstOrDefault(); + + if (slnInSubDirectory != null) + { + return Path.Combine(subDirectory, slnInSubDirectory); + } } + + Logger.LogError("There is no solution or more that one solution in current directory."); + return null; } return solutionPath; } + private string GetSolutionFolder(CommandLineArgs commandLineArgs) + { + return commandLineArgs.Options.GetOrNull(Options.SolutionDirectory.Short, Options.SolutionDirectory.Long) + ?? Directory.GetCurrentDirectory(); + } + public static class Options { - public static class SolutionPath + public static class SolutionDirectory { - public const string Short = "sp"; - public const string Long = "solution-path"; + public const string Short = "sd"; + public const string Long = "solution-directory"; } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs index dcb36d7bae..e369690785 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs @@ -107,14 +107,18 @@ namespace Volo.Abp.Cli.ProjectModification private async Task DownloadSourceCodesToSolutionFolder(ModuleWithMastersInfo module, string modulesFolderInSolution, string version = null) { + var targetModuleFolder = Path.Combine(modulesFolderInSolution, module.Name); + await SourceCodeDownloadService.DownloadAsync( module.Name, - Path.Combine(modulesFolderInSolution, module.Name), + targetModuleFolder, version, null, null ); + await DeleteAppFolderAsync(targetModuleFolder); + if (module.MasterModuleInfos == null) { return; @@ -126,6 +130,15 @@ namespace Volo.Abp.Cli.ProjectModification } } + private async Task DeleteAppFolderAsync(string targetModuleFolder) + { + var appFolder = Path.Combine(targetModuleFolder, "app"); + if (Directory.Exists(appFolder)) + { + Directory.Delete(appFolder, true); + } + } + private async Task AddNugetAndNpmReferences(ModuleWithMastersInfo module, string[] projectFiles) { foreach (var nugetPackage in module.NugetPackages) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs index da8ded6a20..587492804b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/VoloNugetPackagesVersionUpdater.cs @@ -106,7 +106,7 @@ namespace Volo.Abp.Cli.ProjectModification } else { - Logger.LogDebug("Package: \"{0}-v{1}\" is up to date.", packageId, packageVersion); + Logger.LogInformation("Package: \"{0}-v{1}\" is up to date.", packageId, packageVersion); } } } diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs index 8eefc78f9b..2ccf9bb9a1 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs @@ -14,6 +14,7 @@ namespace Volo.Abp.Cli .MinimumLevel.Information() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning) + .MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning) #if DEBUG .MinimumLevel.Override("Volo.Abp.Cli", LogEventLevel.Debug) #else diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs index 7816bd20eb..12f3b51d11 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Services/ICrudAppService.cs @@ -5,36 +5,30 @@ namespace Volo.Abp.Application.Services { public interface ICrudAppService : ICrudAppService - where TEntityDto : IEntityDto { } public interface ICrudAppService : ICrudAppService - where TEntityDto : IEntityDto { } public interface ICrudAppService : ICrudAppService - where TEntityDto : IEntityDto { } public interface ICrudAppService : ICrudAppService - where TEntityDto : IEntityDto { } public interface ICrudAppService : IApplicationService - where TGetOutputDto : IEntityDto - where TGetListOutputDto : IEntityDto { Task GetAsync(TKey id); diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs new file mode 100644 index 0000000000..45915826e4 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AbstractKeyCrudAppService.cs @@ -0,0 +1,341 @@ +using System; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Linq; +using Volo.Abp.MultiTenancy; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Application.Services +{ + public abstract class AbstractKeyCrudAppService + : AbstractKeyCrudAppService + where TEntity : class, IEntity + { + protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) + { + + } + } + + public abstract class AbstractKeyCrudAppService + : AbstractKeyCrudAppService + where TEntity : class, IEntity + { + protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) + { + + } + } + + public abstract class AbstractKeyCrudAppService + : AbstractKeyCrudAppService + where TEntity : class, IEntity + { + protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) + { + + } + } + + public abstract class AbstractKeyCrudAppService + : AbstractKeyCrudAppService + where TEntity : class, IEntity + { + protected AbstractKeyCrudAppService(IRepository repository) + : base(repository) + { + + } + + protected override TEntityDto MapToGetListOutputDto(TEntity entity) + { + return MapToGetOutputDto(entity); + } + } + + public abstract class AbstractKeyCrudAppService + : ApplicationService, + ICrudAppService + where TEntity : class, IEntity + { + public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; } + + protected IRepository Repository { get; } + + protected virtual string GetPolicyName { get; set; } + + protected virtual string GetListPolicyName { get; set; } + + protected virtual string CreatePolicyName { get; set; } + + protected virtual string UpdatePolicyName { get; set; } + + protected virtual string DeletePolicyName { get; set; } + + protected AbstractKeyCrudAppService(IRepository repository) + { + Repository = repository; + AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance; + } + + public virtual async Task GetAsync(TKey id) + { + await CheckGetPolicyAsync(); + + var entity = await GetEntityByIdAsync(id); + return MapToGetOutputDto(entity); + } + + public virtual async Task> GetListAsync(TGetListInput input) + { + await CheckGetListPolicyAsync(); + + var query = CreateFilteredQuery(input); + + var totalCount = await AsyncQueryableExecuter.CountAsync(query); + + query = ApplySorting(query, input); + query = ApplyPaging(query, input); + + var entities = await AsyncQueryableExecuter.ToListAsync(query); + + return new PagedResultDto( + totalCount, + entities.Select(MapToGetListOutputDto).ToList() + ); + } + + public virtual async Task CreateAsync(TCreateInput input) + { + await CheckCreatePolicyAsync(); + + var entity = MapToEntity(input); + + TryToSetTenantId(entity); + + await Repository.InsertAsync(entity, autoSave: true); + + return MapToGetOutputDto(entity); + } + + public virtual async Task UpdateAsync(TKey id, TUpdateInput input) + { + await CheckUpdatePolicyAsync(); + + var entity = await GetEntityByIdAsync(id); + //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise + MapToEntity(input, entity); + await Repository.UpdateAsync(entity, autoSave: true); + + return MapToGetOutputDto(entity); + } + + public virtual async Task DeleteAsync(TKey id) + { + await CheckDeletePolicyAsync(); + + await DeleteByIdAsync(id); + } + + protected abstract Task DeleteByIdAsync(TKey id); + + protected abstract Task GetEntityByIdAsync(TKey id); + + protected virtual async Task CheckGetPolicyAsync() + { + await CheckPolicyAsync(GetPolicyName); + } + + protected virtual async Task CheckGetListPolicyAsync() + { + await CheckPolicyAsync(GetListPolicyName); + } + + protected virtual async Task CheckCreatePolicyAsync() + { + await CheckPolicyAsync(CreatePolicyName); + } + + protected virtual async Task CheckUpdatePolicyAsync() + { + await CheckPolicyAsync(UpdatePolicyName); + } + + protected virtual async Task CheckDeletePolicyAsync() + { + await CheckPolicyAsync(DeletePolicyName); + } + + /// + /// Should apply sorting if needed. + /// + /// The query. + /// The input. + protected virtual IQueryable ApplySorting(IQueryable query, TGetListInput input) + { + //Try to sort query if available + if (input is ISortedResultRequest sortInput) + { + if (!sortInput.Sorting.IsNullOrWhiteSpace()) + { + return query.OrderBy(sortInput.Sorting); + } + } + + //IQueryable.Task requires sorting, so we should sort if Take will be used. + if (input is ILimitedResultRequest) + { + return ApplyDefaultSorting(query); + } + + //No sorting + return query; + } + + /// + /// Applies sorting if no sorting specified but a limited result requested. + /// + /// The query. + protected virtual IQueryable ApplyDefaultSorting(IQueryable query) + { + if (typeof(TEntity).IsAssignableTo()) + { + return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + } + + throw new AbpException("No sorting specified but this query requires sorting. Override the ApplyDefaultSorting method for your application service derived from AbstractKeyCrudAppService!"); + } + + /// + /// Should apply paging if needed. + /// + /// The query. + /// The input. + protected virtual IQueryable ApplyPaging(IQueryable query, TGetListInput input) + { + //Try to use paging if available + if (input is IPagedResultRequest pagedInput) + { + return query.PageBy(pagedInput); + } + + //Try to limit query result if available + if (input is ILimitedResultRequest limitedInput) + { + return query.Take(limitedInput.MaxResultCount); + } + + //No paging + return query; + } + + /// + /// This method should create based on given input. + /// It should filter query if needed, but should not do sorting or paging. + /// Sorting should be done in and paging should be done in + /// methods. + /// + /// The input. + protected virtual IQueryable CreateFilteredQuery(TGetListInput input) + { + return Repository; + } + + /// + /// Maps to . + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity) + { + return ObjectMapper.Map(entity); + } + + /// + /// Maps to . + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity) + { + return ObjectMapper.Map(entity); + } + + /// + /// Maps to to create a new entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual TEntity MapToEntity(TCreateInput createInput) + { + var entity = ObjectMapper.Map(createInput); + SetIdForGuids(entity); + return entity; + } + + /// + /// Sets Id value for the entity if is . + /// It's used while creating a new entity. + /// + protected virtual void SetIdForGuids(TEntity entity) + { + var entityWithGuidId = entity as IEntity; + + if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty) + { + return; + } + + EntityHelper.TrySetId( + entityWithGuidId, + () => GuidGenerator.Create(), + true + ); + } + + /// + /// Maps to to update the entity. + /// It uses by default. + /// It can be overriden for custom mapping. + /// + protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity) + { + ObjectMapper.Map(updateInput, entity); + } + + protected virtual void TryToSetTenantId(TEntity entity) + { + if (entity is IMultiTenant && HasTenantIdProperty(entity)) + { + var tenantId = CurrentTenant.Id; + + if (!tenantId.HasValue) + { + return; + } + + var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)); + + if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null) + { + return; + } + + propertyInfo.SetValue(entity, tenantId); + } + } + + protected virtual bool HasTenantIdProperty(TEntity entity) + { + return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs index 4134b59a4c..a2af0592a4 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppService.cs @@ -1,13 +1,10 @@ using System; using System.Linq; -using System.Linq.Dynamic.Core; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Linq; -using Volo.Abp.MultiTenancy; -using Volo.Abp.ObjectMapping; namespace Volo.Abp.Application.Services { @@ -65,274 +62,49 @@ namespace Volo.Abp.Application.Services } public abstract class CrudAppService - : ApplicationService, - ICrudAppService - where TEntity : class, IEntity + : AbstractKeyCrudAppService + where TEntity : class, IEntity where TGetOutputDto : IEntityDto where TGetListOutputDto : IEntityDto { - public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; } - - protected IRepository Repository { get; } - - protected virtual string GetPolicyName { get; set; } - - protected virtual string GetListPolicyName { get; set; } - - protected virtual string CreatePolicyName { get; set; } - - protected virtual string UpdatePolicyName { get; set; } - - protected virtual string DeletePolicyName { get; set; } + protected new IRepository Repository { get; } protected CrudAppService(IRepository repository) + : base(repository) { Repository = repository; - AsyncQueryableExecuter = DefaultAsyncQueryableExecuter.Instance; } - public virtual async Task GetAsync(TKey id) + protected override async Task DeleteByIdAsync(TKey id) { - await CheckGetPolicyAsync(); - - var entity = await GetEntityByIdAsync(id); - return MapToGetOutputDto(entity); - } - - public virtual async Task> GetListAsync(TGetListInput input) - { - await CheckGetListPolicyAsync(); - - var query = CreateFilteredQuery(input); - - var totalCount = await AsyncQueryableExecuter.CountAsync(query); - - query = ApplySorting(query, input); - query = ApplyPaging(query, input); - - var entities = await AsyncQueryableExecuter.ToListAsync(query); - - return new PagedResultDto( - totalCount, - entities.Select(MapToGetListOutputDto).ToList() - ); - } - - public virtual async Task CreateAsync(TCreateInput input) - { - await CheckCreatePolicyAsync(); - - var entity = MapToEntity(input); - - TryToSetTenantId(entity); - - await Repository.InsertAsync(entity, autoSave: true); - - return MapToGetOutputDto(entity); - } - - public virtual async Task UpdateAsync(TKey id, TUpdateInput input) - { - await CheckUpdatePolicyAsync(); - - var entity = await GetEntityByIdAsync(id); - //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise - MapToEntity(input, entity); - await Repository.UpdateAsync(entity, autoSave: true); - - return MapToGetOutputDto(entity); - } - - public virtual async Task DeleteAsync(TKey id) - { - await CheckDeletePolicyAsync(); - await Repository.DeleteAsync(id); } - protected virtual Task GetEntityByIdAsync(TKey id) - { - return Repository.GetAsync(id); - } - - protected virtual async Task CheckGetPolicyAsync() - { - await CheckPolicyAsync(GetPolicyName); - } - - protected virtual async Task CheckGetListPolicyAsync() - { - await CheckPolicyAsync(GetListPolicyName); - } - - protected virtual async Task CheckCreatePolicyAsync() - { - await CheckPolicyAsync(CreatePolicyName); - } - - protected virtual async Task CheckUpdatePolicyAsync() - { - await CheckPolicyAsync(UpdatePolicyName); - } - - protected virtual async Task CheckDeletePolicyAsync() + protected override async Task GetEntityByIdAsync(TKey id) { - await CheckPolicyAsync(DeletePolicyName); + return await Repository.GetAsync(id); } - /// - /// Should apply sorting if needed. - /// - /// The query. - /// The input. - protected virtual IQueryable ApplySorting(IQueryable query, TGetListInput input) - { - //Try to sort query if available - if (input is ISortedResultRequest sortInput) - { - if (!sortInput.Sorting.IsNullOrWhiteSpace()) - { - return query.OrderBy(sortInput.Sorting); - } - } - - //IQueryable.Task requires sorting, so we should sort if Take will be used. - if (input is ILimitedResultRequest) - { - return query.OrderByDescending(e => e.Id); - } - - //No sorting - return query; - } - - /// - /// Should apply paging if needed. - /// - /// The query. - /// The input. - protected virtual IQueryable ApplyPaging(IQueryable query, TGetListInput input) - { - //Try to use paging if available - if (input is IPagedResultRequest pagedInput) - { - return query.PageBy(pagedInput); - } - - //Try to limit query result if available - if (input is ILimitedResultRequest limitedInput) - { - return query.Take(limitedInput.MaxResultCount); - } - - //No paging - return query; - } - - /// - /// This method should create based on given input. - /// It should filter query if needed, but should not do sorting or paging. - /// Sorting should be done in and paging should be done in - /// methods. - /// - /// The input. - protected virtual IQueryable CreateFilteredQuery(TGetListInput input) - { - return Repository; - } - - /// - /// Maps to . - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity) - { - return ObjectMapper.Map(entity); - } - - /// - /// Maps to . - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity) - { - return ObjectMapper.Map(entity); - } - - /// - /// Maps to to create a new entity. - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual TEntity MapToEntity(TCreateInput createInput) - { - var entity = ObjectMapper.Map(createInput); - SetIdForGuids(entity); - return entity; - } - - /// - /// Sets Id value for the entity if is . - /// It's used while creating a new entity. - /// - protected virtual void SetIdForGuids(TEntity entity) - { - var entityWithGuidId = entity as IEntity; - - if (entityWithGuidId == null || entityWithGuidId.Id != Guid.Empty) - { - return; - } - - EntityHelper.TrySetId( - entityWithGuidId, - () => GuidGenerator.Create(), - true - ); - } - - /// - /// Maps to to update the entity. - /// It uses by default. - /// It can be overriden for custom mapping. - /// - protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity) + protected override void MapToEntity(TUpdateInput updateInput, TEntity entity) { if (updateInput is IEntityDto entityDto) { entityDto.Id = entity.Id; } - ObjectMapper.Map(updateInput, entity); + base.MapToEntity(updateInput, entity); } - protected virtual void TryToSetTenantId(TEntity entity) + protected override IQueryable ApplyDefaultSorting(IQueryable query) { - if (entity is IMultiTenant && HasTenantIdProperty(entity)) + if (typeof(TEntity).IsAssignableTo()) { - var tenantId = CurrentTenant.Id; - - if (!tenantId.HasValue) - { - return; - } - - var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)); - - if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null) - { - return; - } - - propertyInfo.SetValue(entity, tenantId); + return query.OrderByDescending(e => ((ICreationAuditedObject)e).CreationTime); + } + else + { + return query.OrderByDescending(e => e.Id); } - } - - protected virtual bool HasTenantIdProperty(TEntity entity) - { - return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null; } } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs index a33c5f56ec..2b38059ef8 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityHelper.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; -using Volo.Abp.MultiTenancy; -using Volo.Abp.Reflection; namespace Volo.Abp.Domain.Entities { @@ -13,12 +11,24 @@ namespace Volo.Abp.Domain.Entities /// public static class EntityHelper { - public static bool IsEntity([NotNull] Type type) { return typeof(IEntity).IsAssignableFrom(type); } + public static bool IsEntityWithId([NotNull] Type type) + { + foreach (var interfaceType in type.GetInterfaces()) + { + if (interfaceType.GetTypeInfo().IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEntity<>)) + { + return true; + } + } + + return false; + } + public static bool HasDefaultId(IEntity entity) { if (EqualityComparer.Default.Equals(entity.Id, default)) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs index 510d0384c2..85466ccf0e 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/EntityNotFoundException.cs @@ -47,7 +47,11 @@ namespace Volo.Abp.Domain.Entities /// Creates a new object. /// public EntityNotFoundException(Type entityType, object id, Exception innerException) - : base($"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", innerException) + : base( + id == null + ? $"There is no such an entity given given id. Entity type: {entityType.FullName}" + : $"There is no such an entity. Entity type: {entityType.FullName}, id: {id}", + innerException) { EntityType = entityType; Id = id; diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs index 2ac16ec229..3bda51bd2e 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/IRepository.cs @@ -18,6 +18,34 @@ namespace Volo.Abp.Domain.Repositories public interface IRepository : IReadOnlyRepository, IBasicRepository where TEntity : class, IEntity { + /// + /// Get a single entity by the given . + /// It returns null if no entity with the given . + /// It throws if there are multiple entities with the given . + /// + /// A condition to find the entity + /// Set true to include all children of this entity + /// A to observe while waiting for the task to complete. + Task FindAsync( + [NotNull] Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + + /// + /// Get a single entity by the given . + /// It throws if there is no entity with the given . + /// It throws if there are multiple entities with the given . + /// + /// A condition to filter entities + /// Set true to include all children of this entity + /// A to observe while waiting for the task to complete. + Task GetAsync( + [NotNull] Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + /// /// Deletes many entities by function. /// Notice that: All entities fits to given predicate are retrieved and deleted. @@ -30,7 +58,11 @@ namespace Volo.Abp.Domain.Repositories /// This is useful for ORMs / database APIs those only save changes with an explicit method call, but you need to immediately save changes to the database. /// /// A to observe while waiting for the task to complete. - Task DeleteAsync([NotNull] Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default); + Task DeleteAsync( + [NotNull] Expression> predicate, + bool autoSave = false, + CancellationToken cancellationToken = default + ); } public interface IRepository : IRepository, IReadOnlyRepository, IBasicRepository diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs index 3ade403017..bd7e486d29 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Repositories/RepositoryBase.cs @@ -49,6 +49,26 @@ namespace Volo.Abp.Domain.Repositories protected abstract IQueryable GetQueryable(); + public abstract Task FindAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default); + + public async Task GetAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + var entity = await FindAsync(predicate, includeDetails, cancellationToken); + + if (entity == null) + { + throw new EntityNotFoundException(typeof(TEntity)); + } + + return entity; + } + public abstract Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default); protected virtual TQueryable ApplyDataFilters(TQueryable query) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index 106e00f77a..a48e5d7da5 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -93,6 +93,20 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return DbSet.AsQueryable(); } + public override async Task FindAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return includeDetails + ? await WithDetails() + .Where(predicate) + .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)) + : await DbSet + .Where(predicate) + .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)); + } + public override async Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) { var entities = await GetQueryable() @@ -173,18 +187,6 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore } - public virtual TEntity Get(TKey id, bool includeDetails = true) - { - var entity = Find(id, includeDetails); - - if (entity == null) - { - throw new EntityNotFoundException(typeof(TEntity), id); - } - - return entity; - } - public virtual async Task GetAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default) { var entity = await FindAsync(id, includeDetails, GetCancellationToken(cancellationToken)); @@ -197,13 +199,6 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore return entity; } - public virtual TEntity Find(TKey id, bool includeDetails = true) - { - return includeDetails - ? WithDetails().FirstOrDefault(e => e.Id.Equals(id)) - : DbSet.Find(id); - } - public virtual async Task FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default) { return includeDetails diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs index b81aa7ae87..2457c3c453 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs @@ -31,6 +31,14 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb return ApplyDataFilters(Collection.AsQueryable()); } + public override Task FindAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return Task.FromResult(Collection.AsQueryable().Where(predicate).SingleOrDefault()); + } + public override Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) { var entities = Collection.AsQueryable().Where(predicate).ToList(); diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 3333f1d505..5533cb9f74 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -169,6 +169,16 @@ namespace Volo.Abp.Domain.Repositories.MongoDB return GetMongoQueryable(); } + public override async Task FindAsync( + Expression> predicate, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .Where(predicate) + .SingleOrDefaultAsync(GetCancellationToken(cancellationToken)); + } + public virtual IMongoQueryable GetMongoQueryable() { return ApplyDataFilters( diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs index 7e1dd24ae6..067f4b6a01 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo/Abp/Domain/Repositories/RepositoryRegistration_Tests.cs @@ -246,6 +246,11 @@ namespace Volo.Abp.Domain.Repositories throw new NotImplementedException(); } + public override Task FindAsync(Expression> predicate, bool includeDetails = true, CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + public override Task DeleteAsync(Expression> predicate, bool autoSave = false, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs new file mode 100644 index 0000000000..81052d17cc --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictAppService.cs @@ -0,0 +1,30 @@ +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Application.Dto; + +namespace Volo.Abp.TestApp.Application +{ + //This is especially used to test the AbstractKeyCrudAppService + public class DistrictAppService : AbstractKeyCrudAppService + { + public DistrictAppService(IRepository repository) + : base(repository) + { + } + + protected override async Task DeleteByIdAsync(DistrictKey id) + { + await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name); + } + + protected override async Task GetEntityByIdAsync(DistrictKey id) + { + return await AsyncQueryableExecuter.FirstOrDefaultAsync( + Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name) + ); + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs new file mode 100644 index 0000000000..a7d5c9b965 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/DistrictKey.cs @@ -0,0 +1,11 @@ +using System; + +namespace Volo.Abp.TestApp.Application +{ + public class DistrictKey + { + public Guid CityId { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs new file mode 100644 index 0000000000..3b5f3e5f83 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/DistrictDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.TestApp.Application.Dto +{ + public class DistrictDto : EntityDto + { + public Guid CityId { get; set; } + + public string Name { get; set; } + + public int Population { get; set; } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs index 6d1be41f31..03e4ed5465 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs @@ -41,7 +41,7 @@ namespace Volo.Abp.TestApp { var istanbul = new City(IstanbulCityId, "Istanbul"); istanbul.Districts.Add(new District(istanbul.Id, "Bakirkoy", 1283999)); - istanbul.Districts.Add(new District(istanbul.Id, "Mecidiyek�y", 2222321)); + istanbul.Districts.Add(new District(istanbul.Id, "Mecidiyekoy", 2222321)); istanbul.Districts.Add(new District(istanbul.Id, "Uskudar", 726172)); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Tokyo")); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs index 07e6211c09..28c94b02fc 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Basic_Tests.cs @@ -28,6 +28,14 @@ namespace Volo.Abp.TestApp.Testing person.Phones.Count.ShouldBe(2); } + [Fact] + public async Task GetAsync_With_Predicate() + { + var person = await PersonRepository.GetAsync(p => p.Name == "Douglas"); + person.Name.ShouldBe("Douglas"); + person.Phones.Count.ShouldBe(2); + } + [Fact] public async Task FindAsync_Should_Return_Null_For_Not_Found_Entity() { @@ -35,6 +43,14 @@ namespace Volo.Abp.TestApp.Testing person.ShouldBeNull(); } + [Fact] + public async Task FindAsync_Should_Return_Null_For_Not_Found_Entity_With_Predicate() + { + var randomName = Guid.NewGuid().ToString(); + var person = await PersonRepository.FindAsync(p => p.Name == randomName); + person.ShouldBeNull(); + } + [Fact] public async Task DeleteAsync() { diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 2ab190b724..a38a51a16c 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -32,4 +32,8 @@ + + + + diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml index 8c0b63d998..5019c8a29e 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Index.cshtml @@ -7,7 +7,7 @@ @inject IAuthorizationService Authorization @model Volo.Blogging.Pages.Blog.Posts.IndexModel @{ - ViewBag.PageTitle = "Blog"; + ViewBag.Title = "Blog"; } @section scripts { diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs index 0f573634a4..9381a9b2c8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.Identity { public interface IIdentityRoleAppService : IApplicationService { + Task> GetAllListAsync(); + Task> GetListAsync(PagedAndSortedResultRequestDto input); Task CreateAsync(IdentityRoleCreateDto input); diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs index ffc13fdbca..b02319bc24 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs @@ -27,6 +27,13 @@ namespace Volo.Abp.Identity await _roleManager.GetByIdAsync(id)); } + public virtual async Task> GetAllListAsync() + { + var list = await _roleRepository.GetListAsync(); + return new ListResultDto( + ObjectMapper.Map, List>(list)); + } + public virtual async Task> GetListAsync(PagedAndSortedResultRequestDto input) { var list = await _roleRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount); diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index 025bb7fd87..d6d6ca3983 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -82,6 +82,7 @@ "DisplayName:Abp.Identity.Lockout.LockoutDuration": "Lockout duration(seconds)", "DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts": "Max failed access attempts", "DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail": "Require confirmed email", + "DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Enable phone number confirmation", "DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Require confirmed phoneNumber", "DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled": "Is username update enabled", "DisplayName:Abp.Identity.User.IsEmailUpdateEnabled": "Is email update enabled", @@ -95,6 +96,7 @@ "Description:Abp.Identity.Lockout.LockoutDuration": "The duration a user is locked out for when a lockout occurs.", "Description:Abp.Identity.Lockout.MaxFailedAccessAttempts": "The number of failed access attempts allowed before a user is locked out, assuming lock out is enabled.", "Description:Abp.Identity.SignIn.RequireConfirmedEmail": "Whether a confirmed email address is required to sign in.", + "Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Whether the phoneNumber can be confirmed by the user.", "Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Whether a confirmed telephone number is required to sign in.", "Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Whether the username can be updated by the user.", "Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user." diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs index babeb4fb77..9c07f6c55b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs @@ -30,6 +30,7 @@ private const string SignInPrefix = Prefix + ".SignIn"; public const string RequireConfirmedEmail = SignInPrefix + ".RequireConfirmedEmail"; + public const string EnablePhoneNumberConfirmation = SignInPrefix + ".EnablePhoneNumberConfirmation"; public const string RequireConfirmedPhoneNumber = SignInPrefix + ".RequireConfirmedPhoneNumber"; } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs index efae6e467a..92847004ea 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs @@ -72,6 +72,11 @@ namespace Volo.Abp.Identity false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail"), L("Description:Abp.Identity.SignIn.RequireConfirmedEmail"), true), + new SettingDefinition( + IdentitySettingNames.SignIn.EnablePhoneNumberConfirmation, + true.ToString(), L("DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"), + L("Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"), + true), new SettingDefinition( IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber, false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs index 09760109d0..9925d18abb 100644 --- a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Application.Dtos; @@ -19,6 +20,13 @@ namespace Volo.Abp.Identity _roleAppService = roleAppService; } + [HttpGet] + [Route("all")] + public virtual Task> GetAllListAsync() + { + return _roleAppService.GetAllListAsync(); + } + [HttpGet] public virtual Task> GetListAsync(PagedAndSortedResultRequestDto input) { diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs index aad90e73cc..6c1ac1ff72 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs @@ -29,9 +29,9 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users { UserInfo = new UserInfoViewModel(); - var roleDtoList = await _identityRoleAppService.GetListAsync(new PagedAndSortedResultRequestDto()); + var roleDtoList = (await _identityRoleAppService.GetAllListAsync()).Items; - Roles = ObjectMapper.Map, AssignedRoleViewModel[]>(roleDtoList.Items); + Roles = ObjectMapper.Map, AssignedRoleViewModel[]>(roleDtoList); foreach (var role in Roles) { diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs index 6c8d40c14d..98a32fd1bd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs @@ -31,9 +31,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users { UserInfo = ObjectMapper.Map(await _identityUserAppService.GetAsync(id)); - Roles = ObjectMapper.Map, AssignedRoleViewModel[]>( - (await _identityRoleAppService.GetListAsync(new PagedAndSortedResultRequestDto())).Items - ); + Roles = ObjectMapper.Map, AssignedRoleViewModel[]>((await _identityRoleAppService.GetAllListAsync()).Items); var userRoleNames = (await _identityUserAppService.GetRolesAsync(UserInfo.Id)).Items.Select(r => r.Name).ToList(); foreach (var role in Roles) diff --git a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs index 663cc07786..5d64bb6c78 100644 --- a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs @@ -34,6 +34,18 @@ namespace Volo.Abp.Identity result.Id.ShouldBe(moderator.Id); } + [Fact] + public async Task GetAllListAsync() + { + //Act + + var result = await _roleAppService.GetAllListAsync(); + + //Assert + + result.Items.Count.ShouldBeGreaterThan(0); + } + [Fact] public async Task GetListAsync() { diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs index 7561835ee3..189d7d0f80 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateDto.cs @@ -1,7 +1,22 @@ -namespace Volo.Abp.TenantManagement +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.TenantManagement.Localization; + +namespace Volo.Abp.TenantManagement { public class TenantCreateDto : TenantCreateOrUpdateDtoBase { + [Required] + [EmailAddress] + [MaxLength(256)] + public string AdminEmailAddress { get; set; } + + [Required] + [MaxLength(128)] + public string AdminPassword { get; set; } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs index a5a9b4af3d..b02da79b79 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application.Contracts/Volo/Abp/TenantManagement/TenantCreateOrUpdateDtoBase.cs @@ -1,7 +1,11 @@ -namespace Volo.Abp.TenantManagement +using System.ComponentModel.DataAnnotations; + +namespace Volo.Abp.TenantManagement { public abstract class TenantCreateOrUpdateDtoBase { + [Required] + [StringLength(TenantConsts.MaxNameLength)] public string Name { get; set; } } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs index 3dabc569bd..cbe9b67c76 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Application/Volo/Abp/TenantManagement/TenantAppService.cs @@ -15,7 +15,7 @@ namespace Volo.Abp.TenantManagement protected ITenantManager TenantManager { get; } public TenantAppService( - ITenantRepository tenantRepository, + ITenantRepository tenantRepository, ITenantManager tenantManager, IDataSeeder dataSeeder) { @@ -51,10 +51,13 @@ namespace Volo.Abp.TenantManagement { //TODO: Handle database creation? - //TODO: Set admin email & password..? - await DataSeeder.SeedAsync(tenant.Id); + await DataSeeder.SeedAsync( + new DataSeedContext(tenant.Id) + .WithProperty("AdminEmail", input.AdminEmailAddress) + .WithProperty("AdminPassword", input.AdminPassword) + ); } - + return ObjectMapper.Map(tenant); } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json index fc51f00750..d6d1d7c155 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json @@ -15,6 +15,8 @@ "Permission:Edit": "Edit", "Permission:Delete": "Delete", "Permission:ManageConnectionStrings": "Manage connection strings", - "Permission:ManageFeatures": "Manage features" + "Permission:ManageFeatures": "Manage features", + "DisplayName:AdminEmailAddress": "Admin Email Address", + "DisplayName:AdminPassword": "Admin Password" } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json index 1b6bdca2c2..72b570f3f9 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json @@ -15,6 +15,8 @@ "Permission:Edit": "Düzenleme", "Permission:Delete": "Silme", "Permission:ManageConnectionStrings": "Bağlantı cümlelerini yönet", - "Permission:ManageFeatures": "Özellikleri yönet" + "Permission:ManageFeatures": "Özellikleri yönet", + "DisplayName:AdminEmailAddress": "Admin Eposta Adresi", + "DisplayName:AdminPassword": "Admin Şifresi" } } \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs index 2aabf871de..df8e513003 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.HttpApi/Volo/Abp/TenantManagement/TenantController.cs @@ -35,6 +35,7 @@ namespace Volo.Abp.TenantManagement [HttpPost] public virtual Task CreateAsync(TenantCreateDto input) { + ValidateModel(); return _service.CreateAsync(input); } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml index b3a2c9d5be..8593679a7c 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml @@ -12,7 +12,11 @@ - + + + + + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs index a602b12a65..23c71bba96 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/CreateModal.cshtml.cs @@ -31,8 +31,16 @@ namespace Volo.Abp.TenantManagement.Web.Pages.TenantManagement.Tenants { [Required] [StringLength(TenantConsts.MaxNameLength)] - [Display(Name = "DisplayName:TenantName")] public string Name { get; set; } + + [Required] + [EmailAddress] + [MaxLength(256)] + public string AdminEmailAddress { get; set; } + + [Required] + [MaxLength(128)] + public string AdminPassword { get; set; } } } } diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs index 3e44e7c8ea..b27ceff6dc 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.Application.Tests/Volo/Abp/TenantManagement/TenantAppService_Tests.cs @@ -59,7 +59,7 @@ namespace Volo.Abp.TenantManagement public async Task CreateAsync() { var tenancyName = Guid.NewGuid().ToString("N").ToLowerInvariant(); - var tenant = await _tenantAppService.CreateAsync(new TenantCreateDto { Name = tenancyName }); + var tenant = await _tenantAppService.CreateAsync(new TenantCreateDto { Name = tenancyName , AdminEmailAddress = "admin@admin.com", AdminPassword = "123456"}); tenant.Name.ShouldBe(tenancyName); tenant.Id.ShouldNotBe(default(Guid)); } @@ -69,7 +69,7 @@ namespace Volo.Abp.TenantManagement { await Assert.ThrowsAsync(async () => { - await _tenantAppService.CreateAsync(new TenantCreateDto { Name = "acme" }); + await _tenantAppService.CreateAsync(new TenantCreateDto { Name = "acme", AdminEmailAddress = "admin@admin.com", AdminPassword = "123456" }); }); } diff --git a/npm/ng-packs/apps/dev-app/src/app/shared/shared.module.ts b/npm/ng-packs/apps/dev-app/src/app/shared/shared.module.ts index 6c4c9b016c..1f268b4572 100644 --- a/npm/ng-packs/apps/dev-app/src/app/shared/shared.module.ts +++ b/npm/ng-packs/apps/dev-app/src/app/shared/shared.module.ts @@ -3,7 +3,6 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { NgModule } from '@angular/core'; import { ThemeBasicModule } from '@abp/ng.theme.basic'; import { ThemeSharedModule } from '@abp/ng.theme.shared'; -import { TableModule } from 'primeng/table'; import { NgxValidateCoreModule } from '@ngx-validate/core'; @NgModule({ @@ -12,7 +11,6 @@ import { NgxValidateCoreModule } from '@ngx-validate/core'; CoreModule, ThemeSharedModule, ThemeBasicModule, - TableModule, NgbDropdownModule, NgxValidateCoreModule, ], @@ -20,7 +18,6 @@ import { NgxValidateCoreModule } from '@ngx-validate/core'; CoreModule, ThemeSharedModule, ThemeBasicModule, - TableModule, NgbDropdownModule, NgxValidateCoreModule, ], diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index e020b3f235..93ef5c8ae7 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -21,19 +21,19 @@ "generate:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" }, "devDependencies": { - "@abp/ng.account": "~2.1.0", - "@abp/ng.account.config": "~2.1.0", - "@abp/ng.core": "~2.1.0", - "@abp/ng.feature-management": "~2.1.0", - "@abp/ng.identity": "~2.1.0", - "@abp/ng.identity.config": "~2.1.0", - "@abp/ng.permission-management": "~2.1.0", - "@abp/ng.setting-management": "~2.1.0", - "@abp/ng.setting-management.config": "~2.1.0", - "@abp/ng.tenant-management": "~2.1.0", - "@abp/ng.tenant-management.config": "~2.1.0", - "@abp/ng.theme.basic": "~2.1.0", - "@abp/ng.theme.shared": "~2.1.0", + "@abp/ng.account": "~2.2.0", + "@abp/ng.account.config": "~2.2.0", + "@abp/ng.core": "~2.2.0", + "@abp/ng.feature-management": "~2.2.0", + "@abp/ng.identity": "~2.2.0", + "@abp/ng.identity.config": "~2.2.0", + "@abp/ng.permission-management": "~2.2.0", + "@abp/ng.setting-management": "~2.2.0", + "@abp/ng.setting-management.config": "~2.2.0", + "@abp/ng.tenant-management": "~2.2.0", + "@abp/ng.tenant-management.config": "~2.2.0", + "@abp/ng.theme.basic": "~2.2.0", + "@abp/ng.theme.shared": "~2.2.0", "@angular-builders/jest": "^8.2.0", "@angular-devkit/build-angular": "~0.803.21", "@angular-devkit/build-ng-packagr": "~0.803.21", diff --git a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html index 145935f7c5..6e732540d4 100644 --- a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html +++ b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html @@ -1,8 +1,10 @@
- + + +