diff --git a/abp_io/src/Volo.Utils.SolutionTemplating/Volo/Utils/SolutionTemplating/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs b/abp_io/src/Volo.Utils.SolutionTemplating/Volo/Utils/SolutionTemplating/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs index 0901d0250b..d8a622fe83 100644 --- a/abp_io/src/Volo.Utils.SolutionTemplating/Volo/Utils/SolutionTemplating/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs +++ b/abp_io/src/Volo.Utils.SolutionTemplating/Volo/Utils/SolutionTemplating/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs @@ -21,9 +21,9 @@ namespace Volo.Utils.SolutionTemplating.Building.Steps var lines = file.GetLines(); for (var i = 0; i < lines.Length; i++) { - if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore")) + if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")) { - lines[i] = lines[i].Replace("EntityFrameworkCore", "MongoDB"); + lines[i] = lines[i].Replace("EntityFrameworkCore.DbMigrations", "MongoDB"); file.SetLines(lines); return; } diff --git a/docs/en/AspNetCore/Auto-API-Controllers.md b/docs/en/AspNetCore/Auto-API-Controllers.md index db7fb63642..b81afbcac4 100644 --- a/docs/en/AspNetCore/Auto-API-Controllers.md +++ b/docs/en/AspNetCore/Auto-API-Controllers.md @@ -1,6 +1,6 @@ # Auto API Controllers -Once you create an [application service](Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. +Once you create an [application service](../Application-Services.md), you generally want to create an API controller to expose this service as an HTTP (REST) API endpoint. A typical API controller does nothing but redirects method calls to the application service and configures the REST API using attributes like [HttpGet], [HttpPost], [Route]... etc. ABP can **automagically** configures your application services as MVC API Controllers by convention. Most of time you don't care about its detailed configuration, but it's possible fully customize it. @@ -106,7 +106,7 @@ public class PersonAppService : ApplicationService ### TypePredicate Option -You can further filter classes to become an API controller by providing the `TypePedicate` option: +You can further filter classes to become an API controller by providing the `TypePredicate` option: ````csharp services.Configure(options => diff --git a/docs/en/AspNetCore/Client-Side-Package-Management.md b/docs/en/AspNetCore/Client-Side-Package-Management.md index 3c1785daa5..0d98fe1dc5 100644 --- a/docs/en/AspNetCore/Client-Side-Package-Management.md +++ b/docs/en/AspNetCore/Client-Side-Package-Management.md @@ -22,14 +22,14 @@ The benefit of a **standard package** is: Depending on a standard package is easy. Just add it to your **package.json** file like you normally do. Example: - - { - ... - "dependencies": { - "@abp/bootstrap": "^1.0.0" - } - } - +```` +{ + ... + "dependencies": { + "@abp/bootstrap": "^1.0.0" + } +} +```` It's suggested to depend on a standard package instead of directly depending on a third-party package. diff --git a/docs/en/Best-Practices/Entities.md b/docs/en/Best-Practices/Entities.md index 03843c5278..1c76469cb7 100644 --- a/docs/en/Best-Practices/Entities.md +++ b/docs/en/Best-Practices/Entities.md @@ -63,7 +63,7 @@ public class Issue : FullAuditedAggregateRoot //Using Guid as the key/iden protected Issue() { - /* This conctructor is for ORMs to be used while getting the entity from database. + /* This constructor is for ORMs to be used while getting the entity from database. * - No need to initialize the Labels collection since it will be overrided from the database. - It's protected since proxying and deserialization tools diff --git a/docs/en/Module-Development-Basics.md b/docs/en/Module-Development-Basics.md index 6a0561a0c5..8149ec2cb1 100644 --- a/docs/en/Module-Development-Basics.md +++ b/docs/en/Module-Development-Basics.md @@ -112,7 +112,7 @@ Lastly, you can override ``OnApplicationShutdown`` method if you want to execute ## Module Dependencies -In a modular application, it's not unusual for one module to depend upon another module(s). An Abp module must declare ``[DependsOn]`` attribute if it does have a dependcy upon another module, as shown below: +In a modular application, it's not unusual for one module to depend upon another module(s). An Abp module must declare ``[DependsOn]`` attribute if it does have a dependency upon another module, as shown below: ````C# [DependsOn(typeof(AbpAspNetCoreMvcModule))] diff --git a/docs/en/Modules/Index.md b/docs/en/Modules/Index.md index 9dc5da7a99..2771c9a8a9 100644 --- a/docs/en/Modules/Index.md +++ b/docs/en/Modules/Index.md @@ -21,6 +21,6 @@ There are some **free and open source** application modules developed and mainta * **Permission Management**: Used to persist permissions. * **Setting Management**: Used to persist settings. * **Tenant Management**: Used to manage tenants for a [multi-tenant](../Multi-Tenancy.md) application. -* **Users**: Used the abstract users, so other modules can depend on this instead of the Identity module. +* **Users**: Used to abstract users, so other modules can depend on this instead of the Identity module. Documenting the modules is in the progress. See [this repository](https://github.com/abpframework/abp/tree/master/modules) for source code of all modules. \ No newline at end of file diff --git a/docs/en/Multi-Tenancy.md b/docs/en/Multi-Tenancy.md index ee0648f483..f5510a82db 100644 --- a/docs/en/Multi-Tenancy.md +++ b/docs/en/Multi-Tenancy.md @@ -56,7 +56,7 @@ namespace MyCompany.MyProject } ```` -IMultiTenant requires to define a **TenantId** property in the implementing entity (See entity documentation (TODO: link) for more about entities). +IMultiTenant requires to define a **TenantId** property in the implementing entity (See [entity documentation](Entities.md) for more about entities). #### Obtain Current Tenant's Id diff --git a/docs/en/Repositories.md b/docs/en/Repositories.md index 3e4a46a4f5..e6b55cb3de 100644 --- a/docs/en/Repositories.md +++ b/docs/en/Repositories.md @@ -48,8 +48,8 @@ In this example; Generic Repositories provides some standard CRUD features out of the box: -* Providers `Insert` method to save a new entity. -* Providers `Update` and `Delete` methods to update or delete an entity by entity object or it's id. +* Provides `Insert` method to save a new entity. +* Provides `Update` and `Delete` methods to update or delete an entity by entity object or it's id. * Provides `Delete` method to delete multiple entities by a filter. * Implements `IQueryable`, so you can use LINQ and extension methods like `FirstOrDefault`, `Where`, `OrderBy`, `ToList` and so on... * Have **sync** and **async** versions for all methods. diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index 4edebfbe03..bc3c8c0933 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -168,7 +168,7 @@ These are the actual applications those have user interfaces to interact to the - **AuthServer.Host**: Host the IdentityServer4 to provide an authentication service to other services and applications. It is a single-sign server and contains the login page. - **BackendAdminApp.Host**: This is a backend admin application that host UI for Identity and Product management modules. -- **PubicWebSite.Host**: As public web site that contains a simple product list page and blog module UI. +- **PublicWebSite.Host**: As public web site that contains a simple product list page and blog module UI. - **ConsoleClientDemo**: A simple console application to demonstrate the usage of services from a C# application. ### Gateways / BFFs (Backend for Frontend) @@ -1471,4 +1471,4 @@ ABP provides automatic audit logging which saves every request in detail (who is All of the services and applications are configured to write audit logs. Audit logs are saved to the MsDemo_Identity SQL database. So, you can query all audit logs of all applications from a single point. -An Audit Log record has a `CorrelationId` property that can be used to track a request. When a service calls another service in a single web request, they both save audit logs with the same `CorrelationId`. See the `AbpAuditLogs` table in the database. \ No newline at end of file +An Audit Log record has a `CorrelationId` property that can be used to track a request. When a service calls another service in a single web request, they both save audit logs with the same `CorrelationId`. See the `AbpAuditLogs` table in the database. diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 41eb7f1aeb..be77f0ab24 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -224,11 +224,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.FluentValidation", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.FluentValidation.Tests", "test\Volo.Abp.FluentValidation.Tests\Volo.Abp.FluentValidation.Tests.csproj", "{E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli.Tests", "test\Volo.Abp.Cli.Tests\Volo.Abp.Cli.Tests.csproj", "{92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.Sqlite", "src\Volo.Abp.EntityFrameworkCore.Sqlite\Volo.Abp.EntityFrameworkCore.Sqlite.csproj", "{58CF8957-5045-4F81-884D-72DF48F721CC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Cli.Core", "src\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj", "{3DA9923E-048E-4FE7-9748-3A0194F5D196}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli.Core", "src\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj", "{3DA9923E-048E-4FE7-9748-3A0194F5D196}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Specifications", "src\Volo.Abp.Specifications\Volo.Abp.Specifications.csproj", "{2C621EED-563C-4F81-A75E-50879E173544}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Specifications.Tests", "test\Volo.Abp.Specifications.Tests\Volo.Abp.Specifications.Tests.csproj", "{D078553A-C70C-4F56-B3E2-9C5BA6384C72}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli.Core.Tests", "test\Volo.Abp.Cli.Core.Tests\Volo.Abp.Cli.Core.Tests.csproj", "{F006B0B4-F25D-4511-9FB3-F17AA44BDCEA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Widgets", "src\Volo.Abp.AspNetCore.Mvc.UI.Widgets\Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj", "{EE1AAB08-3FBD-487F-B0B4-BEBA4B69528A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Dashboards", "src\Volo.Abp.AspNetCore.Mvc.UI.Dashboards\Volo.Abp.AspNetCore.Mvc.UI.Dashboards.csproj", "{054D766D-5992-460E-A4D8-936D80BE2C1A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -672,10 +680,6 @@ Global {E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}.Release|Any CPU.Build.0 = Release|Any CPU - {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}.Release|Any CPU.Build.0 = Release|Any CPU {58CF8957-5045-4F81-884D-72DF48F721CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58CF8957-5045-4F81-884D-72DF48F721CC}.Debug|Any CPU.Build.0 = Debug|Any CPU {58CF8957-5045-4F81-884D-72DF48F721CC}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -684,6 +688,26 @@ Global {3DA9923E-048E-4FE7-9748-3A0194F5D196}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DA9923E-048E-4FE7-9748-3A0194F5D196}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DA9923E-048E-4FE7-9748-3A0194F5D196}.Release|Any CPU.Build.0 = Release|Any CPU + {2C621EED-563C-4F81-A75E-50879E173544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C621EED-563C-4F81-A75E-50879E173544}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C621EED-563C-4F81-A75E-50879E173544}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C621EED-563C-4F81-A75E-50879E173544}.Release|Any CPU.Build.0 = Release|Any CPU + {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D078553A-C70C-4F56-B3E2-9C5BA6384C72}.Release|Any CPU.Build.0 = Release|Any CPU + {F006B0B4-F25D-4511-9FB3-F17AA44BDCEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F006B0B4-F25D-4511-9FB3-F17AA44BDCEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F006B0B4-F25D-4511-9FB3-F17AA44BDCEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F006B0B4-F25D-4511-9FB3-F17AA44BDCEA}.Release|Any CPU.Build.0 = Release|Any CPU + {EE1AAB08-3FBD-487F-B0B4-BEBA4B69528A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE1AAB08-3FBD-487F-B0B4-BEBA4B69528A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE1AAB08-3FBD-487F-B0B4-BEBA4B69528A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE1AAB08-3FBD-487F-B0B4-BEBA4B69528A}.Release|Any CPU.Build.0 = Release|Any CPU + {054D766D-5992-460E-A4D8-936D80BE2C1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {054D766D-5992-460E-A4D8-936D80BE2C1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {054D766D-5992-460E-A4D8-936D80BE2C1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {054D766D-5992-460E-A4D8-936D80BE2C1A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -798,9 +822,13 @@ Global {69168816-4394-4DDA-BB6B-C21983D37F0B} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {43D5FE61-ECBF-4B16-AD95-0043E18EB93A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {E9E1714F-7ED2-4BD1-BA4A-BA06E398288A} = {447C8A77-E5F0-4538-8687-7383196D04EA} - {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF} = {447C8A77-E5F0-4538-8687-7383196D04EA} {58CF8957-5045-4F81-884D-72DF48F721CC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {3DA9923E-048E-4FE7-9748-3A0194F5D196} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {2C621EED-563C-4F81-A75E-50879E173544} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {D078553A-C70C-4F56-B3E2-9C5BA6384C72} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {F006B0B4-F25D-4511-9FB3-F17AA44BDCEA} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {EE1AAB08-3FBD-487F-B0B4-BEBA4B69528A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {054D766D-5992-460E-A4D8-936D80BE2C1A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs index a9f3bb9c62..c7b20c6a33 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpDynamicformTagHelperService.cs @@ -205,6 +205,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form protected virtual List ExploreModelsRecursively(List list, ModelExplorer model) { + if (model.GetAttribute() != null) + { + return list; + } + if (IsCsharpClassOrPrimitive(model.ModelType) || IsListOfCsharpClassOrPrimitive(model.ModelType)) { list.Add(ModelExplorerToModelExpressionConverter(model)); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs index 2c7e6ad1cf..46ac3a8e90 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs @@ -187,8 +187,23 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form { var localizer = _tagHelperLocalizer.GetLocalizer(explorer); - var selectItems = explorer.Metadata.IsEnum ? explorer.ModelType.GetTypeInfo().GetMembers(BindingFlags.Public | BindingFlags.Static) - .Select((t, i) => new SelectListItem { Value = i.ToString(), Text = GetLocalizedPropertyName(localizer, explorer.ModelType, t.Name) }).ToList() : null; + var selectItems = new List(); + var isNullableType = Nullable.GetUnderlyingType(explorer.ModelType) != null; + var enumType = explorer.ModelType; + + if (isNullableType) + { + enumType = Nullable.GetUnderlyingType(explorer.ModelType); + selectItems.Add(new SelectListItem()); + } + + selectItems.AddRange(enumType.GetTypeInfo().GetMembers(BindingFlags.Public | BindingFlags.Static) + .Select((t, i) => + new SelectListItem + { + Value = ((int) Enum.Parse(enumType, t.Name)).ToString(), + Text = GetLocalizedPropertyName(localizer, enumType, t.Name) + }).ToList()); return selectItems; } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DynamicFormIgnore.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DynamicFormIgnore.cs new file mode 100644 index 0000000000..8726fc89ab --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DynamicFormIgnore.cs @@ -0,0 +1,9 @@ +using System; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form +{ + [AttributeUsage(AttributeTargets.Property)] + public class DynamicFormIgnore : Attribute + { + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs index 714a7aa68b..a0f0e65f1e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/AbpAspNetCoreMvcUiBundlingModule.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Properties/launchSettings.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Properties/launchSettings.json new file mode 100644 index 0000000000..0ce91a367d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:57333/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.AspNetCore.Mvc.UI.Dashboards": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:57343/" + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo.Abp.AspNetCore.Mvc.UI.Dashboards.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo.Abp.AspNetCore.Mvc.UI.Dashboards.csproj new file mode 100644 index 0000000000..c2e85e8239 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo.Abp.AspNetCore.Mvc.UI.Dashboards.csproj @@ -0,0 +1,34 @@ + + + + + + netstandard2.0 + Volo.Abp.AspNetCore.Mvc.UI.Dashboards + Volo.Abp.AspNetCore.Mvc.UI.Dashboards + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + true + Library + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/AbpAspNetCoreMvcUiDashboardsModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/AbpAspNetCoreMvcUiDashboardsModule.cs new file mode 100644 index 0000000000..6fcb11d28e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/AbpAspNetCoreMvcUiDashboardsModule.cs @@ -0,0 +1,20 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + [DependsOn( + typeof(AbpAspNetCoreMvcUiWidgetsModule) + )] + public class AbpAspNetCoreMvcUiDashboardsModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewComponent.cshtml.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewComponent.cshtml.cs new file mode 100644 index 0000000000..01d14cb7f1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewComponent.cshtml.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards.Components.Dashboard +{ + public class DashboardViewComponent : AbpViewComponent + { + public async Task InvokeAsync(string dashboardName, DashboardOptions dashboardOptions, WidgetOptions widgetOptions) + { + var model = new DashboardViewModel(dashboardName, dashboardOptions, widgetOptions); + return View("~/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/Default.cshtml", model); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewModel.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewModel.cs new file mode 100644 index 0000000000..4658078a3a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards.Components.Dashboard +{ + public class DashboardViewModel + { + public DashboardDefinition Dashboard { get; set; } + public DashboardOptions DashboardOptions { get; set; } + public WidgetOptions WidgetOptions { get; set; } + + public DashboardViewModel(string dashboardName, DashboardOptions dashboardOptions, WidgetOptions widgetOptions) + { + Dashboard = dashboardOptions.Dashboards.Single(d => d.Name.Equals(dashboardName)); + DashboardOptions = dashboardOptions; + WidgetOptions = widgetOptions; + } + + public WidgetDefinition GetWidget(string name) + { + return WidgetOptions.Widgets.Single(d => d.Name.Equals(name)); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/Default.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/Default.cshtml new file mode 100644 index 0000000000..524bf6d79e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/Default.cshtml @@ -0,0 +1,21 @@ +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets +@inject IWidgetRenderer WidgetRenderer +@inject IStringLocalizerFactory localizer +@model Volo.Abp.AspNetCore.Mvc.UI.Dashboards.Components.Dashboard.DashboardViewModel +@{ +} + + @foreach (var widgetConfiguration in Model.Dashboard.AvailableWidgets) + { + var widgetDefinition = Model.GetWidget(widgetConfiguration.WidgetName); + widgetDefinition.DefaultDimensions = widgetConfiguration.Dimensions ?? widgetDefinition.DefaultDimensions ?? new WidgetDimensions(5, 5); + + + + @await WidgetRenderer.RenderAsync(Component, widgetDefinition.Name) + + } + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardDefinition.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardDefinition.cs new file mode 100644 index 0000000000..20f8da6b75 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardDefinition.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; +using Volo.Abp.Localization; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + public class DashboardDefinition + { + /// + /// Unique name of the dashboard. + /// + [NotNull] + public string Name { get; } + + /// + /// A list of Widgets available for this dashboard. + /// + public List AvailableWidgets { get; } + + /// + /// Display name of the widget. + /// + [NotNull] + public ILocalizableString DisplayName + { + get => _displayName; + set => _displayName = Check.NotNull(value, nameof(value)); + } + private ILocalizableString _displayName; + + public DashboardDefinition( + [NotNull] string name, + [CanBeNull] ILocalizableString displayName) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + DisplayName = displayName ?? new FixedLocalizableString(name); + + AvailableWidgets = new List(); + } + + public DashboardDefinition WithWidget(string widgetName, WidgetLocation location = null, WidgetDimensions dimensions = null) + { + AvailableWidgets.Add( new DashboardWidgetConfiguration(widgetName, dimensions, location)); + + return this; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardOptions.cs new file mode 100644 index 0000000000..fc3ca09b74 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardOptions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + public class DashboardOptions + { + public List Dashboards { get; } + + public DashboardOptions() + { + Dashboards = new List(); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardRenderer.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardRenderer.cs new file mode 100644 index 0000000000..d35c58f0e2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardRenderer.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Dashboards.Components.Dashboard; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + public class DashboardRenderer : IDashboardRenderer + { + public async Task RenderAsync(IViewComponentHelper componentHelper, object args = null) + { + return await componentHelper.InvokeAsync(typeof(DashboardViewComponent), args ?? new object()); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardWidgetConfiguration.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardWidgetConfiguration.cs new file mode 100644 index 0000000000..74a20bc877 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/DashboardWidgetConfiguration.cs @@ -0,0 +1,28 @@ +using JetBrains.Annotations; +using Volo.Abp.AspNetCore.Mvc.UI.Widgets; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + public class DashboardWidgetConfiguration + { + [NotNull] + public string WidgetName { get; } + + [CanBeNull] + public WidgetDimensions Dimensions { get; set; } + + [CanBeNull] + public WidgetLocation Location { get; set; } + + public DashboardWidgetConfiguration( + [NotNull] string widgetName, + [CanBeNull] WidgetDimensions dimensions = null, + [CanBeNull] WidgetLocation location = null + ) + { + WidgetName = Check.NotNullOrWhiteSpace(widgetName, nameof(widgetName)); + Dimensions = dimensions; + Location = location; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/IDashboardRenderer.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/IDashboardRenderer.cs new file mode 100644 index 0000000000..64e149696f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/IDashboardRenderer.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + public interface IDashboardRenderer : ITransientDependency + { + Task RenderAsync(IViewComponentHelper componentHelper, object args = null); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/WidgetLocation.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/WidgetLocation.cs new file mode 100644 index 0000000000..7ce21a342e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/WidgetLocation.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + public class WidgetLocation + { + public int X { get; set; } + + public int Y { get; set; } + + public WidgetLocation(int x, int y) + { + X = x; + Y = y; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/_ViewImports.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/_ViewImports.cshtml new file mode 100644 index 0000000000..225780c2c2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs index bab248f669..ec82d4b3d6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Packages/Volo/Abp/AspNetCore/Mvc/UI/Packages/AbpAspNetCoreMvcUiPackagesModule.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.Modularity; namespace Volo.Abp.AspNetCore.Mvc.UI.Packages diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj new file mode 100644 index 0000000000..26634c6270 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo.Abp.AspNetCore.Mvc.UI.Widgets.csproj @@ -0,0 +1,16 @@ + + + + + + netstandard2.0 + Volo.Abp.AspNetCore.Mvc.UI.Widgets + Volo.Abp.AspNetCore.Mvc.UI.Widgets + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs new file mode 100644 index 0000000000..cdf1db75cd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/AbpAspNetCoreMvcUiWidgetsModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + [DependsOn( + typeof(AbpAspNetCoreMvcUiBootstrapModule) + )] + public class AbpAspNetCoreMvcUiWidgetsModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/IWidgetRenderer.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/IWidgetRenderer.cs new file mode 100644 index 0000000000..ab3c4ee09f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/IWidgetRenderer.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + public interface IWidgetRenderer : ITransientDependency + { + Task RenderAsync(IViewComponentHelper componentHelper, string widgetName, object args = null); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs new file mode 100644 index 0000000000..2296cfb4e4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs @@ -0,0 +1,44 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Localization; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + public class WidgetDefinition + { + /// + /// Unique name of the widget. + /// + [NotNull] + public string Name { get; } + + /// + /// Display name of the widget. + /// + [NotNull] + public ILocalizableString DisplayName + { + get => _displayName; + set => _displayName = Check.NotNull(value, nameof(value)); + } + private ILocalizableString _displayName; + + [NotNull] + public Type ViewComponentType { get; } + + [CanBeNull] + public WidgetDimensions DefaultDimensions { get; set; } + + public WidgetDefinition( + [NotNull] string name, + [NotNull] Type viewComponentType, + [CanBeNull] ILocalizableString displayName, + [CanBeNull] WidgetDimensions defaultDimensions = null) + { + DefaultDimensions = defaultDimensions; + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + ViewComponentType = Check.NotNull(viewComponentType, nameof(viewComponentType)); + DisplayName = displayName ?? new FixedLocalizableString(name); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDimensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDimensions.cs new file mode 100644 index 0000000000..eb24d208b4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDimensions.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + public class WidgetDimensions + { + public int Width { get; } + + public int Height { get; } + + public WidgetDimensions(int width, int height) + { + Width = width; + Height = height; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetOptions.cs new file mode 100644 index 0000000000..7a4fa3487f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetOptions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + public class WidgetOptions + { + public List Widgets { get; } + + public WidgetOptions() + { + Widgets = new List(); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetRenderer.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetRenderer.cs new file mode 100644 index 0000000000..2bb8c58fe9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetRenderer.cs @@ -0,0 +1,25 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Widgets +{ + public class WidgetRenderer : IWidgetRenderer + { + private readonly WidgetOptions _widgetOptions; + + public WidgetRenderer(IOptions widgetOptions) + { + _widgetOptions = widgetOptions.Value; + } + + public async Task RenderAsync(IViewComponentHelper componentHelper, string widgetName, object args = null) + { + var componentType = _widgetOptions.Widgets.Single(w=>w.Name.Equals(widgetName)).ViewComponentType; + + return await componentHelper.InvokeAsync(componentType, args ?? new object()); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs index 616190af64..da406fa490 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingOptions.cs @@ -9,6 +9,12 @@ namespace Volo.Abp.Auditing { //TODO: Consider to add an option to disable auditing for application service methods? + /// + /// If this value is true, auditing will not throw an exceptions and it will log it when an error occurred while saving AuditLog. + /// Default: true. + /// + public bool HideErrors { get; set; } + /// /// Default: true. /// @@ -36,11 +42,12 @@ namespace Volo.Abp.Auditing /// Default: false. /// public bool IsEnabledForGetRequests { get; set; } - + public AbpAuditingOptions() { IsEnabled = true; IsEnabledForAnonymousUsers = true; + HideErrors = true; Contributors = new List(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj index 41cb647649..708c296880 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj +++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj @@ -17,10 +17,12 @@ + + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 2dd39cdfce..c1894b2e6b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -22,7 +22,8 @@ namespace Volo.Abp.Cli { options.Commands["help"] = typeof(HelpCommand); options.Commands["new"] = typeof(NewCommand); - options.Commands["add"] = typeof(AddCommand); + options.Commands["add-package"] = typeof(AddPackageCommand); + options.Commands["add-module"] = typeof(AddModuleCommand); options.Commands["login"] = typeof(LoginCommand); options.Commands["logout"] = typeof(LogoutCommand); }); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgumentParser.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgumentParser.cs index 7125699546..1dd29e5df7 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgumentParser.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgumentParser.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.Cli.Args var argumentList = args.ToList(); - //Get command + //Command var command = argumentList[0]; argumentList.RemoveAt(0); @@ -26,7 +26,7 @@ namespace Volo.Abp.Cli.Args return new CommandLineArgs(command); } - //Get target + //Target var target = argumentList[0]; if (target.StartsWith("-")) @@ -43,7 +43,7 @@ namespace Volo.Abp.Cli.Args return new CommandLineArgs(command, target); } - //Get options + //Options var commandLineArgs = new CommandLineArgs(command, target); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs index 68dfc52cf9..5cf93e7c2d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs @@ -22,7 +22,7 @@ namespace Volo.Abp.Cli.Auth var accessToken = await AuthenticationService.GetAccessTokenAsync( new IdentityClientConfiguration( "https://localhost:44333", //TODO: Configure - "role email abpio_www", + "role email abpio_www abpio_commercial", "abp-cli", "1q2w3e*", OidcConstants.GrantTypes.Password, diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliOptions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliOptions.cs index 3885314e70..09cbbe09b7 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliOptions.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliOptions.cs @@ -7,6 +7,10 @@ namespace Volo.Abp.Cli { public Dictionary Commands { get; } + public string AbpIoWwwUrlRoot { get; set; } = "https://abp.io/"; + + public string AbpIoAccountUrlRoot { get; set; } = "https://account.abp.io/"; + public CliOptions() { Commands = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs index 9c3f5a458c..eb6339b9da 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs @@ -12,4 +12,4 @@ namespace Volo.Abp.Cli private static readonly string AbpRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".abp"); } -} +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs index 502ab294e6..3c2a88094f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -1,42 +1,51 @@ using System; +using System.Diagnostics; +using System.IO; using System.Reflection; +using System.Runtime.InteropServices; using Microsoft.Extensions.DependencyInjection; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Commands; +using Volo.Abp.Cli.NuGet; +using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; -using Volo.Abp.ProjectBuilding; namespace Volo.Abp.Cli { public class CliService : ITransientDependency { - public static string Version => typeof(AbpCliCoreModule).Assembly.GetFileVersion(); - public ILogger Logger { get; set; } protected ICommandLineArgumentParser CommandLineArgumentParser { get; } protected ICommandSelector CommandSelector { get; } - public IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected NuGetService NuGetService { get; } public CliService( ICommandLineArgumentParser commandLineArgumentParser, ICommandSelector commandSelector, - IHybridServiceScopeFactory serviceScopeFactory) + IHybridServiceScopeFactory serviceScopeFactory, + NuGetService nugetService) { CommandLineArgumentParser = commandLineArgumentParser; CommandSelector = commandSelector; ServiceScopeFactory = serviceScopeFactory; + NuGetService = nugetService; Logger = NullLogger.Instance; } public async Task RunAsync(string[] args) { - Logger.LogInformation("ABP CLI (abp.io)"); - Logger.LogInformation("Version: " + Version); + Logger.LogInformation($"ABP CLI, version {GetCliVersion()}."); + Logger.LogInformation("https://abp.io"); + + await CheckForNewVersion(); + + CheckDependencies(); var commandLineArgs = CommandLineArgumentParser.Parse(args); var commandType = CommandSelector.Select(commandLineArgs); @@ -44,8 +53,118 @@ namespace Volo.Abp.Cli using (var scope = ServiceScopeFactory.CreateScope()) { var command = (IConsoleCommand)scope.ServiceProvider.GetRequiredService(commandType); - await command.ExecuteAsync(commandLineArgs); + + try + { + await command.ExecuteAsync(commandLineArgs); + } + catch (CliUsageException usageException) + { + Logger.LogWarning(usageException.Message); + } + catch (Exception ex) + { + Logger.LogException(ex); + } + } + } + + private void CheckDependencies() + { + var installedNpmPackages = GetInstalledNpmPackages(); + + if (!installedNpmPackages.Contains(" yarn@")) + { + InstallYarn(); + } + if (!installedNpmPackages.Contains(" gulp@")) + { + InstallGulp(); } } + + private string GetInstalledNpmPackages() + { + var output = ""; + + using (var process = new Process()) + { + process.StartInfo = new ProcessStartInfo(CmdHelper.GetFileName()) + { + Arguments = CmdHelper.GetArguments("npm list -g --depth 0"), + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + process.Start(); + + using (var stdOut = process.StandardOutput) + { + using (var stdErr = process.StandardError) + { + output = stdOut.ReadToEnd(); + output += stdErr.ReadToEnd(); + } + } + } + + return output; + } + + private void InstallYarn() + { + Logger.LogInformation("Installing yarn..."); + CmdHelper.RunCmd("npm install yarn -g"); + } + + private void InstallGulp() + { + Logger.LogInformation("Installing gulp..."); + CmdHelper.RunCmd("npm install gulp -g"); + } + + private async Task CheckForNewVersion() + { + try + { + var currentVersion = GetCliVersion(); + var latestVersion = await NuGetService.GetLatestVersionOrNullAsync("Volo.Abp.Cli"); + + if (!latestVersion.IsNullOrEmpty() && currentVersion != latestVersion) + { + Logger.LogInformation(""); + Logger.LogWarning("ABP CLI has a newer version (" + latestVersion + "). Please update to get the latest features and fixes."); + Logger.LogWarning(""); + Logger.LogWarning("Update Command: "); + Logger.LogWarning(" dotnet tool update -g Volo.Abp.Cli"); + Logger.LogWarning(""); + } + } + catch (Exception e) + { + Logger.LogWarning("Could not get the latest version infom from NuGet.org:"); + Logger.LogWarning(e.Message); + } + } + + private static string GetCliVersion() + { + var version = typeof(CliService) + .Assembly + .GetFileVersion(); + + /* Assembly versions are like "2.4.0.0", but NuGet removes the last "0" here, + * like "2.4.0". + * So, we need to remove it from the assembly version to match to the NuGet version. + */ + + if (version.Split('.').Length == 4) + { + version = version.RemovePostFix(".0"); + } + + return version; + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUsageException.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUsageException.cs new file mode 100644 index 0000000000..5ce2b7a1b3 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUsageException.cs @@ -0,0 +1,19 @@ +using System; + +namespace Volo.Abp.Cli +{ + public class CliUsageException : Exception + { + public CliUsageException(string message) + : base(message) + { + + } + + public CliUsageException(string message, Exception innerException) + : base(message, innerException) + { + + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommand.cs deleted file mode 100644 index e5034725ba..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommand.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using System.Threading.Tasks; -using Volo.Abp.Cli.Args; -using Volo.Abp.DependencyInjection; -using Volo.Abp.ProjectModification; - -namespace Volo.Abp.Cli.Commands -{ - public class AddCommand : IConsoleCommand, ITransientDependency - { - public ILogger Logger { get; set; } - - protected ModuleAdder ModuleAdder { get; } - - public AddCommand(ModuleAdder moduleAdder) - { - ModuleAdder = moduleAdder; - Logger = NullLogger.Instance; - } - - public async Task ExecuteAsync(CommandLineArgs commandLineArgs) - { - if (commandLineArgs.Target == null) - { - Logger.LogWarning("Module name is missing."); - AddCommandHelper.WriteUsage(Logger); - return; - } - - await ModuleAdder.AddModuleAsync( - new AddModuleArgs( - commandLineArgs.Target, - commandLineArgs.Options.GetOrNull(Options.Solution.Short, Options.Solution.Long), - commandLineArgs.Options.GetOrNull(Options.Project.Short, Options.Project.Long) - ) - ); - } - - public static class Options - { - public static class Solution - { - public const string Short = "s"; - public const string Long = "solution"; - } - - public static class Project - { - public const string Short = "p"; - public const string Long = "project"; - } - } - } -} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommandHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommandHelper.cs index 0704ecdbf3..d1b5c9d716 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommandHelper.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommandHelper.cs @@ -4,24 +4,6 @@ namespace Volo.Abp.Cli.Commands { public static class AddCommandHelper { - public static void WriteUsage(ILogger logger) - { - logger.LogWarning(""); - logger.LogWarning("'add' command is used to add a module to a solution or to a project."); - logger.LogWarning("Use in a solution folder to add a multi-package module to a solution."); - logger.LogWarning("Use in a project folder to add a single-package module to a project."); - logger.LogWarning(""); - logger.LogWarning("Usage:"); - logger.LogWarning(" abp add [-s|--solution] [-p|--project]"); - logger.LogWarning(""); - logger.LogWarning("Options:"); - logger.LogWarning(" -s|--solution Specify solution file explicitly."); - logger.LogWarning(" -p|--project Specify project file explicitly."); - logger.LogWarning(""); - logger.LogWarning("Examples:"); - logger.LogWarning(" abp add Volo.Abp.TenantManagement Adds tenant management module packages to the solution."); - logger.LogWarning(" abp add Volo.Abp.FluentValidation Adds a single module package to the project."); - logger.LogWarning(""); - } + } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs new file mode 100644 index 0000000000..7925b82aa6 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectModification; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class AddModuleCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + protected SolutionModuleAdder SolutionModuleAdder { get; } + + public AddModuleCommand(SolutionModuleAdder solutionModuleAdder) + { + SolutionModuleAdder = solutionModuleAdder; + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + if (commandLineArgs.Target == null) + { + throw new CliUsageException("Module name is missing!" + Environment.NewLine + Environment.NewLine + GetUsageInfo()); + } + + var skipDbMigrations = Convert.ToBoolean( + commandLineArgs.Options.GetOrNull(Options.DbMigrations.Skip) ?? "false"); + + await SolutionModuleAdder.AddAsync( + GetSolutionFile(commandLineArgs), + commandLineArgs.Target, + skipDbMigrations + ); + } + + protected virtual string GetSolutionFile(CommandLineArgs commandLineArgs) + { + var providedSolutionFile = PathHelper.NormalizePath( + commandLineArgs.Options.GetOrNull( + AddPackageCommand.Options.Project.Short, + AddPackageCommand.Options.Project.Long + ) + ); + + if (!providedSolutionFile.IsNullOrWhiteSpace()) + { + return providedSolutionFile; + } + + var foundSolutionFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.sln"); + if (foundSolutionFiles.Length == 1) + { + return foundSolutionFiles[0]; + } + + if (foundSolutionFiles.Length == 0) + { + throw new CliUsageException("'abp add-module' command should be used inside a folder contaning a .sln file!"); + } + + //foundSolutionFiles.Length > 1 + + var sb = new StringBuilder("There are multiple solution (.sln) files in the current directory. Please specify one of the files below:"); + + foreach (var foundSolutionFile in foundSolutionFiles) + { + sb.AppendLine("* " + foundSolutionFile); + } + + sb.AppendLine("Example:"); + sb.AppendLine($"abp add-module {commandLineArgs.Target} -p {foundSolutionFiles[0]}"); + + throw new CliUsageException(sb.ToString()); + } + + protected virtual string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("'add-module' command is used to add a multi-package ABP module to a solution."); + sb.AppendLine("It should be used in a folder containing a .sln file."); + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(" abp add-module [-s|--solution]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(" -s|--solution Specify the solution file explicitly."); + sb.AppendLine(" --skip-db-migrations Specify if a new migration will be added or not."); + sb.AppendLine(""); + sb.AppendLine("Examples:"); + sb.AppendLine(" abp add-module Volo.Blogging Adds the module to the current soluton."); + sb.AppendLine(" abp add-module Volo.Blogging -s Acme.BookStore Adds the module to the given soluton."); + sb.AppendLine(" abp add-module Volo.Blogging -s Acme.BookStore --skip-db-migrations false Adds the module to the given soluton but doesn't add-migration."); + sb.AppendLine(""); + + return sb.ToString(); + } + + public static class Options + { + public static class Solution + { + public const string Short = "s"; + public const string Long = "solution"; + } + + public static class DbMigrations + { + public const string Skip = "skip-db-migrations"; + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs new file mode 100644 index 0000000000..d657000b2d --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -0,0 +1,110 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectModification; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class AddPackageCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; } + + public AddPackageCommand(ProjectNugetPackageAdder projectNugetPackageAdder) + { + ProjectNugetPackageAdder = projectNugetPackageAdder; + Logger = NullLogger.Instance; + } + + public virtual async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + if (commandLineArgs.Target == null) + { + throw new CliUsageException("Package name is missing!" + Environment.NewLine + Environment.NewLine + GetUsageInfo()); + } + + await ProjectNugetPackageAdder.AddAsync( + GetProjectFile(commandLineArgs), + commandLineArgs.Target + ); + } + + protected virtual string GetProjectFile(CommandLineArgs commandLineArgs) + { + var providedProjectFile = PathHelper.NormalizePath( + commandLineArgs.Options.GetOrNull( + Options.Project.Short, + Options.Project.Long + ) + ); + + if (!providedProjectFile.IsNullOrWhiteSpace()) + { + return providedProjectFile; + } + + var foundProjectFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.csproj"); + if (foundProjectFiles.Length == 1) + { + return foundProjectFiles[0]; + } + + if (foundProjectFiles.Length == 0) + { + throw new CliUsageException("'abp add-package' command should be used inside a folder contaning a .csproj file!"); + } + + //foundProjectFiles.Length > 1 + + var sb = new StringBuilder("There are multiple project (.csproj) files in the current directory. Please specify one of the files below:"); + + foreach (var foundProjectFile in foundProjectFiles) + { + sb.AppendLine("* " + foundProjectFile); + } + + sb.AppendLine("Example:"); + sb.AppendLine($"abp add-package {commandLineArgs.Target} -p {foundProjectFiles[0]}"); + + throw new CliUsageException(sb.ToString()); + } + + protected virtual string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("'add-package' command is used to add an ABP package to a project."); + sb.AppendLine("It should be used in a folder containing a .csproj file."); + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(" abp add-package [-p|--project]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(" -p|--project Specify the project file explicitly."); + sb.AppendLine(""); + sb.AppendLine("Examples:"); + sb.AppendLine(" abp add-package Volo.Abp.FluentValidation Adds the package to the current project."); + sb.AppendLine(" abp add-package Volo.Abp.FluentValidation -p Acme.BookStore.Application Adds the package to the given project."); + sb.AppendLine(""); + + return sb.ToString(); + } + + public static class Options + { + public static class Project + { + public const string Short = "p"; + public const string Long = "project"; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs index d3d3d7c736..595917e845 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs @@ -1,6 +1,6 @@ -using System; +using Microsoft.Extensions.Options; +using System; using System.Collections.Generic; -using Microsoft.Extensions.Options; using Volo.Abp.Cli.Args; using Volo.Abp.DependencyInjection; @@ -17,7 +17,12 @@ namespace Volo.Abp.Cli.Commands public Type Select(CommandLineArgs commandLineArgs) { - return Options.Commands.GetOrDefault(commandLineArgs.Command) + if (commandLineArgs.Command.IsNullOrWhiteSpace()) + { + return typeof(HelpCommand); + } + + return Options.Commands.GetOrDefault(commandLineArgs.Command) ?? typeof(HelpCommand); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs index fdad2a445e..671bf3cbab 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs @@ -1,5 +1,6 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Args; using Volo.Abp.DependencyInjection; @@ -7,13 +8,21 @@ namespace Volo.Abp.Cli.Commands { public class HelpCommand : IConsoleCommand, ITransientDependency { + public ILogger Logger { get; set; } + + public HelpCommand() + { + Logger = NullLogger.Instance; + } + public Task ExecuteAsync(CommandLineArgs commandLineArgs) { - Console.WriteLine("*********** ABP CLI ****************"); - Console.WriteLine(); - Console.WriteLine("Usage:"); - Console.WriteLine(); - Console.WriteLine(" abp [options]"); + Logger.LogInformation(""); + Logger.LogInformation("Usage:"); + Logger.LogInformation(""); + Logger.LogInformation(" abp [options]"); + Logger.LogInformation(""); + return Task.CompletedTask; } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs index b0c96a7297..28bc64afd5 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs @@ -25,6 +25,7 @@ namespace Volo.Abp.Cli.Commands { if (commandLineArgs.Target.IsNullOrEmpty()) { + Logger.LogInformation(""); Logger.LogWarning("Username is missing."); LogHelp(); return; @@ -34,6 +35,7 @@ namespace Volo.Abp.Cli.Commands var password = ConsoleHelper.ReadSecret(); if (password.IsNullOrWhiteSpace()) { + Logger.LogInformation(""); Logger.LogWarning("Password is missing."); LogHelp(); return; @@ -41,7 +43,7 @@ namespace Volo.Abp.Cli.Commands await AuthService.LoginAsync(commandLineArgs.Target, password); - Console.WriteLine($"Successfully logged in as '{commandLineArgs.Target}'"); + Logger.LogInformation($"Successfully logged in as '{commandLineArgs.Target}'"); } private void LogHelp() 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 c4ab4f6216..59790a7c7d 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 @@ -4,9 +4,9 @@ using Ionic.Zip; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectBuilding; +using Volo.Abp.Cli.ProjectBuilding.Building; using Volo.Abp.DependencyInjection; -using Volo.Abp.ProjectBuilding; -using Volo.Abp.ProjectBuilding.Building; namespace Volo.Abp.Cli.Commands { @@ -27,6 +27,7 @@ namespace Volo.Abp.Cli.Commands { if (commandLineArgs.Target == null) { + Logger.LogInformation(""); Logger.LogWarning("Project name is missing."); Logger.LogWarning(""); Logger.LogWarning("Usage:"); 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 new file mode 100644 index 0000000000..115ce41d74 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/NuGet/NuGetService.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; +using Volo.Abp.Threading; + +namespace Volo.Abp.Cli.NuGet +{ + public class NuGetService : ISingletonDependency + { + protected IJsonSerializer JsonSerializer { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + + public NuGetService( + IJsonSerializer jsonSerializer, + ICancellationTokenProvider cancellationTokenProvider) + { + JsonSerializer = jsonSerializer; + CancellationTokenProvider = cancellationTokenProvider; + } + + public async Task GetLatestVersionOrNullAsync(string packageId, bool includePreviews = false) + { + using (var client = new HttpClient()) + { + client.Timeout = TimeSpan.FromSeconds(30); + + var responseMessage = await client.GetAsync( + $"https://api.nuget.org/v3-flatcontainer/{packageId.ToLowerInvariant()}/index.json", + CancellationTokenProvider.Token + ); + + if (!responseMessage.IsSuccessStatusCode) + { + throw new Exception("Remote server returns error! HTTP status code: " + responseMessage.StatusCode); + } + + var result = await responseMessage.Content.ReadAsStringAsync(); + + var versions = JsonSerializer.Deserialize(result).Versions; + + if (!includePreviews) + { + versions = versions + .Where(x => !x.Contains("beta") && !x.Contains("preview") && !x.Contains("alpha") && !x.Contains("rc")) + .ToList(); + } + + return versions.Count > 0 ? versions.Last() : null; + } + } + + public class NuGetVersionResultDto + { + [JsonProperty("versions")] + public List Versions { get; set; } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoTemplateStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoTemplateStore.cs new file mode 100644 index 0000000000..adfa4e044a --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoTemplateStore.cs @@ -0,0 +1,159 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; +using Volo.Abp.IO; +using Volo.Abp.Json; +using Volo.Abp.Threading; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class AbpIoTemplateStore : ITemplateStore, ITransientDependency + { + public ILogger Logger { get; set; } + + protected CliOptions Options { get; } + protected IJsonSerializer JsonSerializer { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + + public AbpIoTemplateStore( + IOptions options, + IJsonSerializer jsonSerializer, + ICancellationTokenProvider cancellationTokenProvider) + { + JsonSerializer = jsonSerializer; + CancellationTokenProvider = cancellationTokenProvider; + Options = options.Value; + + Logger = NullLogger.Instance; + } + + public async Task GetAsync( + string name, + DatabaseProvider databaseProvider, + string projectName, + string version = null) + { + if (version == null) + { + version = await GetLatestTemplateVersionAsync(name); + } + + DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache); + + var localCacheFile = Path.Combine(CliPaths.TemplateCache, name + "-" + version + ".zip"); + if (File.Exists(localCacheFile)) + { + Logger.LogInformation("Using cached template: " + name + ", version: " + version); + return new TemplateFile(File.ReadAllBytes(localCacheFile), version); + } + + Logger.LogInformation("Downloading template: " + name + ", version: " + version); + + var fileContent = await DownloadTemplateFileContentAsync( + new TemplateDownloadInputDto + { + Name = name, + Version = version, + DatabaseProvider = databaseProvider.ToString(), + ProjectName = projectName + } + ); + + File.WriteAllBytes(localCacheFile, fileContent); + + return new TemplateFile(fileContent, version); + } + + private async Task GetLatestTemplateVersionAsync(string name) + { + var postData = JsonSerializer.Serialize(new GetLatestTemplateVersionDto { Name = name }); + + using (var client = new HttpClient()) + { + client.Timeout = TimeSpan.FromSeconds(30); + + AddAuthentication(client); + + var responseMessage = await client.PostAsync( + Options.AbpIoWwwUrlRoot + "api/download/template/get-version/", + new StringContent(postData, Encoding.UTF8, MimeTypes.Application.Json), + CancellationTokenProvider.Token + ); + + if (!responseMessage.IsSuccessStatusCode) + { + throw new Exception("Remote server returns error! HTTP status code: " + responseMessage.StatusCode); + } + + var result = await responseMessage.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(result).Version; + } + } + + private async Task DownloadTemplateFileContentAsync(TemplateDownloadInputDto input) + { + var postData = JsonSerializer.Serialize(input); + + using (var client = new HttpClient()) + { + client.Timeout = TimeSpan.FromMinutes(3); + + AddAuthentication(client); + + var responseMessage = await client.PostAsync( + Options.AbpIoWwwUrlRoot + "api/download/template/", + new StringContent(postData, Encoding.UTF8, MimeTypes.Application.Json), + CancellationTokenProvider.Token + ); + + if (!responseMessage.IsSuccessStatusCode) + { + throw new Exception("Remote server returns error! HTTP status code: " + responseMessage.StatusCode); + } + + return await responseMessage.Content.ReadAsByteArrayAsync(); + } + } + + private static void AddAuthentication(HttpClient client) + { + if (File.Exists(CliPaths.AccessToken)) + { + var accessToken = File.ReadAllText(CliPaths.AccessToken, Encoding.UTF8); + if (!accessToken.IsNullOrEmpty()) + { + client.SetBearerToken(accessToken); + } + } + } + + public class TemplateDownloadInputDto + { + public string Name { get; set; } + + public string Version { get; set; } + + public string DatabaseProvider { get; set; } + + public string ProjectName { get; set; } + } + + public class GetLatestTemplateVersionDto + { + public string Name { get; set; } + } + + public class GetLatestTemplateVersionResultDto + { + public string Version { get; set; } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/DatabaseProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseProvider.cs similarity index 70% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/DatabaseProvider.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseProvider.cs index 0352bed89c..852c63057a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/DatabaseProvider.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseProvider.cs @@ -1,4 +1,4 @@ -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public enum DatabaseProvider { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/GithubRepositoryInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/GithubRepositoryInfo.cs similarity index 94% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/GithubRepositoryInfo.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/GithubRepositoryInfo.cs index 7a7277f8a4..22c8bbe569 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/GithubRepositoryInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/GithubRepositoryInfo.cs @@ -1,6 +1,6 @@ using System; -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public class GithubRepositoryInfo { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContext.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs similarity index 65% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContext.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs index 7e16a377c6..1ea040022f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContext.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs @@ -1,16 +1,13 @@ using JetBrains.Annotations; -using Volo.Abp.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Files; -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public class ProjectBuildContext { [NotNull] public TemplateFile TemplateFile { get; } - [NotNull] - public string Version { get; } - [NotNull] public ProjectBuildArgs BuildArgs { get; } @@ -21,15 +18,14 @@ namespace Volo.Abp.ProjectBuilding.Building public ProjectResult Result { get; set; } - public ProjectBuildContext([NotNull] TemplateInfo template, + public ProjectBuildContext( + [NotNull] TemplateInfo template, [NotNull] TemplateFile templateFile, - [NotNull] ProjectBuildArgs buildArgs, - [NotNull] string version) + [NotNull] ProjectBuildArgs buildArgs) { Template = Check.NotNull(template, nameof(template)); TemplateFile = Check.NotNull(templateFile, nameof(templateFile)); BuildArgs = Check.NotNull(buildArgs, nameof(buildArgs)); - Version = Check.NotNullOrWhiteSpace(version, nameof(version)); Result = new ProjectResult(); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContextExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContextExtensions.cs similarity index 83% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContextExtensions.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContextExtensions.cs index b4623e6744..e793c77011 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContextExtensions.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContextExtensions.cs @@ -1,8 +1,8 @@ using System; using System.Linq; -using Volo.Abp.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Files; -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public static class ProjectBuildContextExtensions { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipeline.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipeline.cs similarity index 90% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipeline.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipeline.cs index c4afe2aab6..a6e5e2e56f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipeline.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipeline.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public class ProjectBuildPipeline { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs similarity index 86% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs index 35ea1bfe39..c4d15d5548 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs @@ -1,6 +1,6 @@ -using Volo.Abp.ProjectBuilding.Building.Steps; +using Volo.Abp.Cli.ProjectBuilding.Building.Steps; -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public static class ProjectBuildPipelineBuilder { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineStep.cs similarity index 71% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineStep.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineStep.cs index 3080224ed2..d148cea84a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineStep.cs @@ -1,4 +1,4 @@ -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public abstract class ProjectBuildPipelineStep { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectResult.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectResult.cs similarity index 63% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectResult.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectResult.cs index 19230adfb5..206a713413 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectResult.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectResult.cs @@ -1,4 +1,4 @@ -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public class ProjectResult { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeLocalhostPortStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeLocalhostPortStep.cs new file mode 100644 index 0000000000..e902e245cf --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ChangeLocalhostPortStep.cs @@ -0,0 +1,35 @@ +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class ChangeLocalhostPortStep : ProjectBuildPipelineStep + { + public string LaunchSettingsFilePath { get; } + public int[] PortNumbers { get; } + + public ChangeLocalhostPortStep( + string launchSettingsFilePath, + params int[] portNumbers) + { + Check.NotNullOrEmpty(portNumbers, nameof(portNumbers)); + + LaunchSettingsFilePath = launchSettingsFilePath; + PortNumbers = portNumbers; + } + + public override void Execute(ProjectBuildContext context) + { + var launchSettings = context.GetFile(LaunchSettingsFilePath); + var randomPort = RandomHelper.GetRandom(50001, 59990); + var newContent = launchSettings.Content; + + for (var i = 0; i < PortNumbers.Length; i++) + { + newContent = newContent.Replace( + PortNumbers[i].ToString(), + (randomPort + i).ToString() + ); + } + + launchSettings.SetContent(newContent); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs similarity index 79% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs index a2b468effd..53e9415617 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs @@ -1,8 +1,8 @@ using Ionic.Zip; -using Volo.Abp.ProjectBuilding.Files; -using Volo.Abp.ProjectBuilding.Zipping; +using Volo.Abp.Cli.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Zipping; -namespace Volo.Abp.ProjectBuilding.Building.Steps +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class CreateProjectResultZipStep : ProjectBuildPipelineStep { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs similarity index 82% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs index 22f1d8840f..3484621df1 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs @@ -1,9 +1,9 @@ using System.IO; using Ionic.Zip; -using Volo.Abp.ProjectBuilding.Files; -using Volo.Abp.ProjectBuilding.Zipping; +using Volo.Abp.Cli.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Zipping; -namespace Volo.Abp.ProjectBuilding.Building.Steps +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class FileEntryListReadStep : ProjectBuildPipelineStep { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs similarity index 96% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs index 3d41c682dc..49ccb308c8 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs @@ -2,9 +2,9 @@ using System.IO; using System.Linq; using HtmlAgilityPack; -using Volo.Abp.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Files; -namespace Volo.Abp.ProjectBuilding.Building.Steps +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class NugetReferenceReplaceStep : ProjectBuildPipelineStep { @@ -14,7 +14,7 @@ namespace Volo.Abp.ProjectBuilding.Building.Steps context.Files, "MyCompanyName", "MyProjectName", - context.Version + context.TemplateFile.Version ).Run(); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs similarity index 97% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs index c956e4b4ab..f13098f0df 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RemoveProjectFromSolutionStep.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace Volo.Abp.ProjectBuilding.Building.Steps +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class RemoveProjectFromSolutionStep : ProjectBuildPipelineStep { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SolutionRenameStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs similarity index 97% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SolutionRenameStep.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs index 20f2810633..069b05ff3a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SolutionRenameStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using Volo.Abp.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Files; -namespace Volo.Abp.ProjectBuilding.Building.Steps +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class SolutionRenameStep : ProjectBuildPipelineStep { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs similarity index 92% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs index 6b77808f55..4a6372f15a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs @@ -1,7 +1,7 @@ using System; -using Volo.Abp.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Files; -namespace Volo.Abp.ProjectBuilding.Building.Steps +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class SwitchEntityFrameworkCoreToMongoDbStep : ProjectBuildPipelineStep { @@ -21,9 +21,9 @@ namespace Volo.Abp.ProjectBuilding.Building.Steps var lines = file.GetLines(); for (var i = 0; i < lines.Length; i++) { - if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore")) + if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")) { - lines[i] = lines[i].Replace("EntityFrameworkCore", "MongoDB"); + lines[i] = lines[i].Replace("EntityFrameworkCore.DbMigrations", "MongoDB"); file.SetLines(lines); return; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs similarity index 81% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs index aa3990905a..e673446d9f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs @@ -1,6 +1,6 @@ -using Volo.Abp.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Files; -namespace Volo.Abp.ProjectBuilding.Building.Steps +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class TemplateCodeDeleteStep : ProjectBuildPipelineStep { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/TemplateInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs similarity index 94% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/TemplateInfo.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs index c85d1d571c..4041dc8d7a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/TemplateInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using JetBrains.Annotations; -namespace Volo.Abp.ProjectBuilding.Building +namespace Volo.Abp.Cli.ProjectBuilding.Building { public abstract class TemplateInfo { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/DownloadInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/DownloadInfo.cs similarity index 92% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/DownloadInfo.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/DownloadInfo.cs index 9d07f6e022..e8f51f5958 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/DownloadInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/DownloadInfo.cs @@ -1,9 +1,9 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Volo.Abp.Cli.ProjectBuilding.Building; using Volo.Abp.Domain.Entities.Auditing; -using Volo.Abp.ProjectBuilding.Building; -namespace Volo.Abp.ProjectBuilding +namespace Volo.Abp.Cli.ProjectBuilding { [Table("Downloads")] public class DownloadInfo : CreationAuditedAggregateRoot diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntry.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntry.cs similarity index 98% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntry.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntry.cs index 5dd67846d2..ffa248c9f2 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntry.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntry.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -namespace Volo.Abp.ProjectBuilding.Files +namespace Volo.Abp.Cli.ProjectBuilding.Files { public class FileEntry { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs similarity index 98% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryExtensions.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs index 19bf75456c..699e77fd2e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryExtensions.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Volo.Abp.ProjectBuilding.Files +namespace Volo.Abp.Cli.ProjectBuilding.Files { public static class FileEntryExtensions { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryList.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryList.cs similarity index 82% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryList.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryList.cs index 34543e31fa..69c7b89d6f 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryList.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryList.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Volo.Abp.ProjectBuilding.Files +namespace Volo.Abp.Cli.ProjectBuilding.Files { public class FileEntryList : List { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryListExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryListExtensions.cs similarity index 92% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryListExtensions.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryListExtensions.cs index 1a1a47c1c6..d8eff66813 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryListExtensions.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryListExtensions.cs @@ -1,7 +1,7 @@ using System; using Ionic.Zip; -namespace Volo.Abp.ProjectBuilding.Files +namespace Volo.Abp.Cli.ProjectBuilding.Files { public static class FileEntryListExtensions { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Github/GithubRelease.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Github/GithubRelease.cs similarity index 89% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Github/GithubRelease.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Github/GithubRelease.cs index 449f9a43d5..9d37befbc2 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Github/GithubRelease.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Github/GithubRelease.cs @@ -1,7 +1,7 @@ using System; using Newtonsoft.Json; -namespace Volo.Abp.ProjectBuilding.Github +namespace Volo.Abp.Cli.ProjectBuilding.Github { [JsonObject] public class GithubRelease diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/IProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/IProjectBuilder.cs similarity index 79% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/IProjectBuilder.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/IProjectBuilder.cs index c037442711..5f8caa3252 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/IProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/IProjectBuilder.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace Volo.Abp.ProjectBuilding +namespace Volo.Abp.Cli.ProjectBuilding { public interface IProjectBuilder { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateInfoProvider.cs similarity index 60% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateInfoProvider.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateInfoProvider.cs index 789e11b341..5330bc9962 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateInfoProvider.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateInfoProvider.cs @@ -1,6 +1,6 @@ -using Volo.Abp.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Building; -namespace Volo.Abp.ProjectBuilding +namespace Volo.Abp.Cli.ProjectBuilding { public interface ITemplateInfoProvider { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateStore.cs new file mode 100644 index 0000000000..064c4eb81b --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateStore.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Cli.ProjectBuilding.Building; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public interface ITemplateStore + { + Task GetAsync( + string name, + DatabaseProvider databaseProvider, + string projectName, + [CanBeNull] string version = null + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs similarity index 88% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildArgs.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs index fffeb87a6c..6767a4e164 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildArgs.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs @@ -1,7 +1,7 @@ using JetBrains.Annotations; -using Volo.Abp.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Building; -namespace Volo.Abp.ProjectBuilding +namespace Volo.Abp.Cli.ProjectBuilding { public class ProjectBuildArgs { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildResult.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildResult.cs similarity index 87% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildResult.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildResult.cs index 2acf036ddc..eb5e524f40 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildResult.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildResult.cs @@ -1,4 +1,4 @@ -namespace Volo.Abp.ProjectBuilding +namespace Volo.Abp.Cli.ProjectBuilding { public class ProjectBuildResult { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuilder.cs similarity index 83% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuilder.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuilder.cs index 3a5c7a6b8a..0b66ea375d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuilder.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; -using Volo.Abp.Cli; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.Utils; using Volo.Abp.DependencyInjection; -using Volo.Abp.ProjectBuilding.Building; -namespace Volo.Abp.ProjectBuilding +namespace Volo.Abp.Cli.ProjectBuilding { public class ProjectBuilder : IProjectBuilder, ITransientDependency { @@ -31,15 +31,16 @@ namespace Volo.Abp.ProjectBuilding } } - var version = CliService.Version; - - var templateFile = await TemplateStore.GetAsync(args.TemplateName, version); + var templateFile = await TemplateStore.GetAsync( + args.TemplateName, + args.DatabaseProvider, + args.SolutionName.FullName + ); var context = new ProjectBuildContext( templateInfo, templateFile, - args, - version + args ); ProjectBuildPipelineBuilder.Build(context).Execute(context); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/SolutionName.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs similarity index 97% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/SolutionName.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs index 0e274f5ceb..ba93774fbe 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/SolutionName.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs @@ -1,6 +1,6 @@ using System; -namespace Volo.Abp.ProjectBuilding +namespace Volo.Abp.Cli.ProjectBuilding { public class SolutionName { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateFile.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateFile.cs new file mode 100644 index 0000000000..234a7b509a --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateFile.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class TemplateFile + { + public string Version { get; } + + public byte[] FileBytes { get; } + + public TemplateFile(byte[] fileBytes, string version) + { + FileBytes = fileBytes; + Version = version; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs similarity index 69% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateInfoProvider.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs index e386bd7ef1..e23e0ebe0e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateInfoProvider.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs @@ -1,23 +1,23 @@ using System; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Templates; using Volo.Abp.DependencyInjection; -using Volo.Abp.ProjectBuilding.Building; -using Volo.Abp.ProjectBuilding.Templates; -namespace Volo.Abp.ProjectBuilding +namespace Volo.Abp.Cli.ProjectBuilding { public class TemplateInfoProvider : ITemplateInfoProvider, ITransientDependency { public TemplateInfo GetDefault() { - return Get(MvcApplicationTemplate.TemplateName); + return Get(MvcTemplate.TemplateName); } public TemplateInfo Get(string name) { switch (name) { - case MvcApplicationTemplate.TemplateName: - return new MvcApplicationTemplate(); + case MvcTemplate.TemplateName: + return new MvcTemplate(); case MvcModuleTemplate.TemplateName: return new MvcModuleTemplate(); case ServiceTemplate.TemplateName: diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcModuleTemplate.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcModuleTemplate.cs similarity index 75% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcModuleTemplate.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcModuleTemplate.cs index f37c967b2f..1f1f5b3c39 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcModuleTemplate.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcModuleTemplate.cs @@ -1,6 +1,6 @@ -using Volo.Abp.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Building; -namespace Volo.Abp.ProjectBuilding.Templates +namespace Volo.Abp.Cli.ProjectBuilding.Templates { public class MvcModuleTemplate : TemplateInfo { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcApplicationTemplate.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcTemplate.cs similarity index 76% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcApplicationTemplate.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcTemplate.cs index 581fe9fcb5..0286a484da 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcApplicationTemplate.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcTemplate.cs @@ -1,17 +1,17 @@ using System.Collections.Generic; -using Volo.Abp.ProjectBuilding.Building; -using Volo.Abp.ProjectBuilding.Building.Steps; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Building.Steps; -namespace Volo.Abp.ProjectBuilding.Templates +namespace Volo.Abp.Cli.ProjectBuilding.Templates { - public class MvcApplicationTemplate : TemplateInfo + public class MvcTemplate : TemplateInfo { /// /// "mvc". /// public const string TemplateName = "mvc"; - public MvcApplicationTemplate() + public MvcTemplate() : base(TemplateName, DatabaseProvider.EntityFrameworkCore) { } @@ -21,6 +21,7 @@ namespace Volo.Abp.ProjectBuilding.Templates var steps = new List(); SwitchDatabaseProvider(context, steps); RemoveOtherDatabaseProviders(context, steps); + ChangeLocalhostPort(steps); return steps; } @@ -47,5 +48,16 @@ namespace Volo.Abp.ProjectBuilding.Templates steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.MongoDB")); } } + + private void ChangeLocalhostPort(List steps) + { + steps.Add( + new ChangeLocalhostPortStep( + "/src/MyCompanyName.MyProjectName.Web/Properties/launchSettings.json", + 53929, + 53932 + ) + ); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/ServiceTemplate.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/ServiceTemplate.cs similarity index 73% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/ServiceTemplate.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/ServiceTemplate.cs index 4b547eead0..425029982c 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/ServiceTemplate.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/ServiceTemplate.cs @@ -1,6 +1,6 @@ -using Volo.Abp.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Building; -namespace Volo.Abp.ProjectBuilding.Templates +namespace Volo.Abp.Cli.ProjectBuilding.Templates { public class ServiceTemplate : TemplateInfo { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Zipping/ZipFileExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Zipping/ZipFileExtensions.cs similarity index 94% rename from framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Zipping/ZipFileExtensions.cs rename to framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Zipping/ZipFileExtensions.cs index 97536bd6ea..0160c8390e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Zipping/ZipFileExtensions.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Zipping/ZipFileExtensions.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Ionic.Zip; -using Volo.Abp.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Files; -namespace Volo.Abp.ProjectBuilding.Zipping +namespace Volo.Abp.Cli.ProjectBuilding.Zipping { public static class ZipFileExtensions { diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DbContextFileBuilderConfigureAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DbContextFileBuilderConfigureAdder.cs new file mode 100644 index 0000000000..0211a42243 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DbContextFileBuilderConfigureAdder.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class DbContextFileBuilderConfigureAdder : ITransientDependency + { + public ILogger Logger { get; set; } + + protected UsingStatementAdder UsingStatementAdder { get; } + + public DbContextFileBuilderConfigureAdder(UsingStatementAdder usingStatementAdder) + { + UsingStatementAdder = usingStatementAdder; + Logger = NullLogger.Instance; + } + + public void Add(string path, string moduleConfiguration) + { + var file = File.ReadAllText(path); + + file = UsingStatementAdder.Add(file, GetNamespace(moduleConfiguration)); + + var stringToAdd = GetLineToAdd(moduleConfiguration); + if (!file.Contains(stringToAdd)) + { + var indexToInsert = FindIndexToInsert(file); + + if (indexToInsert <= 0 || indexToInsert >= file.Length) + { + Logger.LogWarning($"\"OnModelCreating(ModelBuilder builder)\" method couldn't be found in {path}"); + return; + } + file = file.Insert(indexToInsert, " " + stringToAdd + Environment.NewLine + " "); + } + + + File.WriteAllText(path, file); + } + + protected int FindIndexToInsert(string file) + { + var indexOfMethodDeclaration = file.IndexOf("OnModelCreating(", StringComparison.Ordinal); + var indexOfOpeningBracket = indexOfMethodDeclaration + file.Substring(indexOfMethodDeclaration).IndexOf('{'); + + var stack = 1; + var index = indexOfOpeningBracket; + + while (stack > 0) + { + index++; + + if (index >= file.Length) + { + break; + } + + if (file[index] == '{') + { + stack++; + } + else if (file[index] == '}') + { + stack--; + } + } + + return index; + } + + protected string GetLineToAdd(string moduleConfiguration) + { + return "builder." + moduleConfiguration.Split(':')[1] + "();"; + } + + protected string GetNamespace(string moduleConfiguration) + { + return string.Join(".", moduleConfiguration.Split(':')[0]); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DerivedClassFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DerivedClassFinder.cs new file mode 100644 index 0000000000..db39a660ce --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DerivedClassFinder.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class DerivedClassFinder : ITransientDependency + { + public ILogger Logger { get; set; } + + public DerivedClassFinder() + { + Logger = NullLogger.Instance; + } + + public virtual List Find(string csprojFilePath, string baseClass) + { + var moduleFilePaths = new List(); + var csprojFileDirectory = Path.GetDirectoryName(csprojFilePath); + var binFile = Path.Combine(csprojFileDirectory, "bin"); + var objFile = Path.Combine(csprojFileDirectory, "obj"); + + + var csFiles = new DirectoryInfo(csprojFileDirectory) + .GetFiles("*.cs", SearchOption.AllDirectories) + .Where(f => !f.DirectoryName.StartsWith(binFile)) + .Where(f => !f.DirectoryName.StartsWith(objFile)) + .Select(f => f.FullName) + .ToList(); + + foreach (var csFile in csFiles) + { + try + { + if (IsDerived(csFile, baseClass)) + { + moduleFilePaths.Add(csFile); + } + } + catch (Exception) + { + Logger.LogDebug($"Couldn't parse {csFile}."); + } + } + + return moduleFilePaths; + } + + protected bool IsDerived(string csFile, string baseClass) + { + Logger.LogDebug(csFile); + var root = CSharpSyntaxTree.ParseText(File.ReadAllText(csFile)).GetRoot(); + var namespaceSyntax = root.DescendantNodes().OfType().First(); + var classDeclaration = (namespaceSyntax.DescendantNodes().OfType()).First(); + var baseTypeList = classDeclaration.BaseList?.Types.Select(t => t.ToString()).ToList(); + + if (baseTypeList == null) + { + return false; + } + + foreach (var baseType in baseTypeList) + { + if (baseType.Contains('<') && baseType.Substring(0, baseType.IndexOf('<')) == baseClass) + { + return true; + } + if (baseType == baseClass) + { + return true; + } + } + + return false; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationAdder.cs new file mode 100644 index 0000000000..d3c324431b --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationAdder.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; +using System.IO; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class EfCoreMigrationAdder : ITransientDependency + { + public void AddMigration(string csprojFile, string module, bool updateDatabase = true) + { + var moduleName = ParseModuleName(module); + var migrationName = "Added_" + moduleName + "_Module" + GetUniquePostFix(); + + CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(csprojFile) + "\" & dotnet ef migrations add " + migrationName); + + if (updateDatabase) + { + UpdateDatabase(csprojFile); + } + } + + protected void UpdateDatabase(string csprojFile) + { + CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(csprojFile) + "\" & dotnet ef database update"); + } + + protected virtual string ParseModuleName(string fullModuleName) + { + var words = fullModuleName?.Split('.'); + + if (words == null || words.Length <= 1) + { + return ""; + } + + return words[words.Length - 1]; + } + + protected virtual string GetUniquePostFix() + { + return "_" + new Random().Next(1,99999); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleClassDependcyAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleClassDependcyAdder.cs new file mode 100644 index 0000000000..9388ce42fe --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleClassDependcyAdder.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Linq; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class ModuleClassDependcyAdder : ITransientDependency + { + protected UsingStatementAdder UsingStatementAdder { get; } + + public ModuleClassDependcyAdder(UsingStatementAdder usingStatementAdder) + { + UsingStatementAdder = usingStatementAdder; + } + + public virtual void Add(string path, string module) + { + ParseModuleNameAndNameSpace(module, out var nameSpace, out var moduleName); + + var file = File.ReadAllText(path); + + file = UsingStatementAdder.Add(file, nameSpace); + + if (!file.Contains(moduleName) ) + { + file = InsertDependsOnAttribute(file, moduleName); + } + + File.WriteAllText(path, file); + } + + protected virtual string InsertDependsOnAttribute(string file, string moduleName) + { + var indexOfPublicClassDeclaration = GetIndexOfWhereDependsOnWillBeAdded(file); + var dependsOnAttribute = GetDependsOnAttribute(moduleName); + + return file.Insert(indexOfPublicClassDeclaration, dependsOnAttribute); + } + + protected virtual int GetIndexOfWhereDependsOnWillBeAdded(string file) + { + var indexOfPublicClassDeclaration = file.IndexOf("public class", StringComparison.Ordinal); + + if (indexOfPublicClassDeclaration < 0) + { + throw new Exception("\"public class\" declaration not found!"); + } + + return indexOfPublicClassDeclaration; + } + + protected virtual string GetDependsOnAttribute(string moduleName) + { + return "[DependsOn(typeof(" + moduleName + "))]" + Environment.NewLine + " "; + } + + protected virtual string GetUsingStatement(string nameSpace) + { + return "using " + nameSpace + ";"; + } + + protected virtual void ParseModuleNameAndNameSpace(string module, out string nameSpace, out string moduleName) + { + var words = module?.Split('.'); + + if (words == null || words.Length <= 1) + { + nameSpace = null; + moduleName = module; + return; + } + + moduleName = words[words.Length - 1]; + nameSpace = string.Join(".", words.Take(words.Length - 1)); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs new file mode 100644 index 0000000000..d5e7a4e15a --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ModuleInfo.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class ModuleInfo + { + public string Name { get; set; } + + public string DisplayName { get; set; } + + public string EfCoreConfigureMethodName { get; set; } + + public List NugetPackages { get; set; } + + public List NpmPackages { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmApplicationType.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmApplicationType.cs new file mode 100644 index 0000000000..9f58aa52e9 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmApplicationType.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Cli.ProjectModification +{ + [Flags] + public enum NpmApplicationType + { + Mvc = 1 + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackageInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackageInfo.cs new file mode 100644 index 0000000000..47f0f1092d --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackageInfo.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Cli.ProjectModification +{ + public class NpmPackageInfo + { + public string Name { get; set; } + + public NpmApplicationType ApplicationType { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NuGetPackageInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NuGetPackageInfo.cs new file mode 100644 index 0000000000..03c5c5493b --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NuGetPackageInfo.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Cli.ProjectModification +{ + public class NugetPackageInfo + { + public string Name { get; set; } + + public string ModuleClass { get; set; } + + public NuGetPackageTarget Target { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NuGetPackageTarget.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NuGetPackageTarget.cs new file mode 100644 index 0000000000..66467bfa36 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NuGetPackageTarget.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.Cli.ProjectModification +{ + public enum NuGetPackageTarget : byte + { + DomainShared = 1, + Domain = 2, + ApplicationContracts = 3, + Application = 4, + HttpApi = 5, + HttpApiClient = 6, + Web = 7, + EntityFrameworkCore = 8, + MongoDB = 9 + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectFinder.cs new file mode 100644 index 0000000000..548290df5c --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectFinder.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Volo.Abp.Cli.ProjectModification +{ + public static class ProjectFinder + { + public static string FindNuGetTargetProjectFile(string[] projectFiles, NuGetPackageTarget target) + { + if (!projectFiles.Any()) + { + return null; + } + + var assemblyNames = GetAssemblyNames(projectFiles); + + switch (target) + { + case NuGetPackageTarget.Web: + return FindProjectEndsWith(projectFiles, assemblyNames, ".Web"); + case NuGetPackageTarget.EntityFrameworkCore: + return FindProjectEndsWith(projectFiles, assemblyNames, ".EntityFrameworkCore"); + case NuGetPackageTarget.MongoDB: + return FindProjectEndsWith(projectFiles, assemblyNames, ".MongoDB"); + case NuGetPackageTarget.Application: + return FindProjectEndsWith(projectFiles, assemblyNames, ".Application") ?? + FindProjectEndsWith(projectFiles, assemblyNames, ".Web"); + case NuGetPackageTarget.ApplicationContracts: + return FindProjectEndsWith(projectFiles, assemblyNames, ".Application.Contracts"); + case NuGetPackageTarget.Domain: + return FindProjectEndsWith(projectFiles, assemblyNames, ".Domain") ?? + FindProjectEndsWith(projectFiles, assemblyNames, ".Application") ?? + FindProjectEndsWith(projectFiles, assemblyNames, ".Web"); + case NuGetPackageTarget.DomainShared: + return FindProjectEndsWith(projectFiles, assemblyNames, ".Domain.Shared"); + case NuGetPackageTarget.HttpApi: + return FindProjectEndsWith(projectFiles, assemblyNames, ".HttpApi"); + case NuGetPackageTarget.HttpApiClient: + return FindProjectEndsWith(projectFiles, assemblyNames, ".HttpApi.Client"); + default: + throw new ApplicationException($"{nameof(NuGetPackageTarget)}.{target} has not implemented!"); + } + } + + public static string[] FindNpmTargetProjectFile(string[] projectFiles) + { + if (!projectFiles.Any()) + { + return Array.Empty(); + } + + var assemblyNames = GetAssemblyNames(projectFiles); + + var projects = new List(); + + var project = FindProjectEndsWith(projectFiles, assemblyNames, ".Web.Host"); + if (project != null) + { + projects.Add(project); + } + + project = FindProjectEndsWith(projectFiles, assemblyNames, ".Host", ".HttpApi.Host"); + if (project != null) + { + projects.Add(project); + } + + if (projects.Any()) + { + return projects.ToArray(); + } + + project = FindProjectEndsWith(projectFiles, assemblyNames, ".Web"); + if (project != null) + { + projects.Add(project); + } + + return projects.ToArray(); + } + + public static string[] GetProjectFiles(string solutionFile) + { + var baseProjectFolder = GetBaseProjectFolder(solutionFile); + return Directory.GetFiles(baseProjectFolder, "*.csproj", SearchOption.AllDirectories); + } + + public static string[] GetAssemblyNames(string[] projectFiles) + { + return projectFiles.Select(GetAssemblyName).ToArray(); + } + + public static string GetAssemblyName(string projectFile) + { + return projectFile + .Substring(projectFile.LastIndexOf(Path.DirectorySeparatorChar) + 1) + .RemovePostFix(StringComparison.OrdinalIgnoreCase, ".csproj"); + } + + private static string FindProjectEndsWith( + string[] projectFiles, + string[] assemblyNames, + string postfix, + string excludePostfix = null) + { + for (var i = 0; i < assemblyNames.Length; i++) + { + var assemblyName = assemblyNames[i]; + if(assemblyName.EndsWith(postfix, StringComparison.OrdinalIgnoreCase) && + (excludePostfix == null || !assemblyName.EndsWith(excludePostfix))) + { + return projectFiles[i]; + } + } + + return null; + } + + private static string GetBaseProjectFolder(string solutionFile) + { + var baseFolder = Path.GetDirectoryName(solutionFile); + var srcFolder = Path.Combine(baseFolder, "src"); + if (Directory.Exists(srcFolder)) + { + baseFolder = srcFolder; + } + + return baseFolder; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs new file mode 100644 index 0000000000..63e0eb0393 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNpmPackageAdder.cs @@ -0,0 +1,42 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; +using Volo.Abp.IO; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class ProjectNpmPackageAdder : ITransientDependency + { + public ILogger Logger { get; set; } + + public ProjectNpmPackageAdder() + { + Logger = NullLogger.Instance; + } + + public Task AddAsync(string directory, NpmPackageInfo npmPackage) + { + var packageJsonFilePath = Path.Combine(directory, "package.json"); + if (!File.Exists(packageJsonFilePath)) + { + return Task.CompletedTask; + } + + Logger.LogInformation($"Installing '{npmPackage.Name}' package to the project '{packageJsonFilePath}'..."); + + using (DirectoryHelper.ChangeCurrentDirectory(directory)) + { + Logger.LogInformation("yarn add " + npmPackage.Name); + CmdHelper.RunCmd("yarn add " + npmPackage.Name); + + Logger.LogInformation("gulp"); + CmdHelper.RunCmd("gulp"); + } + + return Task.CompletedTask; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs new file mode 100644 index 0000000000..d12e9e2f4c --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; +using Volo.Abp.IO; +using Volo.Abp.Json; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class ProjectNugetPackageAdder : ITransientDependency + { + public ILogger Logger { get; set; } + + protected IJsonSerializer JsonSerializer { get; } + protected ProjectNpmPackageAdder NpmPackageAdder { get; } + protected DerivedClassFinder ModuleClassFinder { get; } + protected ModuleClassDependcyAdder ModuleClassDependcyAdder { get; } + + public ProjectNugetPackageAdder( + IJsonSerializer jsonSerializer, + ProjectNpmPackageAdder npmPackageAdder, + DerivedClassFinder moduleClassFinder, + ModuleClassDependcyAdder moduleClassDependcyAdder) + { + JsonSerializer = jsonSerializer; + NpmPackageAdder = npmPackageAdder; + ModuleClassFinder = moduleClassFinder; + ModuleClassDependcyAdder = moduleClassDependcyAdder; + Logger = NullLogger.Instance; + } + + public async Task AddAsync(string projectFile, string packageName) + { + await AddAsync( + projectFile, + await FindNugetPackageInfoAsync(packageName) + ); + } + + public async Task AddAsync(string projectFile, NugetPackageInfo package) + { + using (DirectoryHelper.ChangeCurrentDirectory(Path.GetDirectoryName(projectFile))) + { + Logger.LogInformation($"Installing '{package.Name}' package to the project '{Path.GetFileNameWithoutExtension(projectFile)}'..."); + + CmdHelper.Run("dotnet", "add package " + package.Name); + + var moduleFiles = ModuleClassFinder.Find(projectFile, "AbpModule"); + if (moduleFiles.Count == 0) + { + throw new CliUsageException($"Could not find a class derived from AbpModule in the project {projectFile}"); + } + + if (moduleFiles.Count > 1) + { + throw new CliUsageException($"There are multiple classes derived from AbpModule in the project {projectFile}: " + moduleFiles.JoinAsString(", ")); + } + + ModuleClassDependcyAdder.Add(moduleFiles.First(), package.ModuleClass); + + Logger.LogInformation("Successfully installed."); + } + } + + protected virtual async Task FindNugetPackageInfoAsync(string moduleName) + { + using (var client = new HttpClient()) + { + var url = "https://localhost:44328/api/app/nugetPackage/byName/?name=" + moduleName; + + var response = await client.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + if (response.StatusCode == HttpStatusCode.NotFound) + { + throw new CliUsageException($"'{moduleName}' nuget package could not be found!"); + } + + throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(responseContent); + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..c62beeacc1 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs @@ -0,0 +1,147 @@ +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class SolutionModuleAdder : ITransientDependency + { + public ILogger Logger { get; set; } + + protected IJsonSerializer JsonSerializer { get; } + protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; } + protected DbContextFileBuilderConfigureAdder DbContextFileBuilderConfigureAdder { get; } + protected EfCoreMigrationAdder EfCoreMigrationAdder { get; } + protected ProjectNpmPackageAdder ProjectNpmPackageAdder { get; } + protected DerivedClassFinder DerivedClassFinder { get; } + + public SolutionModuleAdder( + IJsonSerializer jsonSerializer, + ProjectNugetPackageAdder projectNugetPackageAdder, + DbContextFileBuilderConfigureAdder dbContextFileBuilderConfigureAdder, + EfCoreMigrationAdder efCoreMigrationAdder, + DerivedClassFinder derivedClassFinder, + ProjectNpmPackageAdder projectNpmPackageAdder) + { + EfCoreMigrationAdder = efCoreMigrationAdder; + DerivedClassFinder = derivedClassFinder; + JsonSerializer = jsonSerializer; + ProjectNugetPackageAdder = projectNugetPackageAdder; + DbContextFileBuilderConfigureAdder = dbContextFileBuilderConfigureAdder; + ProjectNpmPackageAdder = projectNpmPackageAdder; + Logger = NullLogger.Instance; + } + + public virtual async Task AddAsync( + [NotNull] string solutionFile, + [NotNull] string moduleName, + bool skipDbMigrations = false) + { + Check.NotNull(solutionFile, nameof(solutionFile)); + Check.NotNull(moduleName, nameof(moduleName)); + + var module = await FindModuleInfoAsync(moduleName); + + Logger.LogInformation($"Installing module '{module.Name}' to the solution '{Path.GetFileNameWithoutExtension(solutionFile)}'"); + + var projectFiles = ProjectFinder.GetProjectFiles(solutionFile); + + foreach (var nugetPackage in module.NugetPackages) + { + var targetProjectFile = ProjectFinder.FindNuGetTargetProjectFile(projectFiles, nugetPackage.Target); + if (targetProjectFile == null) + { + Logger.LogDebug($"Target project is not available for NuGet package '{nugetPackage.Name}'"); + continue; + } + + await ProjectNugetPackageAdder.AddAsync(targetProjectFile, nugetPackage); + } + + if (!module.NpmPackages.IsNullOrEmpty()) + { + var targetProjects = ProjectFinder.FindNpmTargetProjectFile(projectFiles); + if (targetProjects.Any()) + { + foreach (var targetProject in targetProjects) + { + foreach (var npmPackage in module.NpmPackages.Where(p => p.ApplicationType.HasFlag(NpmApplicationType.Mvc))) + { + await ProjectNpmPackageAdder.AddAsync(Path.GetDirectoryName(targetProject), npmPackage); + } + } + } + else + { + Logger.LogDebug("Target project is not available for NPM packages."); + } + } + + ModifyDbContext(projectFiles, module, skipDbMigrations); + } + + protected void ModifyDbContext(string[] projectFiles, ModuleInfo module, bool skipDbMigrations = false) + { + if (string.IsNullOrWhiteSpace(module.EfCoreConfigureMethodName)) + { + return; + } + + var dbMigrationsProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrations.csproj")); + + if (dbMigrationsProject == null) + { + Logger.LogDebug("Solution doesn't have a \".DbMigrations\" project."); + return; + } + + var dbContextFile = DerivedClassFinder.Find(dbMigrationsProject, "AbpDbContext").FirstOrDefault(); + + if (dbContextFile == null) + { + Logger.LogDebug($"{dbMigrationsProject} project doesn't have a class that is derived from \"AbpDbContext\"."); + return; + } + + DbContextFileBuilderConfigureAdder.Add(dbContextFile, module.EfCoreConfigureMethodName); + + + if (!skipDbMigrations) + { + EfCoreMigrationAdder.AddMigration(dbMigrationsProject, module.Name); + } + } + + protected virtual async Task FindModuleInfoAsync(string moduleName) + { + using (var client = new HttpClient()) + { + var url = "https://localhost:44328/api/app/module/byName/?name=" + moduleName; + + var response = await client.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + if (response.StatusCode == HttpStatusCode.NotFound) + { + throw new CliUsageException($"ERROR: '{moduleName}' module could not be found!"); + } + + throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(responseContent); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/UsingStatementAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/UsingStatementAdder.cs new file mode 100644 index 0000000000..aed44cc512 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/UsingStatementAdder.cs @@ -0,0 +1,63 @@ +using System; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectModification +{ + public class UsingStatementAdder : ITransientDependency + { + public string Add(string fileContent, string nameSpace) + { + if (fileContent.Contains(nameSpace)) + { + return fileContent; + } + + var index = GetIndexOfTheEndOfTheLastUsingStatement(fileContent); + + if (index < 0 || index >= fileContent.Length) + { + index = 0; + } + + var usingStatement = GetUsingStatement(nameSpace); + + return fileContent.Insert(index, usingStatement); + } + + protected string GetUsingStatement(string nameSpace) + { + return Environment.NewLine + "using " + nameSpace + ";"; + } + + protected virtual int GetIndexOfTheEndOfTheLastUsingStatement(string fileContent) + { + var indexOfNamespaceDeclaration = fileContent.IndexOf("namespace", StringComparison.Ordinal); + + if (indexOfNamespaceDeclaration < 0) + { + return 0; + } + + fileContent = fileContent.Substring(0, indexOfNamespaceDeclaration); + + var indexOfTheStartOfLastUsingStatement = + fileContent.LastIndexOf("using ", StringComparison.Ordinal); + + if (indexOfTheStartOfLastUsingStatement < 0) + { + return 0; + } + + var indexOfFirstSemiColonAfterLastUsingStatement = + fileContent.Substring(indexOfTheStartOfLastUsingStatement).IndexOf(';'); + + if (indexOfFirstSemiColonAfterLastUsingStatement < 0) + { + return 0; + } + + return indexOfTheStartOfLastUsingStatement + + indexOfFirstSemiColonAfterLastUsingStatement + 1; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs new file mode 100644 index 0000000000..c9225b62f0 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/CmdHelper.cs @@ -0,0 +1,43 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Volo.Abp.Cli.Utils +{ + public static class CmdHelper + { + public static void Run(string file, string arguments) + { + var procStartInfo = new ProcessStartInfo(file, arguments); + Process.Start(procStartInfo).WaitForExit(); + } + + public static void RunCmd(string command) + { + var procStartInfo = new ProcessStartInfo(GetFileName(), GetArguments(command)); + Process.Start(procStartInfo).WaitForExit(); + } + + public static string GetArguments(string command) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return "-c \"" + command + "\""; + } + + //Windows default. + return "/C \"" + command + "\""; + } + + public static string GetFileName() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + //TODO: Test this. it should work for both operaion systems. + return "/bin/bash"; + } + + //Windows default. + return "cmd.exe"; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/PathHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/PathHelper.cs new file mode 100644 index 0000000000..eeb1751c84 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/PathHelper.cs @@ -0,0 +1,23 @@ +using System.IO; +using JetBrains.Annotations; + +namespace Volo.Abp.Cli.Utils +{ + public static class PathHelper + { + public static string NormalizePath([CanBeNull] string path) + { + if (string.IsNullOrEmpty(path)) + { + return path; + } + + if (Path.IsPathRooted(path)) + { + return path; + } + + return Path.Combine(Directory.GetCurrentDirectory(), path); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/AbpIoTemplateStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/AbpIoTemplateStore.cs deleted file mode 100644 index 4715d3558e..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/AbpIoTemplateStore.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Volo.Abp.Cli; -using Volo.Abp.DependencyInjection; -using Volo.Abp.IO; - -namespace Volo.Abp.ProjectBuilding -{ - public class AbpIoTemplateStore : ITemplateStore, ITransientDependency - { - public ILogger Logger { get; set; } - - public AbpIoTemplateStore() - { - Logger = NullLogger.Instance; - } - - public async Task GetAsync(string templateName, string version) - { - var localCacheFolder = Path.Combine(CliPaths.TemplateCache, version); - DirectoryHelper.CreateIfNotExists(localCacheFolder); - - var localCacheFile = Path.Combine(localCacheFolder, templateName + ".zip"); - if (File.Exists(localCacheFile)) - { - Logger.LogInformation("Using cached template: " + templateName + ", version: " + version); - return new TemplateFile(File.ReadAllBytes(localCacheFile)); - } - - Logger.LogInformation("Downloading template: " + templateName + ", version: " + version); - - using (var client = new System.Net.Http.HttpClient()) - { - client.Timeout = TimeSpan.FromMinutes(5); - var downloadUrl = "https://abp.io/downloads/templates/" + version + "/" + templateName + ".zip"; - var fileContents = await client.GetByteArrayAsync(downloadUrl); - File.WriteAllBytes(localCacheFile, fileContents); - return new TemplateFile(fileContents); - } - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateStore.cs deleted file mode 100644 index f09faa8603..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateStore.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.ProjectBuilding -{ - public interface ITemplateStore - { - Task GetAsync(string templateInfoName, string version); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateFile.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateFile.cs deleted file mode 100644 index d1f839e27b..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateFile.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Volo.Abp.ProjectBuilding -{ - public class TemplateFile - { - public byte[] FileBytes { get; } - - public TemplateFile(byte[] fileBytes) - { - FileBytes = fileBytes; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/AddModuleArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/AddModuleArgs.cs deleted file mode 100644 index eba87227f3..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/AddModuleArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -using JetBrains.Annotations; - -namespace Volo.Abp.ProjectModification -{ - public class AddModuleArgs - { - [NotNull] - public string ModuleName { get; set; } - - [CanBeNull] - public string SolutionFile { get; set; } - - [CanBeNull] - public string ProjectFile { get; set; } - - public AddModuleArgs( - [NotNull] string moduleName, - [CanBeNull] string solutionFile = null, - [CanBeNull] string projectFile = null) - { - ModuleName = Check.NotNullOrWhiteSpace(moduleName, nameof(moduleName)); - SolutionFile = solutionFile; - ProjectFile = projectFile; - } - } -} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleAdder.cs deleted file mode 100644 index b0e5f2a353..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleAdder.cs +++ /dev/null @@ -1,137 +0,0 @@ -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Volo.Abp.Cli.Commands; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.ProjectModification -{ - public class ModuleAdder : ITransientDependency - { - public ILogger Logger { get; set; } - - protected ProjectModuleAdder ProjectModuleAdder { get; } - protected SolutionModuleAdder SolutionModuleAdder { get; } - - public ModuleAdder(ProjectModuleAdder projectModuleAdder, SolutionModuleAdder solutionModuleAdder) - { - ProjectModuleAdder = projectModuleAdder; - SolutionModuleAdder = solutionModuleAdder; - - Logger = NullLogger.Instance; - } - - public async Task AddModuleAsync(AddModuleArgs args) - { - ValidateArgs(args); - NormalizeArgs(args); - - if (args.ProjectFile.IsNullOrEmpty() && args.SolutionFile.IsNullOrEmpty()) - { - if (!TryToFillProjectOrSolutionName(args)) - { - Logger.LogWarning("Could not find any project (.csproj) or solution (.sln) file in the current directory!"); - AddCommandHelper.WriteUsage(Logger); - return; - } - } - - if (!args.ProjectFile.IsNullOrEmpty()) - { - await ProjectModuleAdder.AddAsync(args.ProjectFile, args.ModuleName); - } - - if (!args.SolutionFile.IsNullOrEmpty()) - { - await SolutionModuleAdder.AddAsync(args.SolutionFile, args.ModuleName); - } - } - - private bool TryToFillProjectOrSolutionName(AddModuleArgs args) - { - var projectFiles = FindFiles(Directory.GetCurrentDirectory(), ".csproj"); - if (projectFiles.Length == 1) - { - args.ProjectFile = projectFiles.First(); - return true; - } - - if (projectFiles.Length > 1) - { - Logger.LogWarning( - "There are multiple project (.csproj) files in the current directory. Please specify one of the files below:"); - foreach (var projectFile in projectFiles) - { - Logger.LogWarning("* " + projectFile); - } - - Logger.LogWarning("Example:"); - Logger.LogWarning($"abp add {args.ModuleName} -p {projectFiles[0]}"); - return false; - } - - var solutionFiles = FindFiles(Directory.GetCurrentDirectory(), ".sln"); - if (solutionFiles.Length == 1) - { - args.SolutionFile = solutionFiles.First(); - return true; - } - - if (solutionFiles.Length > 1) - { - Logger.LogWarning( - "There are multiple solution (.sln) files in the current directory. Please specify one of the files below:"); - foreach (var solutionFile in solutionFiles) - { - Logger.LogWarning("* " + solutionFile); - } - - Logger.LogWarning("Example:"); - Logger.LogWarning($"abp add {args.ModuleName} -s {solutionFiles[0]}"); - } - - return false; - } - - private void ValidateArgs(AddModuleArgs args) - { - if (!args.ProjectFile.IsNullOrEmpty() && - !args.SolutionFile.IsNullOrEmpty()) - { - throw new ArgumentException("Can not specify a solution name and project name together."); - } - } - - private void NormalizeArgs(AddModuleArgs args) - { - args.ProjectFile = NormalizePath(args.ProjectFile); - args.SolutionFile = NormalizePath(args.SolutionFile); - } - - private static string NormalizePath([CanBeNull] string path) - { - if (string.IsNullOrEmpty(path)) - { - return path; - } - - if (Path.IsPathRooted(path)) - { - return path; - } - - return Path.Combine(Directory.GetCurrentDirectory(), path); - } - - - private string[] FindFiles(string directory, string fileExtension) - { - return Directory.GetFiles(directory, "*" + fileExtension); - } - } -} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectModuleAdder.cs deleted file mode 100644 index 1960da10a5..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectModuleAdder.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.ProjectModification -{ - public class ProjectModuleAdder : ITransientDependency - { - public async Task AddAsync(string projectFile, string moduleName) - { - - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/SolutionModuleAdder.cs deleted file mode 100644 index c78099210c..0000000000 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/SolutionModuleAdder.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.ProjectModification -{ - public class SolutionModuleAdder : ITransientDependency - { - public async Task AddAsync(string solutionFile, string moduleName) - { - - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj index 4f0de79850..f28c2554b2 100644 --- a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj +++ b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj @@ -12,10 +12,9 @@ - + - diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs index 7a3fbdc2bf..d20db3d6dc 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs @@ -11,7 +11,13 @@ namespace Volo.Abp.Cli { public override void ConfigureServices(ServiceConfigurationContext context) { - +#if DEBUG + Configure(options => + { + options.AbpIoWwwUrlRoot = "https://localhost:44328/"; + options.AbpIoAccountUrlRoot = "https://localhost:44333/"; + }); +#endif } } } \ No newline at end of file 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 0bcff277c6..8eefc78f9b 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs @@ -13,6 +13,12 @@ namespace Volo.Abp.Cli Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning) +#if DEBUG + .MinimumLevel.Override("Volo.Abp.Cli", LogEventLevel.Debug) +#else + .MinimumLevel.Override("Volo.Abp.Cli", LogEventLevel.Information) +#endif .Enrich.FromLogContext() .WriteTo.File(Path.Combine(CliPaths.Log, "abp-cli-logs.txt")) .WriteTo.Console() diff --git a/framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs b/framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs index 88cf61b48e..e87ab67527 100644 --- a/framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/IO/AbpStreamExtensions.cs @@ -1,4 +1,6 @@ -namespace System.IO +using System.Threading.Tasks; + +namespace System.IO { public static class AbpStreamExtensions { @@ -10,5 +12,14 @@ return memoryStream.ToArray(); } } + + public static async Task GetAllBytesAsync(this Stream stream) + { + using (var memoryStream = new MemoryStream()) + { + await stream.CopyToAsync(memoryStream); + return memoryStream.ToArray(); + } + } } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs index 6d314d0685..f942f44b0f 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs @@ -122,7 +122,7 @@ namespace Volo.Abp } public static string Length( - string value, + [CanBeNull] string value, [InvokerParameterName] [NotNull] string parameterName, int maxLength, int minLength = 0) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs index c4185cbbed..b864695430 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using JetBrains.Annotations; namespace Volo.Abp.IO @@ -45,5 +46,22 @@ namespace Volo.Abp.IO return IsSubDirectoryOf(parentDirectory, parentOfChild); } + + public static IDisposable ChangeCurrentDirectory(string targetDirectory) + { + var currentDirectory = Directory.GetCurrentDirectory(); + + if (currentDirectory.Equals(targetDirectory, StringComparison.OrdinalIgnoreCase)) + { + return NullDisposable.Instance; + } + + Directory.SetCurrentDirectory(targetDirectory); + + return new DisposeAction(() => + { + Directory.SetCurrentDirectory(currentDirectory); + }); + } } } diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AsyncCrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AsyncCrudAppService.cs index 3d633931b3..151212925a 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AsyncCrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/AsyncCrudAppService.cs @@ -1,10 +1,10 @@ using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; using Volo.Abp.Linq; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Application.Services { @@ -90,6 +90,12 @@ namespace Volo.Abp.Application.Services await CheckCreatePolicyAsync(); var entity = MapToEntity(input); + + if(entity is IMultiTenant && !HasTenantIdProperty(entity)) + { + TryToSetTenantId(entity); + } + await Repository.InsertAsync(entity, autoSave: true); return MapToEntityDto(entity); 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 2750d699a4..2595bc6edd 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 @@ -2,6 +2,7 @@ using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; namespace Volo.Abp.Application.Services { @@ -86,6 +87,12 @@ namespace Volo.Abp.Application.Services CheckCreatePolicy(); var entity = MapToEntity(input); + + if(entity is IMultiTenant && !HasTenantIdProperty(entity)) + { + TryToSetTenantId(entity); + } + Repository.Insert(entity, autoSave: true); return MapToEntityDto(entity); diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppServiceBase.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppServiceBase.cs index 4d878ad6c3..b678362c53 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppServiceBase.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/CrudAppServiceBase.cs @@ -4,6 +4,7 @@ using System.Linq.Dynamic.Core; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectMapping; namespace Volo.Abp.Application.Services @@ -150,5 +151,27 @@ namespace Volo.Abp.Application.Services ObjectMapper.Map(updateInput, entity); } + + protected virtual void TryToSetTenantId(TEntity entity) + { + var tenantId = CurrentTenant.Id; + + if (!tenantId.HasValue) + { + return; + } + + var propertyInfo = entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)); + + if (propertyInfo != null && propertyInfo.GetSetMethod() != null) + { + propertyInfo.SetValue(entity, tenantId, null); + } + } + + protected virtual bool HasTenantIdProperty(TEntity entity) + { + return entity.GetType().GetProperty(nameof(IMultiTenant.TenantId)) != null; + } } } diff --git a/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj b/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj new file mode 100644 index 0000000000..97c236cfc5 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo.Abp.Specifications.csproj @@ -0,0 +1,20 @@ + + + + + + netstandard2.0 + Volo.Abp.Specifications + Volo.Abp.Specifications + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs new file mode 100644 index 0000000000..6f55973be1 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AbpSpecificationsModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.Specifications +{ + public class AbpSpecificationsModule : AbpModule + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs new file mode 100644 index 0000000000..e0d51bd5c3 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndNotSpecification.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the combined specification which indicates that the first specification + /// can be satisifed by the given object whereas the second one cannot. + /// + /// The type of the object to which the specification is applied. + public class AndNotSpecification : CompositeSpecification + { + /// + /// Constructs a new instance of class. + /// + /// The first specification. + /// The second specification. + public AndNotSpecification(ISpecification left, ISpecification right) : base(left, right) + { + } + + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + public override Expression> ToExpression() + { + var rightExpression = Right.ToExpression(); + + var bodyNot = Expression.Not(rightExpression.Body); + var bodyNotExpression = Expression.Lambda>(bodyNot, rightExpression.Parameters); + + return Left.ToExpression().And(bodyNotExpression); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs new file mode 100644 index 0000000000..621287d634 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AndSpecification.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the combined specification which indicates that both of the given + /// specifications should be satisfied by the given object. + /// + /// The type of the object to which the specification is applied. + public class AndSpecification : CompositeSpecification + { + /// + /// Constructs a new instance of class. + /// + /// The first specification. + /// The second specification. + public AndSpecification(ISpecification left, ISpecification right) : base(left, right) + { + } + + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + public override Expression> ToExpression() + { + return Left.ToExpression().And(Right.ToExpression()); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs new file mode 100644 index 0000000000..caf6a5f094 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/AnySpecification.cs @@ -0,0 +1,22 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the specification that can be satisfied by the given object + /// in any circumstance. + /// + /// The type of the object to which the specification is applied. + public sealed class AnySpecification : Specification + { + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + public override Expression> ToExpression() + { + return o => true; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs new file mode 100644 index 0000000000..01c1e46d33 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/CompositeSpecification.cs @@ -0,0 +1,30 @@ +namespace Volo.Abp.Specifications +{ + /// + /// Represents the base class for composite specifications. + /// + /// The type of the object to which the specification is applied. + public abstract class CompositeSpecification : Specification, ICompositeSpecification + { + /// + /// Constructs a new instance of class. + /// + /// The first specification. + /// The second specification. + protected CompositeSpecification(ISpecification left, ISpecification right) + { + Left = left; + Right = right; + } + + /// + /// Gets the first specification. + /// + public ISpecification Left { get; } + + /// + /// Gets the second specification. + /// + public ISpecification Right { get; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs new file mode 100644 index 0000000000..dd169d5320 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionFuncExtender.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the extender for Expression[Func[T, bool]] type. + /// This is part of the solution which solves + /// the expression parameter problem when going to Entity Framework. + /// For more information about this solution please refer to http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx. + /// + public static class ExpressionFuncExtender + { + private static Expression Compose(this Expression first, Expression second, + Func merge) + { + // build parameter map (from parameters of second to parameters of first) + var map = first.Parameters.Select((f, i) => new {f, s = second.Parameters[i]}) + .ToDictionary(p => p.s, p => p.f); + + // replace parameters in the second lambda expression with parameters from the first + var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body); + + // apply composition of lambda expression bodies to parameters from the first expression + return Expression.Lambda(merge(first.Body, secondBody), first.Parameters); + } + + /// + /// Combines two given expressions by using the AND semantics. + /// + /// The type of the object. + /// The first part of the expression. + /// The second part of the expression. + /// The combined expression. + public static Expression> And(this Expression> first, + Expression> second) + { + return first.Compose(second, Expression.AndAlso); + } + + /// + /// Combines two given expressions by using the OR semantics. + /// + /// The type of the object. + /// The first part of the expression. + /// The second part of the expression. + /// The combined expression. + public static Expression> Or(this Expression> first, + Expression> second) + { + return first.Compose(second, Expression.OrElse); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs new file mode 100644 index 0000000000..14c1bff040 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ExpressionSpecification.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the specification which is represented by the corresponding + /// LINQ expression. + /// + /// The type of the object to which the specification is applied. + public class ExpressionSpecification : Specification + { + private readonly Expression> _expression; + + /// + /// Initializes a new instance of ExpressionSpecification<T> class. + /// + /// The LINQ expression which represents the current + /// specification. + public ExpressionSpecification(Expression> expression) + { + _expression = expression; + } + + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + public override Expression> ToExpression() + { + return _expression; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs new file mode 100644 index 0000000000..2c73409d34 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ICompositeSpecification.cs @@ -0,0 +1,19 @@ +namespace Volo.Abp.Specifications +{ + /// + /// Represents that the implemented classes are composite specifications. + /// + /// The type of the object to which the specification is applied. + public interface ICompositeSpecification : ISpecification + { + /// + /// Gets the left side of the specification. + /// + ISpecification Left { get; } + + /// + /// Gets the right side of the specification. + /// + ISpecification Right { get; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs new file mode 100644 index 0000000000..d19e28347e --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecification.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents that the implemented classes are specifications. For more + /// information about the specification pattern, please refer to + /// http://martinfowler.com/apsupp/spec.pdf. + /// + /// The type of the object to which the specification is applied. + public interface ISpecification + { + /// + /// Returns a value which indicates whether the specification + /// is satisfied by the given object. + /// + /// The object to which the specification is applied. + /// True if the specification is satisfied, otherwise false. + bool IsSatisfiedBy(T obj); + + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + Expression> ToExpression(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs new file mode 100644 index 0000000000..d2c61034e0 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ISpecificationParser.cs @@ -0,0 +1,19 @@ +namespace Volo.Abp.Specifications +{ + /// + /// Represents that the implemented classes are specification parsers that + /// parses the given specification to a domain specific criteria object, such + /// as the ICriteria instance in NHibernate. + /// + /// The type of the domain specific criteria. + public interface ISpecificationParser + { + /// + /// Parses the given specification to a domain specific criteria object. + /// + /// The type of the object to which the specification is applied. + /// The specified specification instance. + /// The instance of the domain specific criteria. + TCriteria Parse(ISpecification specification); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs new file mode 100644 index 0000000000..fc2e74fdd6 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NoneSpecification.cs @@ -0,0 +1,22 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the specification that can be satisfied by the given object + /// in no circumstance. + /// + /// The type of the object to which the specification is applied. + public sealed class NoneSpecification : Specification + { + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + public override Expression> ToExpression() + { + return o => false; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs new file mode 100644 index 0000000000..34658885cb --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/NotSpecification.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the specification which indicates the semantics opposite to the given specification. + /// + /// The type of the object to which the specification is applied. + public class NotSpecification : Specification + { + private readonly ISpecification _specification; + + /// + /// Initializes a new instance of class. + /// + /// The specification to be reversed. + public NotSpecification(ISpecification specification) + { + _specification = specification; + } + + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + public override Expression> ToExpression() + { + var expression = _specification.ToExpression(); + return Expression.Lambda>( + Expression.Not(expression.Body), + expression.Parameters + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs new file mode 100644 index 0000000000..a6c85f8326 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/OrSpecification.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the combined specification which indicates that either of the given + /// specification should be satisfied by the given object. + /// + /// The type of the object to which the specification is applied. + public class OrSpecification : CompositeSpecification + { + /// + /// Initializes a new instance of class. + /// + /// The first specification. + /// The second specification. + public OrSpecification(ISpecification left, ISpecification right) : base(left, right) + { + } + + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + public override Expression> ToExpression() + { + return Left.ToExpression().Or(Right.ToExpression()); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs new file mode 100644 index 0000000000..603e80cf69 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the parameter rebinder used for rebinding the parameters + /// for the given expressions. This is part of the solution which solves + /// the expression parameter problem when going to Entity Framework. + /// For more information about this solution please refer to http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx. + /// + internal class ParameterRebinder : ExpressionVisitor + { + private readonly Dictionary _map; + + internal ParameterRebinder(Dictionary map) + { + _map = map ?? new Dictionary(); + } + + internal static Expression ReplaceParameters(Dictionary map, + Expression exp) + { + return new ParameterRebinder(map).Visit(exp); + } + + protected override Expression VisitParameter(ParameterExpression p) + { + if (_map.TryGetValue(p, out var replacement)) + { + p = replacement; + } + + return base.VisitParameter(p); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs new file mode 100644 index 0000000000..b1ab72caea --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/Specification.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq.Expressions; + +namespace Volo.Abp.Specifications +{ + /// + /// Represents the base class for specifications. + /// + /// The type of the object to which the specification is applied. + public abstract class Specification : ISpecification + { + /// + /// Returns a value which indicates whether the specification + /// is satisfied by the given object. + /// + /// The object to which the specification is applied. + /// True if the specification is satisfied, otherwise false. + public virtual bool IsSatisfiedBy(T obj) + { + return ToExpression().Compile()(obj); + } + + /// + /// Gets the LINQ expression which represents the current specification. + /// + /// The LINQ expression. + public abstract Expression> ToExpression(); + + /// + /// Implicitly converts a specification to expression. + /// + /// + public static implicit operator Expression>(Specification specification) + { + return specification.ToExpression(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs new file mode 100644 index 0000000000..229b3e7a72 --- /dev/null +++ b/framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/SpecificationExtensions.cs @@ -0,0 +1,72 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Specifications +{ + public static class SpecificationExtensions + { + /// + /// Combines the current specification instance with another specification instance + /// and returns the combined specification which represents that both the current and + /// the given specification must be satisfied by the given object. + /// + /// The specification + /// The specification instance with which the current specification is combined. + /// The combined specification instance. + public static ISpecification And([NotNull] this ISpecification specification, + [NotNull] ISpecification other) + { + Check.NotNull(specification, nameof(specification)); + Check.NotNull(other, nameof(other)); + + return new AndSpecification(specification, other); + } + + /// + /// Combines the current specification instance with another specification instance + /// and returns the combined specification which represents that either the current or + /// the given specification should be satisfied by the given object. + /// + /// The specification + /// The specification instance with which the current specification + /// is combined. + /// The combined specification instance. + public static ISpecification Or([NotNull] this ISpecification specification, + [NotNull] ISpecification other) + { + Check.NotNull(specification, nameof(specification)); + Check.NotNull(other, nameof(other)); + + return new OrSpecification(specification, other); + } + + /// + /// Combines the current specification instance with another specification instance + /// and returns the combined specification which represents that the current specification + /// should be satisfied by the given object but the specified specification should not. + /// + /// The specification + /// The specification instance with which the current specification + /// is combined. + /// The combined specification instance. + public static ISpecification AndNot([NotNull] this ISpecification specification, + [NotNull] ISpecification other) + { + Check.NotNull(specification, nameof(specification)); + Check.NotNull(other, nameof(other)); + + return new AndNotSpecification(specification, other); + } + + /// + /// Reverses the current specification instance and returns a specification which represents + /// the semantics opposite to the current specification. + /// + /// The reversed specification instance. + public static ISpecification Not([NotNull] this ISpecification specification) + { + Check.NotNull(specification, nameof(specification)); + + return new NotSpecification(specification); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs b/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs index b6d60cd848..6217e82805 100644 --- a/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs +++ b/framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using System.IO; using System.Text; +using System.Threading.Tasks; using Volo.Abp; namespace Microsoft.Extensions.FileProviders @@ -30,5 +31,31 @@ namespace Microsoft.Extensions.FileProviders } } } + + /// + /// Reads file content as byte[]. + /// + public static byte[] ReadBytes([NotNull] this IFileInfo fileInfo) + { + Check.NotNull(fileInfo, nameof(fileInfo)); + + using (var stream = fileInfo.CreateReadStream()) + { + return stream.GetAllBytes(); + } + } + + /// + /// Reads file content as byte[]. + /// + public static async Task ReadBytesAsync([NotNull] this IFileInfo fileInfo) + { + Check.NotNull(fileInfo, nameof(fileInfo)); + + using (var stream = fileInfo.CreateReadStream()) + { + return await stream.GetAllBytesAsync(); + } + } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DynamicForms.cshtml.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DynamicForms.cshtml.cs index c61423c6a6..adfd91c688 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DynamicForms.cshtml.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Pages/Components/DynamicForms.cshtml.cs @@ -123,6 +123,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components [Display(Name = "My Car Type")] public CarType MyCarType { get; set; } + [Required] + [Display(Name = "My Car Type Nullable")] + public CarType? MyCarTypeNullable { get; set; } + [Required] [AbpRadioButton(Inline = true)] [Display(Name = "Your Car Type")] @@ -139,6 +143,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components [SelectItems(nameof(CountryList))] [Display(Name = "Neighbor Countries")] public List NeighborCountries { get; set; } + + [DynamicFormIgnore] + public string IgnoredProperty { get; set; } } public enum CarType diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs index e786c24f29..4ee4949d50 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Program.cs @@ -1,21 +1,46 @@ -using System.IO; +using System; +using System.IO; using Microsoft.AspNetCore.Hosting; +using Serilog; +using Serilog.Events; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo { public class Program { - public static void Main(string[] args) + public static int Main(string[] args) { - BuildWebHostInternal(args).Run(); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() //TODO: Should be configurable! + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.File("Logs/logs.txt") + .CreateLogger(); + + try + { + Log.Information("Starting web host."); + BuildWebHostInternal(args).Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } } - public static IWebHost BuildWebHostInternal(string[] args) => + internal static IWebHost BuildWebHostInternal(string[] args) => new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() + .UseSerilog() .Build(); } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Properties/launchSettings.json b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Properties/launchSettings.json index bf3d843c8b..07a310a660 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Properties/launchSettings.json +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Properties/launchSettings.json @@ -1,9 +1,9 @@ { "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, + "windowsAuthentication": false, + "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:49211/", + "applicationUrl": "http://localhost:51339", "sslPort": 0 } }, @@ -15,13 +15,13 @@ "ASPNETCORE_ENVIRONMENT": "Development" } }, - "Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo": { + "Volo.AbpIo.Commercial.Web": { "commandName": "Project", "launchBrowser": true, + "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:49212/" + } } } -} +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs index 52c11d7896..ffd3ca47be 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Startup.cs @@ -1,5 +1,4 @@ using System; -using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj index dd5e36ed14..c5a4731256 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.csproj @@ -2,24 +2,12 @@ netcoreapp2.2 - $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; - Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo - Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo - true - true - false - false - false - true - true - false - - + - + diff --git a/framework/test/Volo.Abp.Cli.Tests/Volo.Abp.Cli.Tests.csproj b/framework/test/Volo.Abp.Cli.Core.Tests/Volo.Abp.Cli.Core.Tests.csproj similarity index 80% rename from framework/test/Volo.Abp.Cli.Tests/Volo.Abp.Cli.Tests.csproj rename to framework/test/Volo.Abp.Cli.Core.Tests/Volo.Abp.Cli.Core.Tests.csproj index 3f094594d0..bb2664cdc1 100644 --- a/framework/test/Volo.Abp.Cli.Tests/Volo.Abp.Cli.Tests.csproj +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo.Abp.Cli.Core.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestBase.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestBase.cs similarity index 100% rename from framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestBase.cs rename to framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestBase.cs diff --git a/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestModule.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestModule.cs similarity index 84% rename from framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestModule.cs rename to framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestModule.cs index 8c2101052b..3fc7dcd457 100644 --- a/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestModule.cs +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestModule.cs @@ -4,7 +4,7 @@ namespace Volo.Abp.Cli { [DependsOn( typeof(AbpTestBaseModule), - typeof(AbpCliModule) + typeof(AbpCliCoreModule) )] public class AbpCliTestModule : AbpModule { diff --git a/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs similarity index 100% rename from framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs rename to framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectFinder_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectFinder_Tests.cs new file mode 100644 index 0000000000..5576960dfa --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectFinder_Tests.cs @@ -0,0 +1,46 @@ +using System.IO; +using Shouldly; +using Volo.Abp.Cli.ProjectModification; +using Xunit; + +namespace Volo.Abp.Cli +{ + public class ProjectFinder_Tests + { + [Fact] + public void FindNpmTargetProjectFile_Tests() + { + var npmTargets = ProjectFinder.FindNpmTargetProjectFile( + new [] + { + GetNormalizedPath(@"c:\temp\project\folder\src\Acme.PhoneBook.Host.csproj"), + GetNormalizedPath(@"c:\temp\project\folder\src\Acme.PhoneBook.Web.Host.csproj"), + GetNormalizedPath(@"c:\temp\project\folder\src\Acme.PhoneBook.HttpApi.Host.csproj"), + GetNormalizedPath(@"c:\temp\project\folder\src\Acme.PhoneBook.Application.Contracts.csproj"), + GetNormalizedPath(@"c:\temp\project\folder\src\Acme.PhoneBook.Application.csproj"), + GetNormalizedPath(@"c:\temp\project\folder\src\Acme.PhoneBook.EntityFrameworkCore.csproj") + } + ); + + npmTargets.ShouldContain(GetNormalizedPath(@"c:\temp\project\folder\src\Acme.PhoneBook.Host.csproj")); + npmTargets.ShouldContain(GetNormalizedPath(@"c:\temp\project\folder\src\Acme.PhoneBook.Web.Host.csproj")); + } + + [Fact] + public void GetAssemblyName_Tests() + { + var assemblyName = ProjectFinder.GetAssemblyName( + GetNormalizedPath( + @"c:\temp\project\folder\src\Acme.PhoneBook.Host.csproj" + ) + ); + + assemblyName.ShouldBe("Acme.PhoneBook.Host"); + } + + private static string GetNormalizedPath(string path) + { + return path.Replace('\\', Path.DirectorySeparatorChar); + } + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Specifications_Tests.cs new file mode 100644 index 0000000000..d9de34f1ce --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Repository_Specifications_Tests.cs @@ -0,0 +1,8 @@ +using Volo.Abp.TestApp.Testing; + +namespace Volo.Abp.EntityFrameworkCore.Repositories +{ + public class Repository_Specifications_Tests : Repository_Specifications_Tests + { + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Repositories/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Repositories/Repository_Specifications_Tests.cs new file mode 100644 index 0000000000..e3183127a0 --- /dev/null +++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/Repositories/Repository_Specifications_Tests.cs @@ -0,0 +1,8 @@ +using Volo.Abp.TestApp.Testing; + +namespace Volo.Abp.MemoryDb.Repositories +{ + public class Repository_Specifications_Tests : Repository_Specifications_Tests + { + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs new file mode 100644 index 0000000000..6529b4c5a9 --- /dev/null +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Repositories/Repository_Specifications_Tests.cs @@ -0,0 +1,8 @@ +using Volo.Abp.TestApp.Testing; + +namespace Volo.Abp.MongoDB.Repositories +{ + public class Repository_Specifications_Tests : Repository_Specifications_Tests + { + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj b/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj new file mode 100644 index 0000000000..6f2cbe09f8 --- /dev/null +++ b/framework/test/Volo.Abp.Specifications.Tests/Volo.Abp.Specifications.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.2 + latest + Volo.Abp.Specifications.Tests + Volo.Abp.Specifications.Tests + true + false + false + false + + + + + + + + + + \ No newline at end of file diff --git a/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs b/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs new file mode 100644 index 0000000000..dbe4fc6b5d --- /dev/null +++ b/framework/test/Volo.Abp.Specifications.Tests/Volo/Abp/Specifications/Specifications_Tests.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Specifications +{ + public class Specification_Tests + { + private readonly IQueryable _customers; + + public Specification_Tests() + { + _customers = new List + { + new Customer("John", 17, 47000, "England"), + new Customer("Tuana", 2, 500, "Turkey"), + new Customer("Martin", 43, 16000, "USA"), + new Customer("Lee", 32, 24502, "China"), + new Customer("Douglas", 42, 42000, "England"), + new Customer("Abelard", 14, 2332, "German"), + new Customer("Neo", 16, 120000, "USA"), + new Customer("Daan", 39, 6000, "Netherlands"), + new Customer("Alessandro", 22, 8271, "Italy"), + new Customer("Noah", 33, 82192, "Belgium") + }.AsQueryable(); + } + + [Fact] + public void Any_Should_Return_All() + { + _customers + .Where(new AnySpecification()) //Implicitly converted to Expression! + .Count() + .ShouldBe(_customers.Count()); + } + + [Fact] + public void None_Should_Return_Empty() + { + _customers + .Where(new NoneSpecification().ToExpression()) + .Count() + .ShouldBe(0); + } + + [Fact] + public void Not_Should_Return_Reverse_Result() + { + _customers + .Where(new EuropeanCustomerSpecification().Not().ToExpression()) + .Count() + .ShouldBe(3); + } + + [Fact] + public void Should_Support_Native_Expressions_And_Combinations() + { + _customers + .Where(new ExpressionSpecification(c => c.Age >= 18).ToExpression()) + .Count() + .ShouldBe(6); + + _customers + .Where(new EuropeanCustomerSpecification().And(new ExpressionSpecification(c => c.Age >= 18)).ToExpression()) + .Count() + .ShouldBe(4); + } + + [Fact] + public void CustomSpecification_Test() + { + _customers + .Where(new EuropeanCustomerSpecification().ToExpression()) + .Count() + .ShouldBe(7); + + _customers + .Where(new Age18PlusCustomerSpecification().ToExpression()) + .Count() + .ShouldBe(6); + + _customers + .Where(new BalanceCustomerSpecification(10000, 30000).ToExpression()) + .Count() + .ShouldBe(2); + + _customers + .Where(new PremiumCustomerSpecification().ToExpression()) + .Count() + .ShouldBe(3); + } + + [Fact] + public void IsSatisfiedBy_Tests() + { + new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 49, 55000, "USA")).ShouldBeTrue(); + + new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 49, 200, "USA")).ShouldBeFalse(); + new PremiumCustomerSpecification().IsSatisfiedBy(new Customer("David", 12, 55000, "USA")).ShouldBeFalse(); + } + + [Fact] + public void CustomSpecification_Composite_Tests() + { + _customers + .Where(new EuropeanCustomerSpecification().And(new Age18PlusCustomerSpecification()).ToExpression()) + .Count() + .ShouldBe(4); + + _customers + .Where(new EuropeanCustomerSpecification().Not().And(new Age18PlusCustomerSpecification()).ToExpression()) + .Count() + .ShouldBe(2); + + _customers + .Where(new Age18PlusCustomerSpecification().AndNot(new EuropeanCustomerSpecification()).ToExpression()) + .Count() + .ShouldBe(2); + } + + private class Customer + { + public string Name { get; private set; } + + public byte Age { get; private set; } + + public long Balance { get; private set; } + + public string Location { get; private set; } + + public Customer(string name, byte age, long balance, string location) + { + Name = name; + Age = age; + Balance = balance; + Location = location; + } + } + + private class EuropeanCustomerSpecification : Specification + { + public override Expression> ToExpression() + { + return c => c.Location == "England" || + c.Location == "Turkey" || + c.Location == "German" || + c.Location == "Netherlands" || + c.Location == "Italy" || + c.Location == "Belgium"; + } + } + + private class Age18PlusCustomerSpecification : Specification + { + public override Expression> ToExpression() + { + return c => c.Age >= 18; + } + } + + private class BalanceCustomerSpecification : Specification + { + public int MinBalance { get; } + + public int MaxBalance { get; } + + public BalanceCustomerSpecification(int minBalance, int maxBalance) + { + MinBalance = minBalance; + MaxBalance = maxBalance; + } + + public override Expression> ToExpression() + { + return c => c.Balance >= MinBalance && c.Balance <= MaxBalance; + } + } + + private class PremiumCustomerSpecification : AndSpecification + { + public PremiumCustomerSpecification() + : base(new Age18PlusCustomerSpecification(), new BalanceCustomerSpecification(20000, int.MaxValue)) + { + } + } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj b/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj index 0cb921f63a..4e7b58ee84 100644 --- a/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj +++ b/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Specifications_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Specifications_Tests.cs new file mode 100644 index 0000000000..a3e7907135 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/Repository_Specifications_Tests.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using Shouldly; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Modularity; +using Volo.Abp.Specifications; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.TestApp.Testing +{ + public abstract class Repository_Specifications_Tests : TestAppTestBase + where TStartupModule : IAbpModule + { + protected readonly IRepository CityRepository; + + protected Repository_Specifications_Tests() + { + CityRepository = GetRequiredService>(); + } + + [Fact] + public void SpecificationWithRepository_Test() + { + WithUnitOfWork(() => + { + CityRepository.Count(new CitySpecification().ToExpression()).ShouldBe(1); + }); + } + } + + public class CitySpecification : Specification + { + public override Expression> ToExpression() + { + return city => city.Name == "Istanbul"; + } + } +} \ No newline at end of file diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs index 59f3ae5431..ecb07300f0 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/AuditingStore.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Volo.Abp.Auditing; using Volo.Abp.DependencyInjection; using Volo.Abp.Guids; @@ -16,29 +17,42 @@ namespace Volo.Abp.AuditLogging private readonly IAuditLogRepository _auditLogRepository; private readonly IGuidGenerator _guidGenerator; private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly AbpAuditingOptions Options; public AuditingStore( IAuditLogRepository auditLogRepository, IGuidGenerator guidGenerator, - IUnitOfWorkManager unitOfWorkManager) + IUnitOfWorkManager unitOfWorkManager, + IOptions options) { _auditLogRepository = auditLogRepository; _guidGenerator = guidGenerator; _unitOfWorkManager = unitOfWorkManager; + Options = options.Value; Logger = NullLogger.Instance; } + private async Task SaveLogAsync(AuditLogInfo auditInfo) + { + using (var uow = _unitOfWorkManager.Begin(requiresNew: true)) + { + var auditLog = new AuditLog(_guidGenerator, auditInfo); + await _auditLogRepository.InsertAsync(auditLog); + await uow.SaveChangesAsync(); + } + } public async Task SaveAsync(AuditLogInfo auditInfo) { + if (!Options.HideErrors) + { + await SaveLogAsync(auditInfo); + return; + } + try { - using (var uow = _unitOfWorkManager.Begin(requiresNew: true)) - { - var auditLog = new AuditLog(_guidGenerator, auditInfo); - await _auditLogRepository.InsertAsync(auditLog); - await uow.SaveChangesAsync(); - } + await SaveLogAsync(auditInfo); } catch (Exception ex) { diff --git a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs index 425fb58575..acc400ef3f 100644 --- a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs +++ b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs @@ -54,7 +54,7 @@ namespace Volo.Blogging.EntityFrameworkCore b.Property(x => x.Url).IsRequired().HasMaxLength(PostConsts.MaxUrlLength).HasColumnName(nameof(Post.Url)); b.Property(x => x.Content).IsRequired(false).HasMaxLength(PostConsts.MaxContentLength).HasColumnName(nameof(Post.Content)); - b.HasMany(question => question.Tags).WithOne().HasForeignKey(qt => qt.PostId); + b.HasMany(p => p.Tags).WithOne().HasForeignKey(qt => qt.PostId); b.HasOne().WithMany().IsRequired().HasForeignKey(p => p.BlogId); }); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Index.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Index.cshtml index c2aebc7192..6526c79ef1 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Index.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Index.cshtml @@ -166,7 +166,7 @@ @post.Title

- @(GetShortContent(post.Content)) + @Html.Raw(GetShortContent(post.Content))