diff --git a/.gitignore b/.gitignore index a6f0dac3dd..742c5b4584 100644 --- a/.gitignore +++ b/.gitignore @@ -294,3 +294,4 @@ samples/MicroserviceDemo/gateways/BackendAdminAppGateway.Host/Logs/logs.txt samples/MicroserviceDemo/applications/PublicWebSite.Host/Logs/logs.txt samples/MicroserviceDemo/gateways/PublicWebSiteGateway.Host/Logs/logs.txt samples/MicroserviceDemo/microservices/BloggingService.Host/Logs/logs.txt +modules/docs/app/Volo.DocsTestApp/Logs/logs.txt diff --git a/abp_io/README.md b/abp_io/README.md index 2095b52ef6..813329cbea 100644 --- a/abp_io/README.md +++ b/abp_io/README.md @@ -1,2 +1,2 @@ -## abp.op +## abp.io Source code of the official web site: https://abp.io diff --git a/abp_io/src/Volo.AbpWebSite.Domain/Volo.AbpWebSite.Domain.csproj b/abp_io/src/Volo.AbpWebSite.Domain/Volo.AbpWebSite.Domain.csproj index b0c9926b35..78d1a7ac96 100644 --- a/abp_io/src/Volo.AbpWebSite.Domain/Volo.AbpWebSite.Domain.csproj +++ b/abp_io/src/Volo.AbpWebSite.Domain/Volo.AbpWebSite.Domain.csproj @@ -12,7 +12,7 @@ - + diff --git a/abp_io/src/Volo.AbpWebSite.Web/publish-ignore.json b/abp_io/src/Volo.AbpWebSite.Web/publish-ignore.json index 89e3176a69..c5e2fd7c51 100644 --- a/abp_io/src/Volo.AbpWebSite.Web/publish-ignore.json +++ b/abp_io/src/Volo.AbpWebSite.Web/publish-ignore.json @@ -3,6 +3,7 @@ "TemplateFiles/": {}, "Logs/": {}, "wwwroot/files/": {}, + "wwwroot/downloads/": {}, "appsettings.json": {}, "web.config": {} } diff --git a/abp_io/src/Volo.Utils.SolutionTemplating/Volo.Utils.SolutionTemplating.csproj b/abp_io/src/Volo.Utils.SolutionTemplating/Volo.Utils.SolutionTemplating.csproj index 254d8b65c5..10004ae55e 100644 --- a/abp_io/src/Volo.Utils.SolutionTemplating/Volo.Utils.SolutionTemplating.csproj +++ b/abp_io/src/Volo.Utils.SolutionTemplating/Volo.Utils.SolutionTemplating.csproj @@ -15,7 +15,7 @@ - + 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/build-all.ps1 b/build-all.ps1 index 8393efd9fb..7a23e66eda 100644 --- a/build-all.ps1 +++ b/build-all.ps1 @@ -20,8 +20,7 @@ $solutionPaths = ( "modules/background-jobs", "modules/client-simulation", "abp_io", - "templates/module", - "templates/service", + "templates/mvc-module", "templates/mvc", "samples/MicroserviceDemo" ) diff --git a/common.props b/common.props index dce50d895e..1677ac8e8b 100644 --- a/common.props +++ b/common.props @@ -1,7 +1,7 @@ latest - 0.16.0 + 0.18.0 $(NoWarn);CS1591 https://abp.io/assets/abp_nupkg.png https://abp.io diff --git a/docs/en/Apps/VoloDocs.md b/docs/en/Apps/VoloDocs.md index 936f7f10d4..dbbd3712d2 100644 --- a/docs/en/Apps/VoloDocs.md +++ b/docs/en/Apps/VoloDocs.md @@ -21,13 +21,21 @@ https://github.com/abpframework/abp/tree/master/modules/docs ## Download -You can download the VoloDocs release from the following link: +You can download the VoloDocs release from the following links: -http://apps.abp.io/VoloDocs/VoloDocs.zip *(will be ready soon...)* +http://apps.abp.io/VoloDocs/VoloDocs.win-x64.zip - **Windows 64 bit** + +http://apps.abp.io/VoloDocs/VoloDocs.win-x86.zip - **Windows 32 bit** + +http://apps.abp.io/VoloDocs/VoloDocs.osx-x64.zip - **MacOS** + +http://apps.abp.io/VoloDocs/VoloDocs.linux-x64.zip - **Linux** + +Notice that, all installations are self-contained deployments. It means all the required third-party dependencies along with the version of .NET Core is included. So you don't need to install any .NET Core SDK / Runtime. ## Folder Structure -When you extract the `VoloDocs.zip` file, you will see a `Web` folder and a `Migrator` folder. `Web` folder contains the website files and `Migrator` contains the application to build your database. Before publishing your website, you need to create a new database or update your existing database to the latest. If this is the first time you install VoloDocs, `Migrator` will create a new database for you, otherwise it updates to the latest version. The only setting you need to configure, is the `ConnectionString` which is located in the `appsettings.json` file. See the next section to learn how to configure your VoloDocs application. +When you extract the `VoloDocs.*.zip` file, you will see a `Web` folder and a `Migrator` folder. The `Web` folder contains the website files and `Migrator` contains the application to build your database. Before publishing your website, you need to create a new database or update your existing database to the latest. If this is the first time you install VoloDocs, `Migrator` will create a new database for you, otherwise it updates to the latest version. The only setting you need to configure, is the `ConnectionString` which is located in the `appsettings.json` file. See the next section to learn how to configure your VoloDocs application. ## Steps by Step Deployment @@ -45,11 +53,11 @@ When you extract the `VoloDocs.zip` file, you will see a `Web` folder and a `Mig If you want to run -- ### Publishing Website +- ### Deploying Website In the previous step, you created or updated your database. Ensure that your database exists on the specified connection string. - - #### Publishing to IIS + - #### Deploying to IIS - Move `Web` folder to your `wwwroot ` folder. - Rename `Web` folder to `VoloDocs` (Now you have `C:\inetpub\wwwroot\VoloDocs`).![Add IIS Website](../images/volodocs-iis-add-website.png) @@ -69,17 +77,13 @@ When you extract the `VoloDocs.zip` file, you will see a `Web` folder and a `Mig - Further information about hosting VoloDocs check out [Microsoft's official document for hosting ASP.NET Core application on IIS](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis). - - #### Publishing to Azure + - #### Deploying to Azure Microsoft has a good document on how to deploy your ASP.NET Core web app to Azure App Service. We recommend you to read this document https://docs.microsoft.com/en-us/azure/app-service/app-service-web-get-started-dotnet. - #### Running the Application From Command Line - Alternatively you can run the application from command line, navigate to `VoloDocs\Web` folder and run the below command in your command line for Windows: - - ```powershell - dotnet VoloDocs.Web.dll - ``` + Alternatively you can run the application from command line, navigate to `VoloDocs\Web` folder and run `VoloDocs.Web.exe` for Windows or `VoloDocs.Web` for MacOS / Linux. - ### First Run @@ -165,6 +169,9 @@ After you save the project, go to root website address and you will see your doc `http:///documents` +### Any Issues? +If you encounter any problem or issues about installation, usage or report a bug, follow the link: +https://github.com/abpframework/abp/issues/new 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/Application-Services.md b/docs/en/Best-Practices/Application-Services.md index 7624464e17..0979304931 100644 --- a/docs/en/Best-Practices/Application-Services.md +++ b/docs/en/Best-Practices/Application-Services.md @@ -203,12 +203,13 @@ This method votes a question and returns the current score of the question. #### Manipulating / Deleting Entities * **Do** always get all the related entities from repositories to perform the operations on them. +* **Do** call repository's Update/UpdateAsync method after updating an entity. Because, not all database APIs support change tracking & auto update. #### Using Other Application Services * **Do not** use other application services of the same module/application. Instead; * Use domain layer to perform the required task. - * Extract a new class and share between the application services to accomplish the code reuse when necessary. + * Extract a new class and share between the application services to accomplish the code reuse when necessary. But be careful to don't couple two use cases. They may seem similar at the beginning, but may evolve to different directions by time. So, use code sharing carefully. * **Can** use application services of others only if; * They are parts of another module / microservice. * The current module has only reference to the application contracts of the used module. 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/Dependency-Injection.md b/docs/en/Dependency-Injection.md index 5d8ddbf94e..eb0da4a0e4 100644 --- a/docs/en/Dependency-Injection.md +++ b/docs/en/Dependency-Injection.md @@ -96,7 +96,7 @@ public class TaxCalculator : ITransientDependency Another way of configuring a service for dependency injection is to use ``DependencyAttribute``. It has the following properties: * ``Lifetime``: Lifetime of the registration: ``Singleton``, ``Transient`` or ``Scoped``. -* ``TryRegister``: Set ``true`` to register the service only it's not registered before. Uses TryAdd... extension methods of IServiceCollection. +* ``TryRegister``: Set ``true`` to register the service only if it's not registered before. Uses TryAdd... extension methods of IServiceCollection. * ``ReplaceServices``: Set ``true`` to replace services if they are already registered before. Uses Replace extension method of IServiceCollection. Example: diff --git a/docs/en/Entities.md b/docs/en/Entities.md index a90b95d100..3a946461e7 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -22,7 +22,7 @@ public class Person : Entity } ``` -> If you do not want derive your entity from the base `Entity` class, you can directly implement `IEntity` interface. +> If you do not want to derive your entity from the base `Entity` class, you can directly implement `IEntity` interface. `Entity` class just defines an `Id` property with the given primary **key type**, which is `int` in the sample above. It can be other types like `string`, `Guid`, `long` or whatever you need. @@ -57,9 +57,9 @@ For the example above, the composite key is composed of `UserId` and `RoleId`. F Entities with composite keys should implement the `GetKeys()` method as shown above. -Notice that you also need to define keys of the entity in your **object-to-relational mapping** (ORM) configuration. +Notice that you also need to define keys of the entity in your **object-relational mapping** (ORM) configuration. -> Composite primary keys has a restriction with repositories. Since it has not known Id property, you can not use `IRepository` for these entities. However, you can always use `IRepository`. See repository documentation (TODO: link) for more. +> Composite primary keys has a restriction with repositories. Since it has not known Id property, you can not use `IRepository` for these entities. However, you can always use `IRepository`. See [repositories documentation](Repositories.md) for more. ### AggregateRoot Class @@ -67,7 +67,7 @@ Notice that you also need to define keys of the entity in your **object-to-relat `AggregateRoot` class extends the `Entity` class. So, it also has an `Id` property by default. -> Notice that ABP creates default repositories only for aggregate roots by default. However, it's possible to include all entities. See repository documentation (TODO: link) for more. +> Notice that ABP creates default repositories only for aggregate roots by default. However, it's possible to include all entities. See [repositories documentation](Repositories.md) for more. ABP does not force you to use aggregate roots, you can in fact use the `Entity` class as defined before. However, if you want to implement DDD and want to create aggregate root classes, there are some best practices you may want to consider: @@ -160,7 +160,7 @@ public class OrderLine : Entity > If you do not want to derive your aggregate root from the base `AggregateRoot` class, you can directly implement the `IAggregateRoot` interface. -`Order` is an **aggregate root** with `Guid` type `Id` property. It has a collection of `OrderLine` entities. `OrderLine` is another entity with a composite primary key (`OrderLine` and ` ProductId`). +`Order` is an **aggregate root** with `Guid` type `Id` property. It has a collection of `OrderLine` entities. `OrderLine` is another entity with a composite primary key (`OrderId` and ` ProductId`). While this example may not implement all the best practices of an aggregate root, it still follows good practices: diff --git a/docs/en/Exception-Handling.md b/docs/en/Exception-Handling.md index 896f26d723..93bad2b6ed 100644 --- a/docs/en/Exception-Handling.md +++ b/docs/en/Exception-Handling.md @@ -9,7 +9,7 @@ ABP provides a built-in infrastructure and offers a standard model for handling ### Automatic Exception Handling -`AbpExceptionFilter` handles an exception if **any of the following conditions** are meet: +`AbpExceptionFilter` handles an exception if **any of the following conditions** are met: * Exception is thrown by a **controller action** which returns an **object result** (not a view result). * The request is an AJAX request (`X-Requested-With` HTTP header value is `XMLHttpRequest`). @@ -29,7 +29,7 @@ Error Message is an instance of the `RemoteServiceErrorResponse` class. The simp } ```` -There are **optional fields** those can be filled based upon the exception that has occured. +There are **optional fields** those can be filled based upon the exception that has occurred. ##### Error Code @@ -70,7 +70,7 @@ Error **details** in an optional field of the JSON error message. Thrown `Except "code": "App:010046", "message": "Your request is not valid, please correct and try again!", "validationErrors": [{ - "message": "Username should be minimum lenght of 3.", + "message": "Username should be minimum length of 3.", "members": ["userName"] }, { diff --git a/docs/en/Microservice-Architecture.md b/docs/en/Microservice-Architecture.md index e30c070a01..ff85873ee7 100644 --- a/docs/en/Microservice-Architecture.md +++ b/docs/en/Microservice-Architecture.md @@ -19,7 +19,7 @@ One of the major goals of the ABP framework is to provide a convenient infrastru ## Microservice for New Applications -One common advise to start a new solution is **always to start with a monolith**, keep it modular and split into microservices once the monolith becomes a problem. This makes your progress fast in the beginning especially if your team is small and you don't want to deal with challanges of the microservice architecture. +One common advise to start a new solution is **always to start with a monolith**, keep it modular and split into microservices once the monolith becomes a problem. This makes your progress fast in the beginning especially if your team is small and you don't want to deal with challenges of the microservice architecture. However, developing such a well-modular application can be a problem since it is **hard to keep modules isolated** from each other as you would do it for microservices (see [Stefan Tilkov's article](https://martinfowler.com/articles/dont-start-monolith.html) about that). Microservice architecture naturally forces you to develop well isolated services, but in a modular monolithic application it's easy to tight couple modules to each other and design **weak module boundaries** and API contracts. 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 118fea5c19..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 @@ -90,32 +90,6 @@ namespace MyCompany.MyProject TODO: ... -### Volo.Abp.MultiTenancy Package - -Volo.Abp.MultiTenancy is the actual package that makes your application multi-tenant. Install it into your project using PMC: - -```` -Install-Package Volo.Abp.MultiTenancy -```` - -Then you can add **AbpMultiTenancyModule** dependency to your module: - -````C# -using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpMultiTenancyModule))] - public class MyModule : AbpModule - { - //... - } -} -```` - -> If you add AbpMultiTenancyModule dependency to your module, then you don't need to add AbpMultiTenancyModule dependency separately since AbpMultiTenancyModule already depends on it. - #### Determining Current Tenant The first thing for a multi-tenant application is to determine the current tenant on the runtime. Volo.Abp.MultiTenancy package only provides abstractions (named as tenant resolver) for determining the current tenant, however it does not have any implementation out of the box. 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/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md index 337f021587..ecc302c34d 100644 --- a/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/en/Tutorials/AspNetCore-Mvc/Part-I.md @@ -84,11 +84,23 @@ EF Core requires you to relate entities with your DbContext. The easiest way to ````C# public class BookStoreDbContext : AbpDbContext { - public DbSet Book { get; set; } + public DbSet Books { get; set; } ... } ```` +#### Configure Your Book Entity + +Open BookStoreDbContextModelCreatingExtensions.cs file from the `Acme.BookStore.EntityFrameworkCore` project, add following code to the end of ConfigureBookStore method to configure Book entity: + +````C# + builder.Entity(b => + { + b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); + b.ConfigureExtraProperties(); + }); +```` + #### Add New Migration & Update the Database The Startup template uses [EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/) to create and maintain the database schema. Open the **Package Manager Console (PMC)** (under the *Tools/Nuget Package Manager* menu), select the `Acme.BookStore.EntityFrameworkCore.DbMigrations` as the **default project** and execute the following command: @@ -139,7 +151,7 @@ namespace Acme.BookStore * **DTO** classes are used to **transfer data** between the *presentation layer* and the *application layer*. See the [Data Transfer Objects document](../../Data-Transfer-Objects.md) for more details. * `BookDto` is used to transfer book data to the presentation layer in order to show the book information on the UI. * `BookDto` is derived from the `AuditedEntityDto` which has audit properties just like the `Book` class defined above. -* `[AutoMapFrom(typeof(Book))]` is used to create AutoMapper mapping from the `Book` class to the `BookDto` class. In this way, you get automatic convertion of `Book` objects to `BookDto` objects (instead of manually copy all properties). +* `[AutoMapFrom(typeof(Book))]` is used to create AutoMapper mapping from the `Book` class to the `BookDto` class. In this way, you get automatic conversion of `Book` objects to `BookDto` objects (instead of manually copy all properties). #### CreateUpdateBookDto @@ -413,7 +425,7 @@ $(function () { ```` * `abp.libs.datatables.createAjax` is a helper function to adapt ABP's dynamic JavaScript API proxies to Datatable's format. -* `abp.libs.datatables.normalizeConfiguration` is another helper function. There's no requirment to use it, but it simplifies the datatables configuration by providing conventional values for missing options. +* `abp.libs.datatables.normalizeConfiguration` is another helper function. There's no requirement to use it, but it simplifies the datatables configuration by providing conventional values for missing options. * `acme.bookStore.book.getList` is the function to get list of books (you have seen it before). * See [Datatable's documentation](https://datatables.net/manual/) for more configuration options. diff --git a/docs/zh-Hans/Apps/VoloDocs.md b/docs/zh-Hans/Apps/VoloDocs.md new file mode 100644 index 0000000000..f8cee69ad5 --- /dev/null +++ b/docs/zh-Hans/Apps/VoloDocs.md @@ -0,0 +1,174 @@ +# VoloDocs + +## 什么是VoloDocs? + +VoloDocs是一个跨平台的应用程序,帮助你简单的创建良好的文档与构建开发人员社区. 它借助Github集成简化了软件文档. 使用Github的强大功能进行版本控制,托管文档,并允许用户编译文档. + +## 主要功能 + +- 集成Github从仓库提供文档. +- 支持Markdown/HTML文档格式. +- 支持版本控制 (集成Github release). +- 支持多个项目. +- 允许用户在Github上编辑文档. +- 跨平台,可部署到Windows/Linux/macOS. + +## Github源码仓库 + +它是免费并且开源的. 你可以查看VoloDocs源码,我们也欢迎PR: + +https://github.com/abpframework/abp/tree/master/modules/docs + +## 下载 + +你可以在下面的链接下载VoloDocs release版: + +http://apps.abp.io/VoloDocs/VoloDocs.win-x64.zip - **Windows 64 bit** + +http://apps.abp.io/VoloDocs/VoloDocs.win-x86.zip - **Windows 32 bit** + +http://apps.abp.io/VoloDocs/VoloDocs.osx-x64.zip - **MacOS** + +http://apps.abp.io/VoloDocs/VoloDocs.linux-x64.zip - **Linux** + +上面所有的release都是自包含运行时的部署文件. 包含所有的第三方依赖与.Net Core Runtime,所以你不需要安装任何.Net Core SDK/Runtime. + +## 目录结构 + +解压 `VoloDocs.*.zip` 文件, 你会得到一个 `Web` 文件夹和 `Migrator` 文件夹. `Web` 文件夹中包含网站文件, `Migrator` 文件夹中包含构建数据库的应用程序. 在发布网站之前需要创建数据库或将现有的数据库更新到最新迁移. 如果这是你第一次安装VoloDocs, `Migrator` 会创建一个新数据库. 如果是升级已有的VoloDocs则会将数据库升级到最新的迁移. 使用 `Migrator` 你需要在 `appsettings.json` 文件中更改 `ConnectionString`. 参阅下一节了解如何配置VoloDocs应用程序. + +## 逐步部署 + +- ### 数据库迁移 + + 进入 `Migrator` 目录中创建或更新数据库. + + 打开 `appsettings.json` 文件设置连接字符串,如果你不清楚如何编写连接字符串,可以查看 https://www.connectionstrings.com/. + + 设置连接字符串后运行 `Migrate.bat` 如果是Linux或MacOS运行 `DotNet VoloDocs.Migrator.dll` 命令. + +- ### 配置网站 + + 进入 `Web` 目录下的VoloDocs文件夹. 打开 `appsettings.json` 文件设置连接字符串(与 `Migrator` 的 `appsettings.json` 相同). 你也可以为网站设置你想要的 `title`,它会显示在网站的左上角. 做完这些现在可以发布网站了. + +- ### 部署网站 + + 在上一步我们创建/更新了数据库(确保数据库链接字符串正确). + + - #### 部署到IIS + + - 移动 `Web` 文件夹到你的 `wwwroot ` 文件夹中. + - 重命名 `Web` 文件夹为 `VoloDocs` (现在我们有了 `C:\inetpub\wwwroot\VoloDocs`).![Add IIS Website](../images/volodocs-iis-add-website.png) + - 创建网站 `VoloDocs` 会自动创建相同名称的应用程序池. 打开 **应用程序池** 并双击 `VoloDocs` 应用程序池做以下设置 + - **.NET CLR 版本**: `无托管代码` + - **托管管道模式**: `集成` + + ![Add IIS Website](../images/volodocs-iis-application-pool.png) + + + + - 如果发生发生发现错误,表示未在服务器安装托管捆绑包. 请参阅文档 [this document](https://docs.microsoft.com/aspnet/core/host-and-deploy/iis/#install-the-net-core-hosting-bundle) 了解如何安装或[下载托管捆绑包](https://www.microsoft.com/net/permalink/dotnetcore-current-windows-runtime-bundle-installer). + + ``` + Handler "aspNetCore" has a bad module "AspNetCoreModuleV2" in its module list using IIS + ``` + + - 关于托管VoloDocs的更多信息,请参阅 [在IIS上托管ASP.NET Core应用程序的微软官方文档](https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/iis). + + - #### 部署到Azure + + 微软有一篇关于将ASP.NET Core应用程序部署到Azure App Service的文档. 我们建议你此[文档](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-get-started-dotnet). + + - #### 使用命令行运行应用程序 + + 你可以从命令行运行应用程序,进入 `VoloDocs\Web` 文件夹并运行 `VoloDocs.Web.exe`,如果是Linux或MacOS运行 `DotNet VoloDocs.Web.dll` 命令. + +- ### 首次运行 + + 打开浏览器导航到你的网址. + + 首次运行网站,我们需要创建一个项目. + + #### 创建项目 + + 跳转到以下网址创建项目 + + - `http:///Account/Login?returnUrl=/Docs/Admin/Projects` + + ##### 默认凭据 + + 使用管理员身份登陆到管理后台,用户名密码如下: + + * **用户名**: `admin` + + * **密码**: `1q2w3E*` + + ##### 示例项目的定义 + + 以下是一个使用了Github源的示例项目信息. + + 配置VoloDocs展示存储在Github仓库中ABP Framework的文档. + + 这是ABP Framework文档在Github的链接: + + https://github.com/abpframework/abp/tree/master/docs/zh-Hans + + + + * **名称**: `ABP Framework` + + * **简称**: `abp` + + * **格式**: `markdown` + + * **默认文档名称**: `Index` + + * **文档导航名称**: `docs-nav.json` ([参见示例](https://github.com/abpframework/abp/blob/master/docs/zh-Hans/docs-nav.json)) + + * **最小版本**: *留空* *(隐藏之前的版本)* + + * **主网站网址**: `/` + + * **最新版本分支名称**: 留空 + + * **GitHub链接**: `https://github.com/abpframework/abp/tree/{version}/docs/zh-Hans/` + + * **GitHub访问令牌**: [参见如何创建Github访问令牌](#retrieving-github-access-token) + + * **GitHub用户名称**: [参见如何找到自己的Github用户名称](#learn-your-github-username) + + ![Creating a new project](../images/docs-create-project.jpg) + + ##### 创建访问令牌 + + 在Github页右上角找到 `Settings`,然后在左侧导航中点击 `Developer Settings`,在这里选择 `Personal access tokens`. 点击创建新token,输入名称与描述并勾选repo复选框. 如果已有访问令牌但已经忘记,可以选择[重新生成](https://github.com/settings/tokens/new). + + ###### 创建仅授权访问公共仓库的token + + 想要仅授权访问公共仓库,仅勾选 `public_repo` 就可以了,然后点击页面底部的 `Generate Token` 按钮完成操作. + + ![Retrieve GitHub Access Token for Public Repo](../images/github-access-token-public-repo.jpg) + + ###### 创建可访问私有仓库的token + + 想要授权访问私有仓库,勾选 `repo` 下的所有选项. 然后点击页面底部的 `Generate Token` 按钮完成操作. + + ![Retrieve GitHub Access Token for Private Repo](../images/github-access-token-private-repo.jpg) + + ###### 查看你的Github用户名称 + + 在Github页面右上角点击头像图片,"Signed in as ..." 后面就是你的用户名称 + + ![Your GitHub Username](../images/github-myusername.jpg) + + + +保存项目后转到网站根地址,你会看到文档已经正常显示. + +`http:///documents` + +### 发现问题? + +如果你在安装,使用时遇到问题或者发现bug,请发起issues与我们联系: + +https://github.com/abpframework/abp/issues/new \ No newline at end of file diff --git a/docs/zh-Hans/Best-Practices/Application-Services.md b/docs/zh-Hans/Best-Practices/Application-Services.md index 8e9c85d63a..342d7cc3bb 100644 --- a/docs/zh-Hans/Best-Practices/Application-Services.md +++ b/docs/zh-Hans/Best-Practices/Application-Services.md @@ -202,12 +202,13 @@ Task VoteAsync(Guid id, VoteType type); #### 操作/删除 实体 * **推荐** 总是从数据库中获取所有的相关实体以对他们执行操作. +* **推荐** 更新实体后调用存储的Update/UpdateAsync方法.因为并非所有数据库API都支持更改跟踪和自动更新. #### 使用其他应用服务 * **不推荐** 使用相同 **模块/应用程序** 的其他应用服务. 相反; * 使用领域层执行所需的任务. - * 提取新类并在应用程序服务之间共享, 在必要时代码重用. + * 提取新类并在应用程序服务之间共享, 在必要时代码重用. 但要小心不要结合两个用例. 它们在开始时可能看起来相似, 但可能会随时间演变为不同的方向. 请谨慎使用代码共享. * **可以** 在以下情况下使用其他应用服务; * 它们是另一个模块/微服务的一部分. * 当前模块仅引用已使用模块的application contracts. \ No newline at end of file diff --git a/docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md b/docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md index e0db48397e..0c6017e6e7 100644 --- a/docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md +++ b/docs/zh-Hans/Best-Practices/Data-Transfer-Objects.md @@ -5,4 +5,4 @@ * **推荐** 定义 **public getter 和 setter** 的DTO成员 . * **推荐** 使用 **data annotations** **验证** service输入DTO的属性. * **不推荐** 在DTO中添加任何 **逻辑**, 在必要的时候可以实现 `IValidatableObject` 接口. -* **推荐** 为所有的DTO标记 **[Serializable]** Attribute. 因为它们已经是可序列化的, 开人发员可能会希望进行二进制序列化. \ No newline at end of file +* **推荐** 为所有的DTO标记 **[Serializable]** Attribute. 因为它们已经是可序列化的, 开发人员可能会希望进行二进制序列化. diff --git a/docs/zh-Hans/Best-Practices/Entities.md b/docs/zh-Hans/Best-Practices/Entities.md index 637ecaef6d..fd709ef35f 100644 --- a/docs/zh-Hans/Best-Practices/Entities.md +++ b/docs/zh-Hans/Best-Practices/Entities.md @@ -64,7 +64,7 @@ public class Issue : FullAuditedAggregateRoot //使用Guid作为键/标识 protected Issue() { /* 此构造函数是提供给ORM用来从数据库中获取实体. - * - 无需初始化Lanels集合 + * - 无需初始化Labels集合 因为它会被来自数据库的值覆盖. - It's protected since proxying and deserialization tools 可能不适用于私有构造函数. @@ -150,4 +150,4 @@ public class IssueLabel : Entity ### 参考文献 * Effective Aggregate Design by Vaughn Vernon - http://dddcommunity.org/library/vernon_2011 \ No newline at end of file + http://dddcommunity.org/library/vernon_2011 diff --git a/docs/zh-Hans/Dependency-Injection.md b/docs/zh-Hans/Dependency-Injection.md index 8328c12918..381b77ec35 100644 --- a/docs/zh-Hans/Dependency-Injection.md +++ b/docs/zh-Hans/Dependency-Injection.md @@ -2,9 +2,11 @@ ABP的依赖注入系统是基于Microsoft的[依赖注入扩展](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection)库(Microsoft.Extensions.DependencyInjection nuget包)开发的.因此,它的文档在ABP中也是有效的. +> 虽然ABP框架没有对任何第三方DI提供程序的核心依赖, 但它必须使用一个提供程序来支持动态代理(dynamic proxying)和一些高级特性以便ABP特性能正常工作.启动模板中已安装了Autofac. 更多信息请参阅 [Autofac 集成](Autofac-Integration.md) 文档. + ### 模块化 -由于ABP是一个模块化框架,因此每个模块都通过依赖注入定义它自己的服务并通过它自己的单独[模块类](Module-Development-Basics.md)进行注册.例: +由于ABP是一个模块化框架,因此每个模块都定义它自己的服务并在它自己的单独[模块类](Module-Development-Basics.md)中通过依赖注入进行注册.例: ````C# public class BlogModule : AbpModule @@ -69,15 +71,15 @@ public class BlogPostAppService : ApplicationService } ```` -``BlogPostAppService`` 由于它是从已知的基类派生的,因此会自动注册transient. +``BlogPostAppService`` 由于它是从已知的基类派生的,因此会自动注册为transient生命周期. #### 依赖接口 如果实现这些接口,则会自动将类注册到依赖注入: -* ``ITransientDependency`` 注册为transient. -* ``ISingletonDependency`` 注册为singleton. -* ``IScopedDependency`` 注册为scoped. +* ``ITransientDependency`` 注册为transient生命周期. +* ``ISingletonDependency`` 注册为singleton生命周期. +* ``IScopedDependency`` 注册为scoped生命周期. 示例: @@ -87,9 +89,9 @@ public class TaxCalculator : ITransientDependency } ```` -``TaxCalculator``因为实现了``ITransientDependency``,所以它会自动注册为transient. +``TaxCalculator``因为实现了``ITransientDependency``,所以它会自动注册为transient生命周期. -#### Dependency 属性 +#### Dependency 特性 配置依赖注入服务的另一种方法是使用``DependencyAttribute``.它具有以下属性: @@ -108,9 +110,9 @@ public class TaxCalculator ```` -``Dependency``如果定义``Lifetime``属性,则具有比其他依赖接口更高的优先级. +如果定义了``Lifetime``属性,则``Dependency``特性具有比其他依赖接口更高的优先级. -#### ExposeServices 属性 +#### ExposeServices 特性 ``ExposeServicesAttribute``用于控制相关类提供了什么服务.例: @@ -133,7 +135,7 @@ public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransie #### 组合到一起 -只要有意义,就可以组合属性和接口. +只要有意义,特性和接口是可以组合在一起使用的. ````C# [Dependency(ReplaceServices = true)] @@ -189,7 +191,7 @@ public class TaxAppService : ApplicationService ``TaxAppService``在构造方法中得到``ITaxCalculator``.依赖注入系统在运行时自动提供所请求的服务. -构造方法注入是将依赖项注入类的优先方式.这样,除非提供了所有构造方法注入的依赖项,否则无法构造类.因此,该类明确的声明了它必需的服务. +构造方法注入是将依赖项注入类的首选方式.这样,除非提供了所有构造方法注入的依赖项,否则无法构造类.因此,该类明确的声明了它必需的服务. #### 属性注入 @@ -266,6 +268,55 @@ using (var scope = _serviceProvider.CreateScope()) 两个服务在创建的scope被处理时(在using块的末尾)释放. +## 高级特性 + +### IServiceCollection.OnRegistred 事件 + +你可能想在注册到依赖注入的每个服务上执行一个操作, 在你的模块的 `PreConfigureServices` 方法中, 使用 `OnRegistred` 方法注册一个回调(callback) , 如下所示: + +````csharp +public class AppModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.OnRegistred(ctx => + { + var type = ctx.ImplementationType; + //... + }); + } +} +```` + +`ImplementationType` 提供了服务类型. 该回调(callback)通常用于向服务添加拦截器. 例如: + +````csharp +public class AppModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.OnRegistred(ctx => + { + if (ctx.ImplementationType.IsDefined(typeof(MyLogAttribute), true)) + { + ctx.Interceptors.TryAdd(); + } + }); + } +} +```` + +这个示例判断一个服务类是否具有 `MyLogAttribute` 特性, 如果有的话就添加一个 `MyLogInterceptor` 到拦截器集合中. + +> 注意, 如果服务类公开了多于一个服务或接口, `OnRegistred` 回调(callback)可能被同一服务类多次调用. 因此, 较安全的方法是使用 `Interceptors.TryAdd` 方法而不是 `Interceptors.Add` 方法. 请参阅动态代理(dynamic proxying)/拦截器 [文档](Dynamic-Proxying-Interceptors.md). + +## 第三方提供程序 + +虽然ABP框架没有对任何第三方DI提供程序的核心依赖, 但它必须使用一个提供程序来支持动态代理(dynamic proxying)和一些高级特性以便ABP特性能正常工作. + +启动模板中已安装了Autofac. 更多信息请参阅 [Autofac 集成](Autofac-Integration.md) 文档. + + ### 请参阅 -* [ASP.NET Core依赖注入最佳实践,提示和技巧](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) +* [ASP.NET Core依赖注入最佳实践,提示和技巧](https://cn.abp.io/blog/Abp/asp-net-core-dependency-injection-best-practices-tips-tricks) diff --git a/docs/zh-Hans/Domain-Driven-Design.md b/docs/zh-Hans/Domain-Driven-Design.md new file mode 100644 index 0000000000..d4e51a152c --- /dev/null +++ b/docs/zh-Hans/Domain-Driven-Design.md @@ -0,0 +1,33 @@ +# 领取驱动设计 + +## 什么是DDD? + +ABP框架提供了**基础设施**使基础**DDD**的开发更易实现. DDD在[维基百科中的定义](https://zh.wikipedia.org/wiki/%E5%9F%9F%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91)如下: + +> **领域驱动设计(DDD)** 是一种通过将实现连接到持续进化的模型来满足复杂需求的软件开发方法. 领域驱动设计的前提是: +> +> - 把项目的主要重点放在核心领域和领域逻辑上 +> - 把复杂的设计放在领域模型上 +> - 发起技术专家和领域专家之间的创造性协作,以迭代方式完善解决特定领域问题的概念模型 + +### 分层 + +ABP框架遵循DDD原则和模式去实现分层应用程序模型,该模型由四个基本层组成: + +- **表示层**: 为用户提供接口. 使用*应用层*实现与用户交互. +- **应用层**: 表示层与领域层的中介,编排业务对象执行特定的应用程序任务. 使用应用程序逻辑实现用例. +- **领域层**: 包含业务对象以及业务规则. 是应用程序的核心. +- **基础设施层**: 提供通用的技术功能,支持更高的层,主要使用第三方类库. + +## 内容 + +* **领域层** + * [实体与聚合根](Entities.md) + * 值对象 + * [仓储](Repositories.md) + * 领域服务 + * 规约 +* **应用层** + * [应用服务](Application-Services.md) + * [数据传输对象(DTO)](Data-Transfer-Objects.md) + * 工作单元 \ No newline at end of file diff --git a/docs/zh-Hans/Exception-Handling.md b/docs/zh-Hans/Exception-Handling.md index ac68058883..a251c50f61 100644 --- a/docs/zh-Hans/Exception-Handling.md +++ b/docs/zh-Hans/Exception-Handling.md @@ -11,15 +11,15 @@ ABP提供了用于处理Web应用程序异常的标准模型. 当满足下面**任意一个条件**时,`AbpExceptionFilter` 会处理此异常: -* 当**controller action**方法返回类型是**object**(不是view)并有异常抛出时. -* 当是一个请求为AJAX(Http请求头中`X-Requested-With`为`XMLHttpRequest`)时. +* 当**controller action**方法返回类型是**object result**(而不是view result)并有异常抛出时. +* 当一个请求为AJAX(Http请求头中`X-Requested-With`为`XMLHttpRequest`)时. * 当客户端接受的返回类型为`application/json`(Http请求头中`accept` 为`application/json`)时. 如果异常被处理过,则会自动**记录日志**并将格式化的**JSON消息**返回给客户端. -#### 异常消息格式 +#### 错误消息格式 -每个异常消息都是`RemoteServiceErrorResponse` 类的实例.下面是一个只有 **Message** 属性的错误JSON: +每个错误消息都是`RemoteServiceErrorResponse` 类的实例.最简单的错误JSON只有一个 **Message** 属性,如下所示: ````json { @@ -29,11 +29,11 @@ ABP提供了用于处理Web应用程序异常的标准模型. } ```` -当异常发生时,会自动填充到这些**可选字段**. +其它**可选字段**可以根据已发生的异常来填充. ##### 错误代码 -错误的 **Code** 是字符串类型,并要求唯一的可选属性.如果抛出的异常包含 **Code** 属性,那么应该实现`IHasErrorCode` 接口,来填充这个字段.示例JSON如下: +错误 **代码(code)** 是异常信息中一个有唯一值并可选的字符串值.抛出的异常应实现`IHasErrorCode` 接口来填充该字段.示例JSON如下: ````json { @@ -44,11 +44,11 @@ ABP提供了用于处理Web应用程序异常的标准模型. } ```` -错误 **Code** 同样可用于异常的本地化及自定义HTTP状态代码(请参阅下面的相关部分). +错误代码同样可用于异常信息的本地化及自定义HTTP状态代码(请参阅下面的相关部分). ##### 错误详细信息 -错误的 **Details** 是可选属性.抛出的异常应该实现`IHasErrorDetails` 接口来填充这个字段.示例JSON如下: +错误的 **详细信息(Details)** 是可选属性.抛出的异常应实现`IHasErrorDetails` 接口来填充该字段.示例JSON如下: ```json { @@ -62,7 +62,7 @@ ABP提供了用于处理Web应用程序异常的标准模型. ##### 验证错误 -当抛出的异常继承至`IHasValidationErrors` 接口时,返回错误对象会包含一个可选属性**validationErrors** .示例JSON如下: +当抛出的异常实现`IHasValidationErrors` 接口时,**validationErrors**是一个可被填充的标准字段.示例JSON如下: ````json { @@ -70,7 +70,7 @@ ABP提供了用于处理Web应用程序异常的标准模型. "code": "App:010046", "message": "Your request is not valid, please correct and try again!", "validationErrors": [{ - "message": "Username should be minimum lenght of 3.", + "message": "Username should be minimum length of 3.", "members": ["userName"] }, { @@ -85,7 +85,7 @@ ABP提供了用于处理Web应用程序异常的标准模型. #### 日志 -自动记录捕获异常的日志. +被捕获的异常会被自动记录到日志中. ##### 日志级别 @@ -102,7 +102,7 @@ public class MyException : Exception, IHasLogLevel ##### 异常自定义日志 -某些异常类型可能需要记录额外日志信息.可以通过实现`IExceptionWithSelfLogging` 来记录指定日志,例如: +某些异常类型可能需要记录额外日志信息.可以通过实现`IExceptionWithSelfLogging` 接口来记录指定日志,例如: ````C# public class MyException : Exception, IExceptionWithSelfLogging @@ -114,7 +114,7 @@ public class MyException : Exception, IExceptionWithSelfLogging } ```` -> 扩展方法`ILogger.LogException` 用来记录日志. 在需要时可以使用相同的扩展方法. +> 扩展方法`ILogger.LogException` 用来记录异常日志. 在需要时可以使用相同的扩展方法. ### 业务异常 @@ -140,9 +140,9 @@ throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer); Volo.Qa:010002 ```` -`Volo.Qa`在这是作为`code-namespace`. `code-namespace` 同样可以在异常 **本地化** 中使用. +`Volo.Qa`在这是作为`code-namespace`. `code-namespace` 同样可以在 **本地化** 异常信息时使用. -* 你可以直接抛出一个 `BusinessException` 异常或者自定义的异常. +* 你可以直接抛出一个 `BusinessException` 异常,或者需要时可以从该类派生你自己的Exception类型. * 对于`BusinessException` 类型,其所有属性都是可选的.但是通常会设置`ErrorCode`或`Message`属性. ### 异常本地化 @@ -153,7 +153,7 @@ Volo.Qa:010002 如果异常实现了 `IUserFriendlyException` 接口,那么ABP不会修改 `Message`和`Details`属性,而直接将它发送给客户端. -`UserFriendlyException` 类默认实现了 `IUserFriendlyException` 接口,示例如下: +`UserFriendlyException` 类是内建的 `IUserFriendlyException` 接口的实现,示例如下: ````C# throw new UserFriendlyException( @@ -167,7 +167,7 @@ throw new UserFriendlyException( throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage"]); ```` -再在本地化资源的语言中添加对应的定义.例如: +再在本地化资源中为每种语言添加对应的定义.例如: ````json { @@ -178,7 +178,7 @@ throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage" } ```` -**string localizer** 是支持格式化参数.例如 +**string localizer** 支持参数化信息.例如 ````C# throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage", "john"]); @@ -192,12 +192,12 @@ throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage" * `IUserFriendlyException`接口派生自`IBusinessException`,而 `UserFriendlyException `类派生自`BusinessException`类. -#### 错误代码 +#### 使用错误代码 `UserFriendlyException`很好用,但是在一些高级用法里面,它存在以下问题: * 在抛出异常的地方必须注入**string localizer** 来实现本地化 . -* 但是,在某些情况下,**可能注入不了string localizer**(比如,静态方法或实体中) +* 但是,在某些情况下,**可能注入不了string localizer**(比如,在静态上下文或实体方法中) 那么这时就可以通过使用 **错误代码** 的方式来处理本地化,而不是在抛出异常的时候. @@ -210,7 +210,7 @@ services.Configure(options => }); ```` -再使用本地化资源,来本地化`Volo.Qa`命名空间下的所有异常. 本地化资源中应包含对应错误代码的文本. 例如: +然后`Volo.Qa`命名空间下的所有异常都将被对应的本地化资源进行本地化处理. 本地化资源中应包含对应错误代码的文本. 例如: ````json { @@ -227,12 +227,12 @@ services.Configure(options => throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer); ```` -* 所有实现`IHasErrorCode` 接口的异常都具有相同的行为.因此,对错误代码的本地化,并不是`BusinessException`类所特有的. -* 错误消息的本地化文本的并不是必须. 如果未定义,ABP会将默认的错误消息发送给客户端. 而并不是发送异常的`Message`属性. 如果你想要发送异常的`Message`,使用`UserFriendlyException`(或使用实现`IUserFriendlyException`接口的异常类型) +* 抛出所有实现`IHasErrorCode` 接口的异常都具有相同的行为.因此,对错误代码的本地化,并不是`BusinessException`类所特有的. +* 为错误消息定义本地化文本并不是必须的. 如果未定义,ABP会将默认的错误消息发送给客户端. 而不使用异常的`Message`属性. 如果你想要发送异常的`Message`,使用`UserFriendlyException`(或使用实现`IUserFriendlyException`接口的异常类型) ##### 使用消息的格式化参数 -如果错误消息包含格式化参数时,则可以使用异常的`Data`属性进行设置.例如: +如果有参数化的错误消息,则可以使用异常的`Data`属性进行设置.例如: ````C# throw new BusinessException("App:010046") @@ -263,9 +263,9 @@ throw new BusinessException("App:010046") } ```` -* `WithData` 支持链式调用 (如`.WithData(...).WithData(...)`). +* `WithData` 支持有多个参数的链式调用 (如`.WithData(...).WithData(...)`). -### HTTP状态代码 映射 +### HTTP状态代码映射 ABP尝试按照以下规则,自动映射常见的异常类型的HTTP状态代码: @@ -297,6 +297,6 @@ services.Configure(options => - 当用户没有权限执行操作时,会抛出 `AbpAuthorizationException` 异常. 有关更多信息,请参阅授权文档(TODO:link). - 如果当前请求的输入无效,则抛出`AbpValidationException 异常`. 有关更多信息,请参阅授权文档(TODO:link). -- 如果请求的实体不存在,则抛出`EntityNotFoundException` 异常. 此异常由 [repositories](Repositories.md) 抛出. +- 如果请求的实体不存在,则抛出`EntityNotFoundException` 异常. 此异常大多数由 [repositories](Repositories.md) 抛出. -你同样可以在代码中抛出这些类型的异常(虽然只在很少时候) +你同样可以在代码中抛出这些类型的异常(虽然很少需要这样做) diff --git a/docs/zh-Hans/Getting-Started-AspNetCore-Application.md b/docs/zh-Hans/Getting-Started-AspNetCore-Application.md index cf9ac3f66f..db6e73f35c 100644 --- a/docs/zh-Hans/Getting-Started-AspNetCore-Application.md +++ b/docs/zh-Hans/Getting-Started-AspNetCore-Application.md @@ -77,11 +77,11 @@ namespace BasicAspNetCoreApplication { public class Startup { - public IServiceProvider ConfigureServices(ServiceConfigurationContext context) + public IServiceProvider ConfigureServices(IServiceCollection services) { - context.Services.AddApplication(); + services.AddApplication(); - return context.Services.BuildServiceProviderFromFactory(); + return services.BuildServiceProviderFromFactory(); } public void Configure(IApplicationBuilder app) diff --git a/docs/zh-Hans/Module-Development-Basics.md b/docs/zh-Hans/Module-Development-Basics.md index 9feaa8c7f9..4519720161 100644 --- a/docs/zh-Hans/Module-Development-Basics.md +++ b/docs/zh-Hans/Module-Development-Basics.md @@ -2,7 +2,7 @@ ### 介绍 -ABP本身是一个模块化框架.它还提供了一个基础架构和架构模型来开发你自己的模块. +ABP本身是一个包含许多nuget包的模块化框架.它还提供了一个完整的基础架构来开发你自己的具有实体, 服务, 数据库集成, API, UI组件等等功能的应用程序模块. ### 模块类 @@ -111,7 +111,7 @@ public class AppModule : AbpModule ### 模块依赖 -在模块化应用程序中,一个模块依赖于另一个模块并不罕见.如果一个Abp模块依赖于另一个模块,它必须声明``[DependsOn]``属性,如下所示: +在模块化应用程序中,一个模块依赖于另一个或几个模块并不罕见.如果一个Abp模块依赖于另一个模块,它必须声明``[DependsOn]``特性,如下所示: ````C# [DependsOn(typeof(AbpAspNetCoreMvcModule))] @@ -122,7 +122,7 @@ public class BlogModule } ```` -你可以根据需要使用多个``DependsOn``属性或将多个模块类型传递给单个``DependsOn``属性. +你可以根据需要使用多个``DependsOn``特性或将多个模块类型传递给单个``DependsOn``特性. 依赖模块可能依赖于另一个模块,但你只需要定义直接依赖项.ABP在启动时会调查应用程序的依赖关系,并以正确的顺序初始化/关闭模块. @@ -130,5 +130,5 @@ public class BlogModule **模块分为两种类型.** 这两种类型并没有任何结构上的区别,只是按功能和用途分类: -- **框架模块**: 这些是**框架的核心模块** 如缓存, 邮件, 主题, 安全, 序列化, 验证, EF Core集成, MongoDB集成... 等. 它们没有应用/业务功能,它们提供了日常开发经常用到的基础设施,集成和抽象. -- **应用程序模块**: 这些模块实现了 **特定的应用/业务功能** 像博客, 文档管理, 身份管理, 租房管理... 等等. 它们通过有自己的实体,服务,API和UI组件. 请参阅 [预构建的应用程序模块](Modules/Index.md). +- **框架模块**: 这些是**框架的核心模块** 如缓存, 邮件, 主题, 安全, 序列化, 验证, EF Core集成, MongoDB集成... 等. 它们没有应用/业务功能,它们提供了日常开发经常用到的通用基础设施,集成和抽象. +- **应用程序模块**: 这些模块实现了 **特定的应用/业务功能** 像博客, 文档管理, 身份管理, 租户管理... 等等. 它们通常有自己的实体,服务,API和UI组件. 请参阅 [预构建的应用程序模块](Modules/Index.md). diff --git a/docs/zh-Hans/Multi-Tenancy.md b/docs/zh-Hans/Multi-Tenancy.md index 6364428ec1..911803b1be 100644 --- a/docs/zh-Hans/Multi-Tenancy.md +++ b/docs/zh-Hans/Multi-Tenancy.md @@ -90,32 +90,6 @@ namespace MyCompany.MyProject TODO: ... -### Volo.Abp.MultiTenancy - -Volo.Abp.MultiTenancy 才是让你的程序实现多租户的真正的包.使用PMC将它安装到你的项目中: - -```` -Install-Package Volo.Abp.MultiTenancy -```` - -然后添加 **AbpMultiTenancyModule** 依赖到你的模块中: - -````C# -using Volo.Abp.Modularity; -using Volo.Abp.MultiTenancy; - -namespace MyCompany.MyProject -{ - [DependsOn(typeof(AbpMultiTenancyModule))] - public class MyModule : AbpModule - { - //... - } -} -```` - -> 如果你添加了AbpMultiTenancyModule依赖,就不需要再另外添加AbpMultiTenancyModule依赖了,因为AbpMultiTenancyModule已经依赖它了. - #### 确定当前租户 多租户的应用程序运行的时候首先要做的就是确定当前租户. diff --git a/docs/zh-Hans/Samples/Microservice-Demo.md b/docs/zh-Hans/Samples/Microservice-Demo.md index c87d7e5406..fdf183d1b6 100644 --- a/docs/zh-Hans/Samples/Microservice-Demo.md +++ b/docs/zh-Hans/Samples/Microservice-Demo.md @@ -116,11 +116,29 @@ ABP框架的主要目标之一就是提供[便捷的基础设施来创建微服 * 在`samples\MicroserviceDemo`文件夹中的命令行运行`dotnet restore`命令. * 在Visual Studio中构建解决方案. -#### 还原数据库 +#### 创建数据库 -在`samples\MicroserviceDemo\databases`文件夹中打开`MsDemo_Identity.zip`和`MsDemo_ProductManagement.zip`并恢复到SQL Server. +MongoDB 数据库是动态创建的,但是你需要创建 SQL server 数据库的结构。其实你可以很轻松的创建数据库,因为这个解决方案配置了使用 Entity Core Code First 来做迁移。 -> 请注意:这些数据库在解决方案中具有EF Core迁移,但它们没有种子数据,尤其是IdentityServer4所需的配置. 因此,恢复数据库要容易得多. +这个解决方案中有两个 SQL server 数据库。 + +##### MsDemo_Identity 数据库 + +* 右键 `AuthServer.Host` 项目,然后点击 `设置为启动项目`. +* 打开 **程序包管理器控制台** (工具 -> NuGet 包管理器 -> 程序包管理器控制台) +* 选择 `AuthServer.Host` 成为 **默认项目**. +* 执行 `Update-Database` 命令. + +![microservice-sample-update-database-authserver](../images/microservice-sample-update-database-authserver.png) + +##### MsDemo_ProductManagement + +* 右键 `ProductService.Host` 项目,然后点击 `设置为启动项目`. +* 打开 **程序包管理器控制台** (工具 -> NuGet 包管理器 -> 程序包管理器控制台) +* 选择 `ProductService.Host` 成为 **默认项目**. +* 执行 `Update-Database` 命令. + +![microservice-sample-update-database-products](../images/microservice-sample-update-database-products.png) #### 运行项目 @@ -148,7 +166,7 @@ Visual Studio解决方案由多个项目组成,每个项目在系统中具有不 - **AuthServer.Host**: 托管IdentityServer4以向其他服务和应用程序提供身份验证服务. 它是一个单点登录服务器,包含登录页面. - **BackendAdminApp.Host**: 这是一个后端管理应用程序,用于托管身份和产品管理模块的UI. -- **PubicWebSite.Host**: 作为包含简单产品列表页面和博客模块UI的公共网站. +- **PublicWebSite.Host**: 作为包含简单产品列表页面和博客模块UI的公共网站. - **ConsoleClientDemo**: 一个简单的控制台应用程序,用于演示C#应用程序中使用服务. ### 网关/BFF(前端后端) @@ -276,8 +294,7 @@ context.Services.AddAuthentication(options => * 它将"Cookies"身份验证添加为主要身份验证类型. * "oidc"身份验证配置为使用AuthServer应用程序作为身份验证服务器. -* 它需要额外的身份范围(scopes) *role*, *email* and *phone*. -* It requires the API resource scopes *BackendAdminAppGateway*, *IdentityService* and *ProductService* because it will use these services as APIs. +* 它需要额外的身份范围(scopes) *role*, *email* 和 *phone*. * 它需要API资源范围 *BackendAdminAppGateway*, *IdentityService* 和 *ProductService*,因为它将这些服务用作API. IdentityServer客户端设置存储在`appsettings.json`文件中: @@ -577,8 +594,6 @@ Ocelot需要知道微服务的真实URL才能重定向HTTP请求. 此网关的 `ReRoutes`是一个URL映射数组. `GlobalConfiguration`部分中的`BaseUrl`是该网关的URL(Ocelot需要知道自己的URL). 参见 [ocelot文档](https://ocelot.readthedocs.io/en/latest/features/configuration.html) 更好地了解配置. -Ocelot is a finalizer ASP.NET Core middleware and should be written as the last item in the pipeline: - Ocelot是一个终结ASP.NET核心中间件,应该写成管道中的最后一项: ````csharp @@ -935,7 +950,7 @@ context.Services.AddAuthentication("Bearer") #### IdentityServer Client -此微服务还通过内部网关使用Identity微服务API, 因为在某些情况下它需要查询用户详细信息(username, email, phone, name and surname). 因此,它也是IdentityServer的客户端,并在`appsettings.json`文件中定义了一个部分: +此微服务还通过内部网关使用Identity微服务API, 因为在某些情况下它需要查询用户详细信息(username, email, phone, name 和 surname). 因此,它也是IdentityServer的客户端,并在`appsettings.json`文件中定义了一个部分: ````json "IdentityClients": { @@ -1334,7 +1349,7 @@ public async Task UpdateAsync(Guid id, UpdateProductDto input) AddDistributedEvent(new ProductStockCountChangedEto(Id, StockCount, stockCount)); ```` -`ProductStockCountChangedEto` was defined as shown below: +`ProductStockCountChangedEto` 定义如下: ````csharp [Serializable] @@ -1458,4 +1473,4 @@ ABP提供自动审计日志记录,详细保存每个请求(当前用户,浏览 所有服务和应用程序都配置为编写审核日志. 审核日志将保存到MsDemo_Identity SQL数据库中. 因此,您可以从单个点查询所有应用程序的所有审核日志. -审核日志记录具有`CorrelationId`属性,可用于跟踪请求. 当服务在单个Web请求中调用另一个服务时,它们都会使用相同的`CorrelationId`保存审核日志. 请参阅数据库中的`AbpAuditLogs`表. \ No newline at end of file +审核日志记录具有`CorrelationId`属性,可用于跟踪请求. 当服务在单个Web请求中调用另一个服务时,它们都会使用相同的`CorrelationId`保存审核日志. 请参阅数据库中的`AbpAuditLogs`表. diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md index 19d278e563..d3e8af3523 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md @@ -24,7 +24,7 @@ ### 创建Book实体 -在 **领域层** 定义[实体](../../Entities.md)(`Acme.BookStore.Domain` 中).这个项目最主要的实体就是`Book`: +在 **领域层** 定义[实体](../../Entities.md)(`Acme.BookStore.Domain` 项目中).这个项目最主要的实体就是`Book`: ````C# using System; @@ -50,14 +50,14 @@ namespace Acme.BookStore } ```` -* ABP有两个基本的实体基类: `AggregateRoot` 和 `Entity`.**Aggregate Root**是 **领域驱动设计(DDD)** 中的概念.查看[实体](../../Entities.md)的更多信息和最佳实践. -* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了(`CreationTime`, `CreatorId`, `LastModificationTime`... 等.)审计属性. -* `Book`实体的主键类型是`Guid`类型. +* ABP有两个基本的实体基类: `AggregateRoot` 和 `Entity`.**Aggregate Root**是 **领域驱动设计(DDD)** 的概念之一.更多信息和最佳实践请查看[实体文档](../../Entities.md). +* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime`... 等.). +* `Guid`是`Book`实体的主键类型. * 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling). #### BookType枚举 -下面是所有要用到的`BookType`枚举: +上面所用到的`BookType`枚举定义如下: ````C# namespace Acme.BookStore @@ -79,19 +79,31 @@ namespace Acme.BookStore #### 将Book实体添加到DbContext中 -EF Core需要你将实体和DbContext建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如: +EF Core需要你将实体和DbContext建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如下所示: ````C# public class BookStoreDbContext : AbpDbContext { - public DbSet Book { get; set; } + public DbSet Books { get; set; } ... } ```` +#### 配置你的Book实体 + +从`Acme.BookStore.EntityFrameworkCore` 项目中打开 BookStoreDbContextModelCreatingExtensions.cs 文件, 在 ConfigureBookStore 方法最后添加如下代码来配置Book实体: + +````C# + builder.Entity(b => + { + b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); + b.ConfigureExtraProperties(); + }); +```` + #### 添加新的Migration并更新数据库 -这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **Package Manager Console (PMC)** (工具/Nuget包管理器菜单),选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令: +这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **程序包管理器控制台(Package Manager Console) (PMC)** (工具/Nuget包管理器菜单),选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令: ![bookstore-pmc-add-book-migration](images/bookstore-pmc-add-book-migration-v2.png) @@ -136,10 +148,10 @@ namespace Acme.BookStore } ```` -* **DTO**类被用来在 **基础设施层** 和 **应用层** **传递数据**.查看[DTO文档](../../Data-Transfer-Objects.md)查看更多信息. -* 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到基础设施层. +* **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.查看[DTO文档](../../Data-Transfer-Objects.md)查看更多信息. +* 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. * `BookDto`继承自 `AuditedEntityDto`.跟上面定义的`Book`类一样具有一些审计属性. -* `[AutoMapFrom(typeof(Book))]`用来创建从`Book`类到`BookDto`的映射.使用这种方法.你可以将`Book`对象自动转换成`BookDto`对象(而不是手动复制所有的属性). +* `[AutoMapFrom(typeof(Book))]`用来创建从`Book`类到`BookDto`的AutoMapper映射.使用这种方法.你可以将`Book`对象自动转换成`BookDto`对象(而不是手动复制所有的属性). #### CreateUpdateBookDto @@ -171,7 +183,7 @@ namespace Acme.BookStore } ```` -* 这个DTO类在创建和更新书籍的时候被使用,用来从页面获取图书信息. +* 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息. * 类中的属性定义了数据注解(如`[Required]`)用来定义有效性验证.ABP会自动校验DTO的数据有效性. #### IBookAppService @@ -191,14 +203,14 @@ namespace Acme.BookStore Guid, //Book实体的主键 PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序 CreateUpdateBookDto, //用于创建书籍 - CreateUpdateBookDto> //用户更新书籍 + CreateUpdateBookDto> //用于更新书籍 { } } ```` -* 为应用服务定义接口不是必须的,不过,我们推荐这么做. +* 为应用服务定义接口不是必须的,不过,这是推荐的最佳实践. * `IAsyncCrudAppService`中定义了基础的 **CRUD**方法:`GetAsync`, `GetListAsync`, `CreateAsync`, `UpdateAsync` 和 `DeleteAsync`.不需要扩展它.取而代之,你可以继承空的`IApplicationService`接口定义你自己的方法. * `IAsyncCrudAppService`有一些变体,你可以为每一个方法使用单个或者多个的DTO.(译者注:意思是类似EntityDto和UpdateEntityDto可以用同一个,也可以分别单独指定 ) @@ -231,14 +243,14 @@ namespace Acme.BookStore ```` * `BookAppService`继承了`AsyncCrudAppService<...>`.`AsyncCrudAppService<...>`实现了上面定义的CRUD方法. -* `BookAppService`注入了`IRepository`,`IRepository`是默认为`Book`创建的仓储.ABP会自动为每一个聚合根(或实体)创建仓储.参考[仓储](../../Repositories.md). -* `BookAppService`使用了 `IObjectMapper` 将`Book`转换成`BookDto`,将`CreateUpdateBookDto`转换成`Book`.启动模板中使用了[AutoMapper](http://automapper.org/)作为映射工具.你可以像上面那样使用`AutoMapFrom` 和 `AutoMapTo`定义映射.查看[AutoMapper继承](../../AutoMapper-Integration.md)获取更多信息. +* `BookAppService`注入了`IRepository`,`IRepository`是默认为`Book`创建的仓储.ABP会自动为每一个聚合根(或实体)创建仓储.参考[仓储文档](../../Repositories.md). +* `BookAppService`使用了 `IObjectMapper` 将`Book`转换成`BookDto`,将`CreateUpdateBookDto`转换成`Book`.启动模板中使用了[AutoMapper](http://automapper.org/)作为对象映射提供程序.你可以像上面那样使用`AutoMapFrom` 和 `AutoMapTo`定义映射.查看[AutoMapper集成文档](../../AutoMapper-Integration.md)获取更多信息. ### 自动生成API Controllers 你通常需要创建 **Controllers** 将应用服务暴露为 **HTTP API**.这样浏览器或第三方客户端可以通过AJAX的方式访问它们. -ABP可以 **自动地** (../../AspNetCore/Auto-API-Controllers.md)将应用服务转换成MVC API Controllers. +ABP可以通过约定[**自动**](../../AspNetCore/Auto-API-Controllers.md)将应用服务转换成MVC API Controllers. #### Swagger UI @@ -250,13 +262,13 @@ ABP可以 **自动地** (../../AspNetCore/Auto-API-Controllers.md)将应用服 ### 动态JavaScript代理 -通过AJAX的方式调用HTTP API接口是很常见的,你可以使用`$.ajax`或这其他的工具来调用接口.当然,ABP中提供了更好的方式. +在Javascript端通过AJAX的方式调用HTTP API接口是很常见的,你可以使用`$.ajax`或这其他的工具来调用接口.当然,ABP中提供了更好的方式. ABP **自动** 为所有的API接口创建了JavaScript **代理**.因此,你可以像调用 **JavaScript function**一样调用任何接口. #### 在浏览器的开发者控制台中测试接口 -你可以使用你最爱的浏览器的 **开发者控制台** 中轻松测试JavaScript代理.运行程序,并打开浏览器的 **开发者工具**(快捷键:F12),切换到 **Console**,输入下面的代码并回车: +你可以使用你钟爱的浏览器的 **开发者控制台** 中轻松测试JavaScript代理.运行程序,并打开浏览器的 **开发者工具**(快捷键:F12),切换到 **Console** 标签,输入下面的代码并回车: ````js acme.bookStore.book.getList({}).done(function (result) { console.log(result); }); @@ -265,14 +277,14 @@ acme.bookStore.book.getList({}).done(function (result) { console.log(result); }) * `acme.bookStore`是`BookAppService`的命名空间,转换成了[驼峰命名](https://en.wikipedia.org/wiki/Camel_case). * `book`是`BookAppService`转换后的名字(去除了AppService后缀并转成了驼峰命名). * `getList`是定义在`AsyncCrudAppService`基类中的`GetListAsync`方法转换后的名字(去除了Async后缀并转成了驼峰命名). -* `{}`参数用来传递一个空的对象给`GetListAsync`方法.GetListAsync期望的参数是`PagedAndSortedResultRequestDto`类型,`PagedAndSortedResultRequestDto`类型中定义了分页和排序. +* `{}`参数用来传递一个空的对象给`GetListAsync`方法.GetListAsync期望的参数是`PagedAndSortedResultRequestDto`类型的对象,`PagedAndSortedResultRequestDto`类型中定义了分页和排序选项. * `getList`方法返回了一个`promise`.因此,你可以传递一个回调函数到`done`(或者`then`)方法中来获取服务返回的结果. 运行这段代码会产生下面的输出: ![bookstore-test-js-proxy-getlist](images/bookstore-test-js-proxy-getlist.png) -你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端和服务器的连接: +你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端到服务器端的通讯信息: ![bookstore-test-js-proxy-getlist-network](images/bookstore-test-js-proxy-getlist-network.png) @@ -292,10 +304,10 @@ successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7 ### 创建书籍页面 -现在我们来创建一些可见的可用的东西,我们使用[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start)代替经典的MVC.微软也推荐使用Razor Pages UI +现在我们来创建一些可见和可用的东西,取代经典的MVC,我们使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start). -在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Books`并添加一个名叫`Index.cshtml`的Razor Page. +在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Books`并添加一个名为`Index.cshtml`的Razor Page. ![bookstore-add-index-page](images/bookstore-add-index-page.png) @@ -310,7 +322,7 @@ successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7

Books

```` -* 改变Razor View Page Model默认的继承,使页面的 **inherits** 来自`BookStorePageBase`类(代替`PageModel`).`BookStorePageBase`类来自启动模板并提供了一些公开的属性/方法,这些属性/方法可以被所有的页面使用. +* 修改Razor View Page Model的默认继承,使页面 **继承** 自`BookStorePageBase`类(代替`PageModel`).`BookStorePageBase`类来自于启动模板,它提供了一些公开的可以被所有的页面使用的属性/方法. #### 将Books页面添加到主菜单 @@ -349,11 +361,11 @@ context.Menu.AddItem( ![bookstore-menu-items](images/bookstore-menu-items.png) -点击菜单就会调转到新增书籍的页面. +点击Books菜单项就会跳转到新增的书籍页面. #### 书籍列表 -我们会在页面上使用JQuery插件[Datatables.net](https://datatables.net/)来展示列表.Datatables可以完全通过AJAX工作,所以它很快而且有良好的用户体验.启动模板中已经配置好了Datatables,因此你可以在你的页面中直接使用,不需要引用样式和脚本文件. +我们会在页面上使用JQuery插件[Datatables.net](https://datatables.net/)来展示列表.Datatables可以完全通过AJAX工作,所以它很快而且有良好的用户体验.启动模板中已经配置好了Datatables插件,因此你可以在你的页面中直接使用,不需要引用样式和脚本文件. ##### 修改Index.cshtml @@ -388,7 +400,7 @@ context.Menu.AddItem( ```` -* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)可以将添加外部的 **scripts**添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[bundling & minification 文档](../../AspNetCore/Bundling-Minification.md)获取更多信息. +* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](../../AspNetCore/Bundling-Minification.md)获取更多信息. * `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](../../AspNetCore/Tag-Helpers.md). * 你可以像上面本地化菜单一样 **本地化** 列名. diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md index a7334030a6..9e54ac7a2f 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-II.md @@ -53,9 +53,9 @@ namespace Acme.BookStore.Pages.Books } ```` -* 这个类继承了 `BookStorePageModelBase` 而非默认的 `PageModel`. `BookStorePageModelBase` 继承了 `PageModel` 并且添加了一些Razor页面模型通用的属性和方法. -* 该类在 `Book` 属性上标记的 `[BindProperty]` 特性绑定了post请求提交上来的数据. -* 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 方法中调用了服务的 `CreateAsync` 方法. +* 该类派生于 `BookStorePageModelBase` 而非默认的 `PageModel`. `BookStorePageModelBase` 继承了 `PageModel` 并且添加了一些可以被你的page model类使用的通用属性和方法. +* `Book` 属性上的 `[BindProperty]` 特性将post请求提交上来的数据绑定到该属性上. +* 该类通过构造函数注入了 `IBookAppService` 应用服务,并且在 `OnPostAsync` 处理程序中调用了服务的 `CreateAsync` 方法. ##### CreateModal.cshtml @@ -82,7 +82,7 @@ namespace Acme.BookStore.Pages.Books * 这个 modal 使用 `abp-dynamic-form` Tag Helper 根据 `CreateBookViewModel` 类自动构建了表单. * `abp-model` 指定了 `Book` 属性为模型对象. - * `data-ajaxForm` 设置了表单通过AJAX提交. + * `data-ajaxForm` 设置了表单通过AJAX提交,而不是经典的页面回发. * `abp-form-content` tag helper 作为表单控件渲染位置的占位符 (这是可选的,只有你在 `abp-dynamic-form` 中像本示例这样添加了其他内容才需要). #### 添加 "New book" 按钮 @@ -211,7 +211,7 @@ namespace Acme.BookStore } ```` -* 仅仅是添加 `[AutoMapFrom(typeof(BookDto))]` 特性就可以创建上述映射关系. +* 仅添加 `[AutoMapFrom(typeof(BookDto))]` 特性就可以创建上述映射关系. #### EditModal.cshtml @@ -238,9 +238,9 @@ namespace Acme.BookStore ```` -除了以下几点,这个页面内容和 `CreateModal.cshtml` 非常相似: +这个页面内容和 `CreateModal.cshtml` 非常相似,除了以下几点: -* 此页面包含了一个 `abp-input` 以保存所编辑book实体的 `Id` 属性. +* 此页面包含了一个 `abp-input` 以保存所编辑book实体的 `Id` 属性值. * 此页面指定的post地址是 `Books/EditModal` ,并用文本 *Update* 作为 modal 标题. #### 为表格添加 "操作(Actions)" 下拉菜单 @@ -325,8 +325,8 @@ $(function () { ```` * 通过 `abp.localization.getResource('BookStore')` 可以在客户端使用服务器端定义的相同的本地化语言文本. -* 定义 `editModal` 为 `ModalManager` 来打开编辑用的 modal 对话框. -* 在 `columnDefs` 起始处新增一列作为 "Actions" 下拉按钮. +* 添加了一个名为 `editModal` 的新的 `ModalManager` 来打开编辑用的 modal 对话框. +* 在 `columnDefs` 起始处新增一列用于显示 "Actions" 下拉按钮. * "Edit" 操作只是简单调用 `editModal.open` 来打开编辑对话框. 现在,你可以运行程序,通过编辑操作来更新任一个book实体. @@ -354,7 +354,7 @@ $(function () { * `confirmMessage` 用来在实际执行 `action` 之前向用户进行确认. * 通过javascript代理方法 `acme.bookStore.book.delete` 执行一个AJAX请求来删除一个book实体. -* `abp.notify.info` 用来提示用户操作成功. +* `abp.notify.info` 用来在执行删除操作后显示一个toastr通知信息. 最终的 `index.js` 文件内容如下所示: diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md index 0514c8fc1a..80c28acc91 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md @@ -58,7 +58,7 @@ namespace Acme.BookStore } ```` -* 这里直接使用了identity模块实现的 `IIdentityDataSeeder` 接口,创建了一个admin角色和admin用户.你同样可以在你的测试代码中直接使用这些代码. +* 这里直接使用了identity模块实现的 `IIdentityDataSeeder` 接口,创建了一个admin角色和admin用户.你可以在测试代码中使用它们. * 你可以在 `BuildInternalAsync` 方法中添加你自己的测试数据. 按下方所示修改 `BookStoreTestDataBuilder` 类: diff --git a/docs/zh-Hans/Virtual-File-System.md b/docs/zh-Hans/Virtual-File-System.md index 05d8f59a85..84873e4aa8 100644 --- a/docs/zh-Hans/Virtual-File-System.md +++ b/docs/zh-Hans/Virtual-File-System.md @@ -1,6 +1,6 @@ ## 虚拟文件系统 -虚拟文件系统可以管理文件系统(磁盘)上实际上不存在的文件. 它主要用于将(js, css, image, cshtml ...)文件嵌入到程序集中, 并在运行时将它们用作物理文件. +虚拟文件系统使得管理物理上不存在于文件系统中(磁盘)的文件成为可能. 它主要用于将(js, css, image, cshtml ...)文件嵌入到程序集中, 并在运行时将它们象物理文件一样使用. ### Volo.Abp.VirtualFileSystem nuget包 @@ -30,12 +30,12 @@ namespace MyCompany.MyProject #### 注册嵌入式文件 -首先需要把文件标记为嵌入式资源将文件嵌入到程序集中. 最简单的方式是在 **解决方案管理器** 中选择文件, 然后找到 **"属性"** 窗口将 **"生成操作"** 设置为 **"嵌入式资源"**. +要将文件嵌入到程序集中, 首先需要把该文件标记为嵌入式资源. 最简单的方式是在 **解决方案管理器** 中选择文件, 然后找到 **"属性"** 窗口将 **"生成操作"** 设置为 **"嵌入式资源"**. 例如: ![build-action-embedded-resource-sample](images/build-action-embedded-resource-sample.png) -如果需要添加多个文件, 这会很乏味. 这时可以直接编辑 **.csproj** 文件: +如果需要添加多个文件, 这样做会很乏味. 作为选择, 你可以直接编辑 **.csproj** 文件: ````C# @@ -45,7 +45,7 @@ namespace MyCompany.MyProject 此配置以递归方式添加项目的 **MyResources** 文件夹下的所有文件(包括将来新添加的文件). -然后应该使用 `VirtualFileSystemOptions` 来配置模块, 以便将嵌入式文件注册到虚拟文件系统. 例如: +然后需要使用 `VirtualFileSystemOptions` 来配置模块, 以便将嵌入式文件注册到虚拟文件系统. 例如: ````C# using Microsoft.Extensions.DependencyInjection; @@ -105,9 +105,9 @@ public class MyService #### 在开发过程中处理嵌入式文件 -通过引用程序集(或添加nuget包)将文件嵌入模块程序集并从另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难. +将文件嵌入到模块程序集中并能够通过引用程序集(或添加nuget包)在另一个项目中使用它对于创建可重用模块非常有价值. 但是, 这使得开发模块本身变得有点困难. -假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以进行更改. 显然, 这是非常耗时和乏味的. +假设你正在开发一个包含嵌入式JavaScript文件的模块. 当你更改文件时, 你必须重新编译项目, 重新启动应用程序并刷新浏览器页面以使更改生效. 显然, 这是非常耗时和乏味的. 我们需要的是应用程序在开发时直接使用物理文件的能力, 让浏览器刷新时同步JavaScript文件的任何更改. `ReplaceEmbeddedByPhysical` 方法使其成为可能. @@ -145,7 +145,7 @@ public class MyWebAppModule : AbpModule * 虚拟文件可以像Web应用程序上的物理(静态)文件一样使用. * Razor Views, Razor Pages, js, css, 图像文件和所有其他Web内容可以嵌入到程序集中并像物理文件一样使用. -* 应用程序(或其他模块)可以覆盖模块的虚拟文件, 就像将具有同名和同扩展名文件放入虚拟文件的同一文件夹中一样. +* 应用程序(或其他模块)可以覆盖模块的虚拟文件, 就像将具有相同名称和扩展名的文件放入虚拟文件的同一文件夹中一样. #### 虚拟文件中间件 @@ -155,12 +155,12 @@ public class MyWebAppModule : AbpModule app.UseVirtualFiles(); ```` -在静态文件中间件之后添加虚拟文件中间件, 可以通过放置在同一位置,使用物理文件来覆盖虚拟文件. +在静态文件中间件之后添加虚拟文件中间件, 使得通过在虚拟文件相同的位置放置物理文件, 从而用物理文件覆盖虚拟文件成为可能. -> 虚拟文件中间件只是像静态文件一样提供虚拟wwwroot文件夹内容. +> 虚拟文件中间件可以虚拟wwwroot文件夹中的内容 - 就像静态文件一样. #### Views & Pages -无需任何配置即可在应用程序中使用嵌入式的 razor Views/pages. 只需要将它们放入模块开发中的标准 Views/Pages 虚拟文件夹即可. +无需任何配置即可在应用程序中使用嵌入式的 razor Views/pages. 只需要将它们放置在要开发的模块中的标准 Views/Pages 虚拟文件夹即可. 如果模块/应用程序将新文件放置同一位置, 则会覆盖嵌入式的 Views/Pages. diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 6b98655e85..8e88cf6921 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -67,8 +67,7 @@ "path": "Exception-Handling.md" }, { - "text": "验证", - "path": "Validation.md" + "text": "验证" }, { "text": "授权" @@ -145,6 +144,7 @@ }, { "text": "领域驱动设计", + "path": "Domain-Driven-Design.md", "items": [ { "text": "领域层", @@ -180,8 +180,7 @@ "text": "数据传输对象(DTO)" }, { - "text": "工作单元", - "path": "Unit-Of-Work.md" + "text": "工作单元" } ] } diff --git a/docs/zh-Hans/images/docs-create-project.jpg b/docs/zh-Hans/images/docs-create-project.jpg new file mode 100644 index 0000000000..742f5e307f Binary files /dev/null and b/docs/zh-Hans/images/docs-create-project.jpg differ diff --git a/docs/zh-Hans/images/github-access-token-private-repo.jpg b/docs/zh-Hans/images/github-access-token-private-repo.jpg new file mode 100644 index 0000000000..5bc53cc63d Binary files /dev/null and b/docs/zh-Hans/images/github-access-token-private-repo.jpg differ diff --git a/docs/zh-Hans/images/github-access-token-public-repo.jpg b/docs/zh-Hans/images/github-access-token-public-repo.jpg new file mode 100644 index 0000000000..b909c2cfe5 Binary files /dev/null and b/docs/zh-Hans/images/github-access-token-public-repo.jpg differ diff --git a/docs/zh-Hans/images/github-myusername.jpg b/docs/zh-Hans/images/github-myusername.jpg new file mode 100644 index 0000000000..4ac57eeed4 Binary files /dev/null and b/docs/zh-Hans/images/github-myusername.jpg differ diff --git a/docs/zh-Hans/images/microservice-sample-update-database-authserver.png b/docs/zh-Hans/images/microservice-sample-update-database-authserver.png new file mode 100644 index 0000000000..689019b587 Binary files /dev/null and b/docs/zh-Hans/images/microservice-sample-update-database-authserver.png differ diff --git a/docs/zh-Hans/images/microservice-sample-update-database-products.png b/docs/zh-Hans/images/microservice-sample-update-database-products.png new file mode 100644 index 0000000000..b49c2a8f8e Binary files /dev/null and b/docs/zh-Hans/images/microservice-sample-update-database-products.png differ diff --git a/docs/zh-Hans/images/volodocs-iis-add-website.png b/docs/zh-Hans/images/volodocs-iis-add-website.png new file mode 100644 index 0000000000..878b3e8f0c Binary files /dev/null and b/docs/zh-Hans/images/volodocs-iis-add-website.png differ diff --git a/docs/zh-Hans/images/volodocs-iis-application-pool.png b/docs/zh-Hans/images/volodocs-iis-application-pool.png new file mode 100644 index 0000000000..cc5186f50b Binary files /dev/null and b/docs/zh-Hans/images/volodocs-iis-application-pool.png differ diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 6c6608315e..be77f0ab24 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -1,7 +1,6 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2036 +VisualStudioVersion = 15.0.28307.168 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}" EndProject @@ -215,6 +214,30 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Features", "src\Vo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Features.Tests", "test\Volo.Abp.Features.Tests\Volo.Abp.Features.Tests.csproj", "{575BEFA1-19C2-49B1-8D31-B5D4472328DE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp", "src\Volo.Abp\Volo.Abp.csproj", "{6C161F55-54B6-42A5-B177-3B0ED50323C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Authentication.JwtBearer", "src\Volo.Abp.AspNetCore.Authentication.JwtBearer\Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj", "{46C6336C-A1D8-4858-98CE-6F4C698C5A77}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli", "src\Volo.Abp.Cli\Volo.Abp.Cli.csproj", "{69168816-4394-4DDA-BB6B-C21983D37F0B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.FluentValidation", "src\Volo.Abp.FluentValidation\Volo.Abp.FluentValidation.csproj", "{43D5FE61-ECBF-4B16-AD95-0043E18EB93A}" +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.EntityFrameworkCore.Sqlite", "src\Volo.Abp.EntityFrameworkCore.Sqlite\Volo.Abp.EntityFrameworkCore.Sqlite.csproj", "{58CF8957-5045-4F81-884D-72DF48F721CC}" +EndProject +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 Debug|Any CPU = Debug|Any CPU @@ -637,6 +660,54 @@ Global {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {575BEFA1-19C2-49B1-8D31-B5D4472328DE}.Release|Any CPU.Build.0 = Release|Any CPU + {6C161F55-54B6-42A5-B177-3B0ED50323C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C161F55-54B6-42A5-B177-3B0ED50323C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C161F55-54B6-42A5-B177-3B0ED50323C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C161F55-54B6-42A5-B177-3B0ED50323C1}.Release|Any CPU.Build.0 = Release|Any CPU + {46C6336C-A1D8-4858-98CE-6F4C698C5A77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46C6336C-A1D8-4858-98CE-6F4C698C5A77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46C6336C-A1D8-4858-98CE-6F4C698C5A77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46C6336C-A1D8-4858-98CE-6F4C698C5A77}.Release|Any CPU.Build.0 = Release|Any CPU + {69168816-4394-4DDA-BB6B-C21983D37F0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69168816-4394-4DDA-BB6B-C21983D37F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69168816-4394-4DDA-BB6B-C21983D37F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69168816-4394-4DDA-BB6B-C21983D37F0B}.Release|Any CPU.Build.0 = Release|Any CPU + {43D5FE61-ECBF-4B16-AD95-0043E18EB93A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43D5FE61-ECBF-4B16-AD95-0043E18EB93A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43D5FE61-ECBF-4B16-AD95-0043E18EB93A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43D5FE61-ECBF-4B16-AD95-0043E18EB93A}.Release|Any CPU.Build.0 = Release|Any CPU + {E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + {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 + {58CF8957-5045-4F81-884D-72DF48F721CC}.Release|Any CPU.Build.0 = Release|Any CPU + {3DA9923E-048E-4FE7-9748-3A0194F5D196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 @@ -746,6 +817,18 @@ Global {88F6D091-CA16-4B71-9499-8D5B8FA2E712} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {01E3D389-8872-4EB1-9D3D-13B6ED54DE0E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {575BEFA1-19C2-49B1-8D31-B5D4472328DE} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {6C161F55-54B6-42A5-B177-3B0ED50323C1} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {46C6336C-A1D8-4858-98CE-6F4C698C5A77} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {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} + {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.Authentication.JwtBearer/Microsoft/AspNetCore/Builder/JwtTokenMiddleware.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Microsoft/AspNetCore/Builder/JwtTokenMiddleware.cs new file mode 100644 index 0000000000..4ff54f39bf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Microsoft/AspNetCore/Builder/JwtTokenMiddleware.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; + +namespace Microsoft.AspNetCore.Builder +{ + public static class ApplicationBuilderAbpJwtTokenMiddlewareExtension + { + public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app, string schema = JwtBearerDefaults.AuthenticationScheme) + { + return app.Use(async (ctx, next) => + { + if (ctx.User.Identity?.IsAuthenticated != true) + { + var result = await ctx.AuthenticateAsync(schema); + if (result.Succeeded && result.Principal != null) + { + ctx.User = result.Principal; + } + } + + await next(); + }); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj new file mode 100644 index 0000000000..7d8a961507 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj @@ -0,0 +1,24 @@ + + + + + + netstandard2.0 + Volo.Abp.AspNetCore.Authentication.JwtBearer + Volo.Abp.AspNetCore.Authentication.JwtBearer + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs new file mode 100644 index 0000000000..8698e2283d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.JwtBearer/Volo/Abp/AspNetCore/Authentication/JwtBearer/AbpAspNetCoreAuthenticationJwtBearerModule.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Modularity; +using Volo.Abp.Security; + +namespace Volo.Abp.AspNetCore.Authentication.JwtBearer +{ + [DependsOn(typeof(AbpSecurityModule))] + public class AbpAspNetCoreAuthenticationJwtBearerModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj index 722b6033ed..54c93df3ec 100644 --- a/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj @@ -1,4 +1,4 @@ - + @@ -14,7 +14,6 @@
- diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperService.cs index 566cbcffbc..53c56295ce 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperService.cs @@ -17,13 +17,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers where TTagHelper : TagHelper { protected const string FormGroupContents = "FormGroupContents"; - protected const string NavItemContents = "FormGroupContents"; protected const string TabItems = "TabItems"; protected const string AccordionItems = "AccordionItems"; protected const string BreadcrumbItemsContent = "BreadcrumbItemsContent"; protected const string CarouselItemsContent = "CarouselItemsContent"; protected const string TabItemsDataTogglePlaceHolder = "{_data_toggle_Placeholder_}"; - protected const string TabItemsVerticalPillPlaceHolder = "{_vertical_pill_Placeholder_}"; protected const string TabItemNamePlaceHolder = "{_Tab_Tag_Name_Placeholder_}"; protected const string AbpFormContentPlaceHolder = "{_AbpFormContentPlaceHolder_}"; protected const string AbpTabItemActivePlaceholder = "{_Tab_Active_Placeholder_}"; @@ -31,9 +29,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers protected const string AbpTabItemShowActivePlaceholder = "{_Tab_Show_Active_Placeholder_}"; protected const string AbpBreadcrumbItemActivePlaceholder = "{_Breadcrumb_Active_Placeholder_}"; protected const string AbpCarouselItemActivePlaceholder = "{_CarouselItem_Active_Placeholder_}"; - protected const string AbpNavItemActivePlaceholder = "{_NavItem_Active_Placeholder_}"; - protected const string AbpNavItemResponsiveFlexPlaceholder = "{_NavItem_Responsive_Flex_Placeholder_}"; - protected const string AbpNavItemResponsiveAlignPlaceholder = "{_NavItem_Responsive_Align_Placeholder_}"; protected const string AbpTabItemSelectedPlaceholder = "{_Tab_Selected_Placeholder_}"; protected const string AbpAccordionParentIdPlaceholder = "{_Parent_Accordion_Id_}"; 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/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index cdf1f30788..dc2f04b623 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -35,10 +35,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form TagHelper.AspFor.Name, SurroundInnerHtmlAndGet(context, output, innerHtml, isCheckbox), order, - out var surpress + out var suppress ); - if (surpress) + if (suppress) { output.SuppressOutput(); } @@ -389,10 +389,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return idAttr != null ? "for=\"" + idAttr.Value + "\"" : ""; } - protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool surpress) + protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) { var list = context.GetValue>(FormGroupContents) ?? new List(); - surpress = list == null; + suppress = list == null; if (list != null && !list.Any(igc => igc.HtmlContent.Contains("id=\"" + propertyName.Replace('.', '_') + "\""))) { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs index 6ec2911714..f89d3c1fee 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs @@ -29,9 +29,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form var html = GetHtml(context, output, selectItems); - AddGroupToFormGroupContents(context, TagHelper.AspFor.Name, html, order, out var surpress); + AddGroupToFormGroupContents(context, TagHelper.AspFor.Name, html, order, out var suppress); - if (surpress) + if (suppress) { output.SuppressOutput(); } @@ -157,10 +157,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return TagHelper.AspFor.ModelExplorer.Model?.ToString(); } - protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool surpress) + protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) { var list = context.GetValue>(FormGroupContents) ?? new List(); - surpress = list == null; + suppress = list == null; if (list != null && !list.Any(igc => igc.HtmlContent.Contains("id=\"" + propertyName.Replace('.', '_') + "\""))) { 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 cd3294d66f..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 @@ -33,9 +33,9 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form var order = TagHelper.AspFor.ModelExplorer.GetDisplayOrder(); - AddGroupToFormGroupContents(context, TagHelper.AspFor.Name, SurroundInnerHtmlAndGet(context, output, innerHtml), order, out var surpress); + AddGroupToFormGroupContents(context, TagHelper.AspFor.Name, SurroundInnerHtmlAndGet(context, output, innerHtml), order, out var suppress); - if (surpress) + if (suppress) { output.SuppressOutput(); } @@ -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; } @@ -304,10 +319,10 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form return idAttr != null ? "for=\"" + idAttr.Value + "\"" : ""; } - protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool surpress) + protected virtual void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) { var list = context.GetValue>(FormGroupContents) ?? new List(); - surpress = list == null; + suppress = list == null; if (list != null && !list.Any(igc => igc.HtmlContent.Contains("id=\"" + propertyName.Replace('.', '_') + "\""))) { 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..633b783ad0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo.Abp.AspNetCore.Mvc.UI.Dashboards.csproj @@ -0,0 +1,35 @@ + + + + + + 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/AbpBasicDashboardScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/AbpBasicDashboardScriptContributor.cs new file mode 100644 index 0000000000..f351abd952 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/AbpBasicDashboardScriptContributor.cs @@ -0,0 +1,8 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + public class AbpBasicDashboardScriptContributor : BundleContributor + { + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/AbpBasicDashboardStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/AbpBasicDashboardStyleContributor.cs new file mode 100644 index 0000000000..5c074783fc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/AbpBasicDashboardStyleContributor.cs @@ -0,0 +1,8 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Dashboards +{ + public class AbpBasicDashboardStyleContributor : BundleContributor + { + } +} 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..9309bcdf7d --- /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,29 @@ +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 + { + private readonly DashboardOptions _dashboardOptions; + private readonly WidgetOptions _widgetOptions; + + public DashboardViewComponent(IOptions dashboardOptions, IOptions widgetOptions) + { + _dashboardOptions = dashboardOptions.Value; + _widgetOptions = widgetOptions.Value; + } + + public IViewComponentResult Invoke(string dashboardName) + { + var dashboard = _dashboardOptions.Dashboards.Single(d => d.Name.Equals(dashboardName)); + + var model = new DashboardViewModel(dashboard, _widgetOptions.Widgets); + + 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..47405ae63c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/DashboardViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +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 List Widgets { get; set; } + + public DashboardViewModel(DashboardDefinition dashboard, List widgets) + { + Dashboard = dashboard; + Widgets = widgets; + } + + public WidgetDefinition GetWidget(string name) + { + return Widgets.Single(d => d.Name.Equals(name)); + } + + public async Task CheckPermissionsAsync(IAuthorizationService authorizationService, WidgetDefinition widget) + { + foreach (var permission in widget.RequiredPermissions) + { + if (!await authorizationService.IsGrantedAsync(permission)) + { + return false; + } + } + + return true; + } + } +} 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..89fecb905c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Dashboards/Volo/Abp/AspNetCore/Mvc/UI/Dashboards/Components/Dashboard/Default.cshtml @@ -0,0 +1,26 @@ +@using Microsoft.AspNetCore.Authorization +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Widgets +@inject IAuthorizationService AuthorizationService +@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); + if (await Model.CheckPermissionsAsync(AuthorizationService, widgetDefinition)) + { + widgetDefinition.DefaultDimensions = widgetConfiguration.Dimensions ?? widgetDefinition.DefaultDimensions ?? new WidgetDimensions(6, 4); + + + + @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.Theme.Basic/Themes/Basic/Layouts/Application.cshtml b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml index 4864f2dafe..1cd530d021 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml @@ -28,9 +28,12 @@ @(await Component.InvokeAsync()) - +
@(await Component.InvokeAsync()) +
+ @RenderSection("content_toolbar", false) +
@RenderBody()
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js index c9d11b5fac..3b177ea3d5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/jquery/jquery-extensions.js @@ -87,7 +87,7 @@ return str; }; - $.fn.serializeFormToObject = function (camelCase = true) { + $.fn.serializeFormToObject = function (camelCase) { //serialize to array var data = $(this).serializeArray(); @@ -108,7 +108,7 @@ //map to object var obj = {}; - if (camelCase) { + if (camelCase !== undefined ? camelCase : true) { data.forEach(function (d) { d.name = toCamelCase(d.name); }); 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..b1b404215e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Widgets/Volo/Abp/AspNetCore/Mvc/UI/Widgets/WidgetDefinition.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +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 List RequiredPermissions { get; set; } + + public WidgetDefinition( + [NotNull] string name, + [CanBeNull] ILocalizableString displayName, + [NotNull] Type viewComponentType) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + ViewComponentType = Check.NotNull(viewComponentType, nameof(viewComponentType)); + DisplayName = displayName ?? new FixedLocalizableString(name); + RequiredPermissions = new List(); + } + + public WidgetDefinition AddRequiredPermission(string permissionName) + { + RequiredPermissions.Add(permissionName); + return this; + } + + public WidgetDefinition SetDefaultDimension(int width, int height) + { + DefaultDimensions = new WidgetDimensions(width, height); + return this; + } + } +} 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..821e53e853 --- /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 widget = _widgetOptions.Widgets.Single(w=>w.Name.Equals(widgetName)); + + return await componentHelper.InvokeAsync(widget.ViewComponentType, args ?? new object()); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs index 8fd16f5864..62258329ad 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/RazorPages/AbpPageModel.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -21,19 +22,43 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages { public abstract class AbpPageModel : PageModel { - public IClock Clock { get; set; } + public IServiceProvider ServiceProvider { get; set; } + protected readonly object ServiceProviderLock = new object(); + protected TService LazyGetRequiredService(ref TService reference) + { + if (reference == null) + { + lock (ServiceProviderLock) + { + if (reference == null) + { + reference = ServiceProvider.GetRequiredService(); + } + } + } + + return reference; + } + + public IClock Clock => LazyGetRequiredService(ref _clock); + private IClock _clock; public AlertList Alerts => AlertManager.Alerts; - public IUnitOfWorkManager UnitOfWorkManager { get; set; } + public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager); + private IUnitOfWorkManager _unitOfWorkManager; - public IObjectMapper ObjectMapper { get; set; } + public IObjectMapper ObjectMapper => LazyGetRequiredService(ref _objectMapper); + private IObjectMapper _objectMapper; - public IGuidGenerator GuidGenerator { get; set; } + public IGuidGenerator GuidGenerator => LazyGetRequiredService(ref _guidGenerator); + private IGuidGenerator _guidGenerator; - public ILoggerFactory LoggerFactory { get; set; } + public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); + private ILoggerFactory _loggerFactory; - public IStringLocalizerFactory StringLocalizerFactory { get; set; } + public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory); + private IStringLocalizerFactory _stringLocalizerFactory; public IStringLocalizer L { @@ -56,17 +81,23 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages protected Type LocalizationResourceType { get; set; } - public ICurrentUser CurrentUser { get; set; } + public ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser); + private ICurrentUser _currentUser; - public ICurrentTenant CurrentTenant { get; set; } + public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant); + private ICurrentTenant _currentTenant; - public ISettingProvider SettingProvider { get; set; } + public ISettingProvider SettingProvider => LazyGetRequiredService(ref _settingProvider); + private ISettingProvider _settingProvider; - public IModelStateValidator ModelValidator { get; set; } + public IModelStateValidator ModelValidator => LazyGetRequiredService(ref _modelValidator); + private IModelStateValidator _modelValidator; - public IAuthorizationService AuthorizationService { get; set; } + public IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService); + private IAuthorizationService _authorizationService; - public IAlertManager AlertManager { get; set; } + public IAlertManager AlertManager => LazyGetRequiredService(ref _alertManager); + private IAlertManager _alertManager; protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningExtensions.cs new file mode 100644 index 0000000000..c239a42a45 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningExtensions.cs @@ -0,0 +1,63 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Versioning; +using Volo.Abp.ApiVersioning; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Conventions; +using Volo.Abp.AspNetCore.Mvc.Versioning; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class AbpApiVersioningExtensions + { + public static IServiceCollection AddAbpApiVersioning(this IServiceCollection services, Action setupAction) + { + services.AddTransient(); + services.AddTransient(); + + services.AddApiVersioning(setupAction); + + return services; + } + + public static void ConfigureAbp(this ApiVersioningOptions options, AbpAspNetCoreMvcOptions mvcOptions) + { + foreach (var setting in mvcOptions.ConventionalControllers.ConventionalControllerSettings) + { + if (setting.ApiVersionConfigurer == null) + { + ConfigureApiVersionsByConvention(options, setting); + } + else + { + setting.ApiVersionConfigurer.Invoke(options); + } + } + } + + private static void ConfigureApiVersionsByConvention(ApiVersioningOptions options, ConventionalControllerSetting setting) + { + foreach (var controllerType in setting.ControllerTypes) + { + var controllerBuilder = options.Conventions.Controller(controllerType); + + if (setting.ApiVersions.Any()) + { + foreach (var apiVersion in setting.ApiVersions) + { + controllerBuilder.HasApiVersion(apiVersion); + } + } + else + { + if (!controllerType.IsDefined(typeof(ApiVersionAttribute), true)) + { + controllerBuilder.IsApiVersionNeutral(); + } + } + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs deleted file mode 100644 index 13148a4bc6..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Microsoft/Extensions/DependencyInjection/AbpApiVersioningOptionsExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Versioning; -using Volo.Abp.ApiVersioning; -using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.AspNetCore.Mvc.Conventions; -using Volo.Abp.AspNetCore.Mvc.Versioning; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class AbpApiVersioningOptionsExtensions - { - public static void ConfigureAbp(this ApiVersioningOptions options, IServiceCollection services) - { - //TODO: Use new builder will be released with Api Versioning 2.1 instead of reflection! - - services.AddTransient(); - - services.Configure(op => - { - //TODO: Configuring api version should be done directly inside ConfigureAbp, - //TODO: not in a callback that will be called by MVC later! For that, we immediately need to controllerAssemblySettings - - foreach (var setting in op.ConventionalControllers.ConventionalControllerSettings) - { - if (setting.ApiVersionConfigurer == null) - { - ConfigureApiVersionsByConvention(options, setting); - } - else - { - setting.ApiVersionConfigurer.Invoke(options); - } - } - }); - } - - private static void ConfigureApiVersionsByConvention(ApiVersioningOptions options, ConventionalControllerSetting setting) - { - foreach (var controllerType in setting.ControllerTypes) - { - var controllerBuilder = options.Conventions.Controller(controllerType); - - if (setting.ApiVersions.Any()) - { - foreach (var apiVersion in setting.ApiVersions) - { - controllerBuilder.HasApiVersion(apiVersion); - } - } - else - { - if (!controllerType.IsDefined(typeof(ApiVersionAttribute), true)) - { - controllerBuilder.IsApiVersionNeutral(); - } - } - } - } - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj index e99572852e..4ee308e591 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj @@ -23,7 +23,7 @@ - +
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs index a5d206cf55..943d0d6e3f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs @@ -2,12 +2,15 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Aspects; using Volo.Abp.AspNetCore.Mvc.Validation; using Volo.Abp.Features; using Volo.Abp.Guids; +using Volo.Abp.Localization; using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectMapping; using Volo.Abp.Timing; @@ -18,27 +21,72 @@ namespace Volo.Abp.AspNetCore.Mvc { public abstract class AbpController : Controller, IAvoidDuplicateCrossCuttingConcerns { - public IUnitOfWorkManager UnitOfWorkManager { get; set; } + public IServiceProvider ServiceProvider { get; set; } + protected readonly object ServiceProviderLock = new object(); + protected TService LazyGetRequiredService(ref TService reference) + { + if (reference == null) + { + lock (ServiceProviderLock) + { + if (reference == null) + { + reference = ServiceProvider.GetRequiredService(); + } + } + } + + return reference; + } - public IObjectMapper ObjectMapper { get; set; } + public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager); + private IUnitOfWorkManager _unitOfWorkManager; - public IGuidGenerator GuidGenerator { get; set; } + public IObjectMapper ObjectMapper => LazyGetRequiredService(ref _objectMapper); + private IObjectMapper _objectMapper; - public ILoggerFactory LoggerFactory { get; set; } + public IGuidGenerator GuidGenerator => LazyGetRequiredService(ref _guidGenerator); + private IGuidGenerator _guidGenerator; - public ICurrentUser CurrentUser { get; set; } + public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); + private ILoggerFactory _loggerFactory; - public ICurrentTenant CurrentTenant { get; set; } + public ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser); + private ICurrentUser _currentUser; - public IAuthorizationService AuthorizationService { get; set; } + public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant); + private ICurrentTenant _currentTenant; + + public IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService); + private IAuthorizationService _authorizationService; protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current; - public IClock Clock { get; set; } + public IClock Clock => LazyGetRequiredService(ref _clock); + private IClock _clock; + + public IModelStateValidator ModelValidator => LazyGetRequiredService(ref _modelValidator); + private IModelStateValidator _modelValidator; - public IModelStateValidator ModelValidator { get; set; } + public IFeatureChecker FeatureChecker => LazyGetRequiredService(ref _featureChecker); + private IFeatureChecker _featureChecker; - public IFeatureChecker FeatureChecker { get; set; } + public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory); + private IStringLocalizerFactory _stringLocalizerFactory; + + public IStringLocalizer L => _localizer ?? (_localizer = StringLocalizerFactory.Create(LocalizationResource)); + private IStringLocalizer _localizer; + + protected Type LocalizationResource + { + get => _localizationResource; + set + { + _localizationResource = value; + _localizer = null; + } + } + private Type _localizationResource = typeof(DefaultResource); public List AppliedCrossCuttingConcerns { get; } = new List(); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpDataAnnotationAutoLocalizationMetadataDetailsProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpDataAnnotationAutoLocalizationMetadataDetailsProvider.cs index 2274f0a9b6..d9de135e7f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpDataAnnotationAutoLocalizationMetadataDetailsProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpDataAnnotationAutoLocalizationMetadataDetailsProvider.cs @@ -39,6 +39,11 @@ namespace Volo.Abp.AspNetCore.Mvc return; } + if (context.Key.Name.IsNullOrWhiteSpace()) + { + return; + } + if (_localizationOptions.Value.Value.DataAnnotationLocalizerProvider == null) { return; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index 1a9646d13f..3eb7d18e39 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -85,7 +85,7 @@ namespace Volo.Abp.AspNetCore.Mvc method, apiDescription.RelativePath, apiDescription.HttpMethod, - setting?.ApiVersions.Select(v => v.ToString()).ToList() ?? new List() //TODO: Also get from ApiVersion attributes if available..? + GetSupportedVersions(controllerType, method, setting) )); AddParameterDescriptionsToModel(actionModel, method, apiDescription); @@ -126,6 +126,29 @@ namespace Volo.Abp.AspNetCore.Mvc return methodNameBuilder.ToString(); } + private static List GetSupportedVersions(Type controllerType, MethodInfo method, ConventionalControllerSetting setting) + { + var supportedVersions = new List(); + + var mapToAttributes = method.GetCustomAttributes().ToArray(); + if (mapToAttributes.Any()) + { + supportedVersions.AddRange( + mapToAttributes.SelectMany(a => a.Versions) + ); + } + else + { + supportedVersions.AddRange( + controllerType.GetCustomAttributes().SelectMany(a => a.Versions) + ); + + setting?.ApiVersions.ForEach(supportedVersions.Add); + } + + return supportedVersions.Select(v => v.ToString()).Distinct().ToList(); + } + private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription) { if (!apiDescription.ParameterDescriptions.Any()) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalApiControllerSpecification.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalApiControllerSpecification.cs new file mode 100644 index 0000000000..59b2184a53 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalApiControllerSpecification.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.AspNetCore.Mvc.Conventions +{ + public class AbpConventionalApiControllerSpecification : IApiControllerSpecification + { + private readonly AbpAspNetCoreMvcOptions _options; + + public AbpConventionalApiControllerSpecification(IOptions options) + { + _options = options.Value; + } + + public bool IsSatisfiedBy(ControllerModel controller) + { + var configuration = _options + .ConventionalControllers + .ConventionalControllerSettings + .GetSettingOrNull(controller.ControllerType.AsType()); + + return configuration != null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj b/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj index 37c728f175..fb85cbb3ec 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj +++ b/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj @@ -29,7 +29,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs index 502fbda5bb..f36a7f4860 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs @@ -36,7 +36,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling // We can't do anything if the response has already started, just abort. if (httpContext.Response.HasStarted) { - _logger.LogWarning("An exception occured, but response has already started!"); + _logger.LogWarning("An exception occurred, but response has already started!"); throw; } 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.AutoMapper/Volo.Abp.AutoMapper.csproj b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj index ae384414a3..029669c348 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj +++ b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj @@ -18,7 +18,7 @@
- + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacServiceProviderFactory.cs b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacServiceProviderFactory.cs index 8e0d4f574d..21a7ca331d 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacServiceProviderFactory.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacServiceProviderFactory.cs @@ -38,7 +38,7 @@ namespace Autofac.Extensions.DependencyInjection /// /// Initializes a new instance of the class. /// - /// Action on a that adds component registrations to the conatiner. + /// Action on a that adds component registrations to the container. public AutofacServiceProviderFactory(Action configurationAction = null) { _configurationAction = configurationAction ?? (builder => { }); diff --git a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/ServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/ServiceCollectionExtensions.cs index 5552cda7b2..1195cb9fb1 100644 --- a/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/ServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/ServiceCollectionExtensions.cs @@ -37,7 +37,7 @@ namespace Autofac.Extensions.DependencyInjection /// Adds the to the service collection. /// /// The service collection to add the factory to. - /// Action on a that adds component registrations to the conatiner. + /// Action on a that adds component registrations to the container. /// The service collection. public static IServiceCollection AddAutofac(this IServiceCollection services, Action configurationAction = null) { diff --git a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj index c8737bbfeb..3e68488105 100644 --- a/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj +++ b/framework/src/Volo.Abp.Autofac/Volo.Abp.Autofac.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobInfo.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobInfo.cs index 7e1154ec7e..6241bc81f9 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobInfo.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/BackgroundJobInfo.cs @@ -42,7 +42,7 @@ namespace Volo.Abp.BackgroundJobs public virtual DateTime? LastTryTime { get; set; } /// - /// This is true if this job is continously failed and will not be executed again. + /// This is true if this job is continuously failed and will not be executed again. /// public virtual bool IsAbandoned { get; set; } diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs index 2e3e8879ec..1dc6d7c700 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/InMemoryBackgroundJobStore.cs @@ -23,19 +23,19 @@ namespace Volo.Abp.BackgroundJobs _jobs = new ConcurrentDictionary(); } - public Task FindAsync(Guid jobId) + public virtual Task FindAsync(Guid jobId) { return Task.FromResult(_jobs.GetOrDefault(jobId)); } - public Task InsertAsync(BackgroundJobInfo jobInfo) + public virtual Task InsertAsync(BackgroundJobInfo jobInfo) { _jobs[jobInfo.Id] = jobInfo; return Task.FromResult(0); } - public Task> GetWaitingJobsAsync(int maxResultCount) + public virtual Task> GetWaitingJobsAsync(int maxResultCount) { var waitingJobs = _jobs.Values .Where(t => !t.IsAbandoned && t.NextTryTime <= Clock.Now) @@ -48,14 +48,14 @@ namespace Volo.Abp.BackgroundJobs return Task.FromResult(waitingJobs); } - public Task DeleteAsync(Guid jobId) + public virtual Task DeleteAsync(Guid jobId) { _jobs.TryRemove(jobId, out _); return Task.FromResult(0); } - public Task UpdateAsync(BackgroundJobInfo jobInfo) + public virtual Task UpdateAsync(BackgroundJobInfo jobInfo) { if (jobInfo.IsAbandoned) { diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs index 70f13c6b18..6be122be8f 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/CacheOptions.cs @@ -10,11 +10,13 @@ namespace Volo.Abp.Caching /// Global Cache entry options. /// public DistributedCacheEntryOptions GlobalCacheEntryOptions { get; set; } + /// /// List of all cache configurators. /// (func argument:Name of cache) /// public List> CacheConfigurators { get; set; } //TODO list item use a configurator interface instead? + public CacheOptions() { CacheConfigurators = new List>(); diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index 9ec056d4d7..e4d2c70ba5 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -12,6 +12,10 @@ using Volo.Abp.Threading; namespace Volo.Abp.Caching { + /// + /// Represents a distributed cache of type. + /// + /// The type of cache item being cached. public class DistributedCache : IDistributedCache where TCacheItem : class { @@ -57,6 +61,12 @@ namespace Volo.Abp.Caching SetDefaultOptions(); } + /// + /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The cache item, or null. public virtual TCacheItem Get( string key, bool? hideErrors = null) @@ -88,6 +98,13 @@ namespace Volo.Abp.Caching return Serializer.Deserialize(cachedBytes); } + /// + /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The cache item, or null. public virtual async Task GetAsync( string key, bool? hideErrors = null, @@ -123,6 +140,15 @@ namespace Volo.Abp.Caching return Serializer.Deserialize(cachedBytes); } + /// + /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item + /// provided by delegate and returns the provided cache item. + /// + /// The key of cached item to be retrieved from the cache. + /// The factory delegate is used to provide the cache item when no cache item is found for the given . + /// The cache options for the factory delegate. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The cache item. public TCacheItem GetOrAdd( string key, Func factory, @@ -150,6 +176,16 @@ namespace Volo.Abp.Caching return value; } + /// + /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item + /// provided by delegate and returns the provided cache item. + /// + /// The key of cached item to be retrieved from the cache. + /// The factory delegate is used to provide the cache item when no cache item is found for the given . + /// The cache options for the factory delegate. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The cache item. public async Task GetOrAddAsync( string key, Func> factory, @@ -179,6 +215,13 @@ namespace Volo.Abp.Caching return value; } + /// + /// Sets the cache item value for the provided key. + /// + /// The key of cached item to be retrieved from the cache. + /// The cache item value to set in the cache. + /// The cache options for the value. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Set( string key, TCacheItem value, @@ -207,6 +250,15 @@ namespace Volo.Abp.Caching } } + /// + /// Sets the cache item value for the provided key. + /// + /// The key of cached item to be retrieved from the cache. + /// The cache item value to set in the cache. + /// The cache options for the value. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task SetAsync( string key, TCacheItem value, @@ -237,6 +289,11 @@ namespace Volo.Abp.Caching } } + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Refresh( string key, bool? hideErrors = null) @@ -259,6 +316,13 @@ namespace Volo.Abp.Caching } } + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RefreshAsync( string key, bool? hideErrors = null, @@ -282,6 +346,12 @@ namespace Volo.Abp.Caching } } + + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. public virtual void Remove( string key, bool? hideErrors = null) @@ -303,6 +373,13 @@ namespace Volo.Abp.Caching } } + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. public virtual async Task RemoveAsync( string key, bool? hideErrors = null, diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs index 1567bfca30..23bd6f4cf2 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/IDistributedCache.cs @@ -6,20 +6,46 @@ using Microsoft.Extensions.Caching.Distributed; namespace Volo.Abp.Caching { + /// + /// Represents a distributed cache of type. + /// + /// The type of cache item being cached. public interface IDistributedCache where TCacheItem : class { + /// + /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The cache item, or null. TCacheItem Get( string key, bool? hideErrors = null ); + /// + /// Gets a cache item with the given key. If no cache item is found for the given key then returns null. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The cache item, or null. Task GetAsync( [NotNull] string key, bool? hideErrors = null, CancellationToken token = default ); + /// + /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item + /// provided by delegate and returns the provided cache item. + /// + /// The key of cached item to be retrieved from the cache. + /// The factory delegate is used to provide the cache item when no cache item is found for the given . + /// The cache options for the factory delegate. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The cache item. TCacheItem GetOrAdd( string key, Func factory, @@ -27,6 +53,16 @@ namespace Volo.Abp.Caching bool? hideErrors = null ); + /// + /// Gets or Adds a cache item with the given key. If no cache item is found for the given key then adds a cache item + /// provided by delegate and returns the provided cache item. + /// + /// The key of cached item to be retrieved from the cache. + /// The factory delegate is used to provide the cache item when no cache item is found for the given . + /// The cache options for the factory delegate. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The cache item. Task GetOrAddAsync( [NotNull] string key, Func> factory, @@ -35,6 +71,13 @@ namespace Volo.Abp.Caching CancellationToken token = default ); + /// + /// Sets the cache item value for the provided key. + /// + /// The key of cached item to be retrieved from the cache. + /// The cache item value to set in the cache. + /// The cache options for the value. + /// Indicates to throw or hide the exceptions for the distributed cache. void Set( string key, TCacheItem value, @@ -42,6 +85,15 @@ namespace Volo.Abp.Caching bool? hideErrors = null ); + /// + /// Sets the cache item value for the provided key. + /// + /// The key of cached item to be retrieved from the cache. + /// The cache item value to set in the cache. + /// The cache options for the value. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. Task SetAsync( [NotNull] string key, [NotNull] TCacheItem value, @@ -50,22 +102,46 @@ namespace Volo.Abp.Caching CancellationToken token = default ); + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. void Refresh( string key, bool? hideErrors = null ); + /// + /// Refreshes the cache value of the given key, and resets its sliding expiration timeout. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. Task RefreshAsync( string key, bool? hideErrors = null, CancellationToken token = default ); + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. void Remove( string key, bool? hideErrors = null ); + /// + /// Removes the cache item for given key from cache. + /// + /// The key of cached item to be retrieved from the cache. + /// Indicates to throw or hide the exceptions for the distributed cache. + /// The for the task. + /// The indicating that the operation is asynchronous. Task RemoveAsync( string key, bool? hideErrors = null, 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 new file mode 100644 index 0000000000..708c296880 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj @@ -0,0 +1,36 @@ + + + + + + netstandard2.0 + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + + + + + + + + + C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Data.SqlXml.dll + + + + \ No newline at end of file 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 new file mode 100644 index 0000000000..c1894b2e6b --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -0,0 +1,32 @@ +using System.Text; +using Volo.Abp.Cli.Commands; +using Volo.Abp.Domain; +using Volo.Abp.IdentityModel; +using Volo.Abp.Json; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Cli +{ + [DependsOn( + typeof(AbpDddDomainModule), + typeof(AbpJsonModule), + typeof(AbpIdentityModelModule) + )] + public class AbpCliCoreModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + Configure(options => + { + options.Commands["help"] = typeof(HelpCommand); + options.Commands["new"] = typeof(NewCommand); + 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/CommandLineArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgs.cs new file mode 100644 index 0000000000..5e1191c385 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgs.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Text; +using JetBrains.Annotations; + +namespace Volo.Abp.Cli.Args +{ + public class CommandLineArgs + { + [CanBeNull] + public string Command { get; } + + [CanBeNull] + public string Target { get; } + + [NotNull] + public CommandLineOptions Options { get; } + + public CommandLineArgs([CanBeNull] string command = null, [CanBeNull] string target = null) + { + Command = command; + Target = target; + Options = new CommandLineOptions(); + } + + public static CommandLineArgs Empty() + { + return new CommandLineArgs(); + } + + public bool IsCommand(string command) + { + return string.Equals(Command, command, StringComparison.OrdinalIgnoreCase); + } + + public override string ToString() + { + var sb = new StringBuilder(); + + if (Command != null) + { + sb.AppendLine($"Command: {Command}"); + } + + if (Target != null) + { + sb.AppendLine($"Target: {Target}"); + } + + if (Options.Any()) + { + sb.AppendLine("Options:"); + foreach (var option in Options) + { + sb.AppendLine($" - {option.Key} = {option.Value}"); + } + } + + if (sb.Length <= 0) + { + sb.Append(""); + } + + return sb.ToString(); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..1dd29e5df7 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgumentParser.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Args +{ + public class CommandLineArgumentParser : ICommandLineArgumentParser, ITransientDependency + { + public CommandLineArgs Parse(string[] args) + { + if (args.IsNullOrEmpty()) + { + return CommandLineArgs.Empty(); + } + + var argumentList = args.ToList(); + + //Command + + var command = argumentList[0]; + argumentList.RemoveAt(0); + + if (!argumentList.Any()) + { + return new CommandLineArgs(command); + } + + //Target + + var target = argumentList[0]; + if (target.StartsWith("-")) + { + target = null; + } + else + { + argumentList.RemoveAt(0); + } + + if (!argumentList.Any()) + { + return new CommandLineArgs(command, target); + } + + //Options + + var commandLineArgs = new CommandLineArgs(command, target); + + while (argumentList.Any()) + { + var optionName = ParseOptionName(argumentList[0]); + argumentList.RemoveAt(0); + + if (!argumentList.Any()) + { + commandLineArgs.Options[optionName] = null; + break; + } + + if (IsOptionName(argumentList[0])) + { + commandLineArgs.Options[optionName] = null; + continue; + } + + commandLineArgs.Options[optionName] = argumentList[0]; + argumentList.RemoveAt(0); + } + + return commandLineArgs; + } + + private static bool IsOptionName(string argument) + { + return argument.StartsWith("-") || argument.StartsWith("--"); + } + + private static string ParseOptionName(string argument) + { + if (argument.StartsWith("--")) + { + if (argument.Length <= 2) + { + throw new ArgumentException("Should specify an option name after '--' prefix!"); + } + + return argument.RemovePreFix("--"); + } + + if (argument.StartsWith("-")) + { + if (argument.Length <= 1) + { + throw new ArgumentException("Should specify an option name after '--' prefix!"); + } + + return argument.RemovePreFix("-"); + } + + throw new ArgumentException("Option names should start with '-' or '--'."); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineOptions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineOptions.cs new file mode 100644 index 0000000000..3e72d6e434 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineOptions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Cli.Args +{ + public class CommandLineOptions : Dictionary + { + [CanBeNull] + public string GetOrNull([NotNull] string name, params string[] alternativeNames) + { + Check.NotNullOrWhiteSpace(name, nameof(name)); + + var value = this.GetOrDefault(name); + if (!value.IsNullOrWhiteSpace()) + { + return value; + } + + if (!alternativeNames.IsNullOrEmpty()) + { + foreach (var alternativeName in alternativeNames) + { + value = this.GetOrDefault(alternativeName); + if (!value.IsNullOrWhiteSpace()) + { + return value; + } + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/ICommandLineArgumentParser.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/ICommandLineArgumentParser.cs new file mode 100644 index 0000000000..c467541210 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/ICommandLineArgumentParser.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Cli.Args +{ + public interface ICommandLineArgumentParser + { + CommandLineArgs Parse(string[] args); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..fa94eb00bb --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Auth/AuthService.cs @@ -0,0 +1,43 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; +using IdentityModel; +using Volo.Abp.DependencyInjection; +using Volo.Abp.IdentityModel; +using Volo.Abp.IO; + +namespace Volo.Abp.Cli.Auth +{ + public class AuthService : ITransientDependency + { + protected IIdentityModelAuthenticationService AuthenticationService { get; } + + public AuthService(IIdentityModelAuthenticationService authenticationService) + { + AuthenticationService = authenticationService; + } + + public async Task LoginAsync(string userName, string password) + { + var accessToken = await AuthenticationService.GetAccessTokenAsync( + new IdentityClientConfiguration( + CliUrls.AccountAbpIo, + "role email abpio_www abpio_commercial", + "abp-cli", + "1q2w3e*", + OidcConstants.GrantTypes.Password, + userName, + password + ) + ); + + File.WriteAllText(CliPaths.AccessToken, accessToken, Encoding.UTF8); + } + + public Task LogoutAsync() + { + FileHelper.DeleteIfExists(CliPaths.AccessToken); + return Task.CompletedTask; + } + } +} 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 new file mode 100644 index 0000000000..3885314e70 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliOptions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Cli +{ + public class CliOptions + { + public Dictionary Commands { get; } + + public CliOptions() + { + Commands = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..eb6339b9da --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs @@ -0,0 +1,15 @@ +using System; +using System.IO; + +namespace Volo.Abp.Cli +{ + public static class CliPaths + { + public static string TemplateCache => Path.Combine(AbpRootPath, "templates"); //TODO: Move somewhere else? + public static string Log => Path.Combine(AbpRootPath, "cli", "logs"); + public static string Root => Path.Combine(AbpRootPath, "cli"); + public static string AccessToken => Path.Combine(AbpRootPath, "cli", "access-token.bin"); + + 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 new file mode 100644 index 0000000000..3c2a88094f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -0,0 +1,170 @@ +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; + +namespace Volo.Abp.Cli +{ + public class CliService : ITransientDependency + { + public ILogger Logger { get; set; } + + protected ICommandLineArgumentParser CommandLineArgumentParser { get; } + protected ICommandSelector CommandSelector { get; } + protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected NuGetService NuGetService { get; } + + public CliService( + ICommandLineArgumentParser commandLineArgumentParser, + ICommandSelector commandSelector, + 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, version {GetCliVersion()}."); + Logger.LogInformation("https://abp.io"); + + await CheckForNewVersion(); + + CheckDependencies(); + + var commandLineArgs = CommandLineArgumentParser.Parse(args); + var commandType = CommandSelector.Select(commandLineArgs); + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var command = (IConsoleCommand)scope.ServiceProvider.GetRequiredService(commandType); + + 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/CliUrls.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs new file mode 100644 index 0000000000..3c034d5702 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliUrls.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.Cli +{ + public static class CliUrls + { +#if DEBUG + public const string WwwAbpIo = "https://localhost:44328/"; + public const string AccountAbpIo = "https://localhost:44333/"; +#else + public const string WwwAbpIo = "https://abp.io/"; + public const string AccountAbpIo = "https://account.abp.io/"; +#endif + } +} 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/AddCommandHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommandHelper.cs new file mode 100644 index 0000000000..d1b5c9d716 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddCommandHelper.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.Cli.Commands +{ + public static class AddCommandHelper + { + + } +} \ 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 new file mode 100644 index 0000000000..595917e845 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using Volo.Abp.Cli.Args; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class CommandSelector : ICommandSelector, ITransientDependency + { + protected CliOptions Options { get; } + + public CommandSelector(IOptions options) + { + Options = options.Value; + } + + public Type Select(CommandLineArgs commandLineArgs) + { + if (commandLineArgs.Command.IsNullOrWhiteSpace()) + { + return typeof(HelpCommand); + } + + return Options.Commands.GetOrDefault(commandLineArgs.Command) + ?? typeof(HelpCommand); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..671bf3cbab --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Args; +using Volo.Abp.DependencyInjection; + +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) + { + Logger.LogInformation(""); + Logger.LogInformation("Usage:"); + Logger.LogInformation(""); + Logger.LogInformation(" abp [options]"); + Logger.LogInformation(""); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ICommandSelector.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ICommandSelector.cs new file mode 100644 index 0000000000..507f8ab452 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ICommandSelector.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Cli.Args; + +namespace Volo.Abp.Cli.Commands +{ + public interface ICommandSelector + { + Type Select(CommandLineArgs commandLineArgs); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/IConsoleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/IConsoleCommand.cs new file mode 100644 index 0000000000..2b1129aab2 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/IConsoleCommand.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; + +namespace Volo.Abp.Cli.Commands +{ + public interface IConsoleCommand + { + Task ExecuteAsync(CommandLineArgs commandLineArgs); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..28bc64afd5 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LoginCommand.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Threading.Tasks; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Auth; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class LoginCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + protected AuthService AuthService { get; } + + public LoginCommand(AuthService authService) + { + AuthService = authService; + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + if (commandLineArgs.Target.IsNullOrEmpty()) + { + Logger.LogInformation(""); + Logger.LogWarning("Username is missing."); + LogHelp(); + return; + } + + Console.Write("Password: "); + var password = ConsoleHelper.ReadSecret(); + if (password.IsNullOrWhiteSpace()) + { + Logger.LogInformation(""); + Logger.LogWarning("Password is missing."); + LogHelp(); + return; + } + + await AuthService.LoginAsync(commandLineArgs.Target, password); + + Logger.LogInformation($"Successfully logged in as '{commandLineArgs.Target}'"); + } + + private void LogHelp() + { + Logger.LogWarning(""); + Logger.LogWarning("Usage:"); + Logger.LogWarning(" abp login "); + Logger.LogWarning(""); + Logger.LogWarning("Example:"); + Logger.LogWarning(" abp login john"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs new file mode 100644 index 0000000000..342e52f529 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/LogoutCommand.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Auth; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class LogoutCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + protected AuthService AuthService { get; } + + public LogoutCommand(AuthService authService) + { + AuthService = authService; + Logger = NullLogger.Instance; + } + + public Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + return AuthService.LogoutAsync(); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..19865bdacf --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -0,0 +1,123 @@ +using System.IO; +using System.Threading.Tasks; +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; + +namespace Volo.Abp.Cli.Commands +{ + public class NewCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + protected ProjectBuilder ProjectBuilder { get; } + + public NewCommand(ProjectBuilder projectBuilder) + { + ProjectBuilder = projectBuilder; + + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + if (commandLineArgs.Target == null) + { + Logger.LogInformation(""); + Logger.LogWarning("Project name is missing."); + Logger.LogWarning(""); + Logger.LogWarning("Usage:"); + Logger.LogWarning(" abp new [-t|--template] [-d|--database-provider] [-o|--output-folder]"); + Logger.LogWarning(""); + Logger.LogWarning("Options:"); + Logger.LogWarning("-t|--template "); + Logger.LogWarning("-d|--database-provider "); + Logger.LogWarning("-o|--output-folder "); + Logger.LogWarning(""); + Logger.LogWarning("Examples:"); + Logger.LogWarning(" abp new Acme.BookStore"); + Logger.LogWarning(" abp new Acme.BookStore -t mvc-module"); + Logger.LogWarning(" abp new Acme.BookStore -t mvc -d mongodb"); + Logger.LogWarning(" abp new Acme.BookStore -t mvc -d mongodb -o d:\\project"); + return; + } + + Logger.LogInformation("Creating a new project..."); + Logger.LogInformation("Project name: " + commandLineArgs.Target); + + var result = await ProjectBuilder.BuildAsync( + new ProjectBuildArgs( + SolutionName.Parse(commandLineArgs.Target), + commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long), + GetDatabaseProviderOrNull(commandLineArgs), + commandLineArgs.Options + ) + ); + + var outputFolder = commandLineArgs.Options.GetOrNull(Options.OutputFolder.Short, Options.OutputFolder.Long); + if (outputFolder != null) + { + if (!Directory.Exists(outputFolder)) + { + Directory.CreateDirectory(outputFolder); + } + + outputFolder = Path.GetFullPath(outputFolder); + } + else + { + outputFolder = Directory.GetCurrentDirectory(); + } + + using (var templateFileStream = new MemoryStream(result.ZipContent)) + { + using (var templateZipFile = ZipFile.Read(templateFileStream)) + { + templateZipFile.ExtractAll(outputFolder, ExtractExistingFileAction.Throw); + } + } + + Logger.LogInformation($"Successfully created the project '{commandLineArgs.Target}'"); + Logger.LogInformation($"The output folder is: '{outputFolder}'"); + } + + protected virtual DatabaseProvider GetDatabaseProviderOrNull(CommandLineArgs commandLineArgs) + { + var optionValue = commandLineArgs.Options.GetOrNull(Options.DatabaseProvider.Short, Options.DatabaseProvider.Long); + switch (optionValue) + { + case "ef": + return DatabaseProvider.EntityFrameworkCore; + case "mongodb": + return DatabaseProvider.MongoDb; + default: + return DatabaseProvider.NotSpecified; + } + } + + public static class Options + { + public static class Template + { + public const string Short = "t"; + public const string Long = "template"; + } + + public static class DatabaseProvider + { + public const string Short = "d"; + public const string Long = "database-provider"; + } + + public static class OutputFolder + { + public const string Short = "o"; + public const string Long = "output-folder"; + } + } + } +} \ No newline at end of file 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..faf20ad974 --- /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( + $"{CliUrls.WwwAbpIo}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( + $"{CliUrls.WwwAbpIo}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/Cli/ProjectBuilding/Building/DatabaseProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseProvider.cs new file mode 100644 index 0000000000..852c63057a --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/DatabaseProvider.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public enum DatabaseProvider + { + NotSpecified = 0, + EntityFrameworkCore = 1, + MongoDb = 2 + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/GithubRepositoryInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/GithubRepositoryInfo.cs new file mode 100644 index 0000000000..22c8bbe569 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/GithubRepositoryInfo.cs @@ -0,0 +1,25 @@ +using System; + +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public class GithubRepositoryInfo + { + public string RepositoryNameWithOrganization { get; } + + public string RepositoryName { get; } + + public string AccessToken { get; } + + public GithubRepositoryInfo(string repositoryNameWithOrganization, string accessToken) + { + if (!repositoryNameWithOrganization.Contains("/")) + { + throw new ApplicationException($"{nameof(repositoryNameWithOrganization)} '{repositoryNameWithOrganization}' is not valid! It should be formatted as 'organization-name/repository-name'." ); + } + + RepositoryNameWithOrganization = repositoryNameWithOrganization; + RepositoryName = repositoryNameWithOrganization.Split('/')[1]; + AccessToken = accessToken; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs new file mode 100644 index 0000000000..1ea040022f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public class ProjectBuildContext + { + [NotNull] + public TemplateFile TemplateFile { get; } + + [NotNull] + public ProjectBuildArgs BuildArgs { get; } + + [NotNull] + public TemplateInfo Template { get; } + + public FileEntryList Files { get; set; } + + public ProjectResult Result { get; set; } + + public ProjectBuildContext( + [NotNull] TemplateInfo template, + [NotNull] TemplateFile templateFile, + [NotNull] ProjectBuildArgs buildArgs) + { + Template = Check.NotNull(template, nameof(template)); + TemplateFile = Check.NotNull(templateFile, nameof(templateFile)); + BuildArgs = Check.NotNull(buildArgs, nameof(buildArgs)); + + Result = new ProjectResult(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContextExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContextExtensions.cs new file mode 100644 index 0000000000..e793c77011 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContextExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public static class ProjectBuildContextExtensions + { + public static FileEntry GetFile(this ProjectBuildContext context, string filePath) + { + var file = context.Files.FirstOrDefault(f => f.Name == filePath); + if (file == null) + { + throw new ApplicationException("Could not find file: " + filePath); + } + + return file; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipeline.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipeline.cs new file mode 100644 index 0000000000..a6e5e2e56f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipeline.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public class ProjectBuildPipeline + { + public List Steps { get; } + + public ProjectBuildPipeline() + { + Steps = new List(); + } + + public void Execute(ProjectBuildContext context) + { + foreach (var step in Steps) + { + step.Execute(context); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs new file mode 100644 index 0000000000..c4d15d5548 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs @@ -0,0 +1,21 @@ +using Volo.Abp.Cli.ProjectBuilding.Building.Steps; + +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public static class ProjectBuildPipelineBuilder + { + public static ProjectBuildPipeline Build(ProjectBuildContext context) + { + var pipeline = new ProjectBuildPipeline(); + + pipeline.Steps.Add(new FileEntryListReadStep()); + pipeline.Steps.AddRange(context.Template.GetCustomSteps(context)); + pipeline.Steps.Add(new NugetReferenceReplaceStep()); + pipeline.Steps.Add(new TemplateCodeDeleteStep()); + pipeline.Steps.Add(new SolutionRenameStep()); + pipeline.Steps.Add(new CreateProjectResultZipStep()); + + return pipeline; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineStep.cs new file mode 100644 index 0000000000..d148cea84a --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildPipelineStep.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public abstract class ProjectBuildPipelineStep + { + public abstract void Execute(ProjectBuildContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectResult.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectResult.cs new file mode 100644 index 0000000000..206a713413 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectResult.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public class ProjectResult + { + public byte[] ZipContent { get; set; } + } +} \ No newline at end of file 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/Cli/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs new file mode 100644 index 0000000000..53e9415617 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs @@ -0,0 +1,23 @@ +using Ionic.Zip; +using Volo.Abp.Cli.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Zipping; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class CreateProjectResultZipStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + context.Result.ZipContent = CreateZipFileFromEntries(context.Files); + } + + private static byte[] CreateZipFileFromEntries(FileEntryList entries) + { + using (var resultZipFile = new ZipFile()) + { + entries.CopyToZipFile(resultZipFile); + return resultZipFile.GetBytes(); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs new file mode 100644 index 0000000000..3484621df1 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs @@ -0,0 +1,26 @@ +using System.IO; +using Ionic.Zip; +using Volo.Abp.Cli.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectBuilding.Zipping; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class FileEntryListReadStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + context.Files = GetEntriesFromZipFile(context.TemplateFile.FileBytes); + } + + private static FileEntryList GetEntriesFromZipFile(byte[] fileBytes) + { + using (var memoryStream = new MemoryStream(fileBytes)) + { + using (var templateZipFile = ZipFile.Read(memoryStream)) + { + return templateZipFile.ToFileEntryList(); + } + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs new file mode 100644 index 0000000000..49ccb308c8 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using HtmlAgilityPack; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class NugetReferenceReplaceStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + new NugetReferenceReplacer( + context.Files, + "MyCompanyName", + "MyProjectName", + context.TemplateFile.Version + ).Run(); + } + + private class NugetReferenceReplacer + { + private readonly List _entries; + private readonly string _companyNamePlaceHolder; + private readonly string _projectNamePlaceHolder; + private readonly string _latestNugetPackageVersion; + + public NugetReferenceReplacer(List entries, string companyNamePlaceHolder, string projectNamePlaceHolder, string latestNugetPackageVersion) + { + _entries = entries; + _companyNamePlaceHolder = companyNamePlaceHolder; + _projectNamePlaceHolder = projectNamePlaceHolder; + _latestNugetPackageVersion = latestNugetPackageVersion; + } + + public void Run() + { + foreach (var fileEntry in _entries) + { + if (fileEntry.Name.EndsWith(".csproj")) + { + fileEntry.SetContent(ProcessFileContent(fileEntry.Content)); + } + } + } + + private string ProcessFileContent(string content) + { + Check.NotNull(content, nameof(content)); + + var doc = new HtmlDocument(); + + doc.Load(GenerateStreamFromString(content)); + + var nodes = doc.DocumentNode.SelectNodes("//projectreference[@include]"); + + if (nodes == null) + { + return content; + } + + return ProcessReferenceNodes(nodes, content); + } + + private string ProcessReferenceNodes(HtmlNodeCollection nodes, string content) + { + Check.NotNull(nodes, nameof(nodes)); + Check.NotNull(content, nameof(content)); + + foreach (var node in nodes) + { + var valueAttr = node.Attributes.FirstOrDefault(a => a.Name.ToLower() == "include"); + + // ReSharper disable once PossibleNullReferenceException : Can not be null because nodes are selected with include attribute filter in previous method + if (valueAttr.Value.Contains($"{_companyNamePlaceHolder}.{_projectNamePlaceHolder}")) + { + continue; + } + + var newValue = ConvertToNugetReference(valueAttr.Value); + + var oldLine = $" file.Name.StartsWith(projectFolderWithSlash)); + context.Files.RemoveAll(file => file.Name == _projectFolderPath); + var solutionFile = context.GetFile(_solutionFilePath); + solutionFile.NormalizeLineEndings(); + solutionFile.SetLines(RemoveProject(solutionFile.GetLines().ToList())); + } + + private List RemoveProject(List solutionFileLines) + { + var projectKey = FindProjectKey(solutionFileLines); + var newSolutionFileLines = new List(); + var firstOccurence = true; + + for (var i = 0; i < solutionFileLines.Count; ++i) + { + if (solutionFileLines[i].Contains(projectKey)) + { + if (firstOccurence) + { + firstOccurence = false; + ++i; //Skip "EndProject" line too. + } + + continue; + } + + newSolutionFileLines.Add(solutionFileLines[i]); + } + + return newSolutionFileLines; + } + + private string FindProjectKey(List solutionFileLines) + { + foreach (var solutionFileLine in solutionFileLines) + { + if (solutionFileLine.Contains(_projectName)) + { + var curlyBracketStartIndex = solutionFileLine.LastIndexOf("{", StringComparison.OrdinalIgnoreCase); + var curlyBracketEndIndex = solutionFileLine.LastIndexOf("}", StringComparison.OrdinalIgnoreCase); + return solutionFileLine.Substring(curlyBracketStartIndex + 1, curlyBracketEndIndex - curlyBracketStartIndex - 1); + } + } + + throw new ApplicationException($"The solution file '{_solutionFilePath}' does not contain a project '{_projectName}'"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RenameHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RenameHelper.cs new file mode 100644 index 0000000000..6f6fc6d983 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/RenameHelper.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public static class RenameHelper + { + public static void RenameAll(List entries, string placeHolder, string name) + { + RenameDirectoryRecursively(entries, placeHolder, name); + RenameAllFiles(entries, placeHolder, name); + ReplaceContent(entries, placeHolder, name); + } + + private static void RenameDirectoryRecursively(List entries, string placeHolder, string name) + { + foreach (var entry in entries.Where(e => e.IsDirectory)) + { + if (entry.Name.Contains(placeHolder)) + { + entry.SetName(entry.Name.Replace(placeHolder, name)); + } + } + } + + private static void RenameAllFiles(List entries, string placeHolder, string name) + { + foreach (var entry in entries.Where(e => !e.IsDirectory)) + { + if (entry.Name.Contains(placeHolder)) + { + entry.SetName(entry.Name.Replace(placeHolder, name)); + } + } + } + + private static void ReplaceContent(List entries, string placeHolder, string name) + { + foreach (var entry in entries.Where(e => !e.IsDirectory)) + { + if (entry.Content.Length < placeHolder.Length) + { + continue; + } + + if (entry.IsBinaryFile) + { + continue; + } + + var newContent = entry.Content.Replace(placeHolder, name); + if (newContent != entry.Content) + { + entry.SetContent(newContent); + } + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs new file mode 100644 index 0000000000..076ee084d1 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/SolutionRenameStep.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class SolutionRenameStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + new SolutionRenamer( + context.Files, + "MyCompanyName", + "MyProjectName", + context.BuildArgs.SolutionName.CompanyName, + context.BuildArgs.SolutionName.ProjectName + ).Run(); + } + + private class SolutionRenamer + { + private readonly List _entries; + private readonly string _companyNamePlaceHolder; + private readonly string _projectNamePlaceHolder; + + private readonly string _companyName; + private readonly string _projectName; + + public SolutionRenamer(List entries, string companyNamePlaceHolder, string projectNamePlaceHolder, string companyName, string projectName) + { + if (string.IsNullOrWhiteSpace(companyName)) + { + companyName = null; + } + + if (companyNamePlaceHolder == null && companyName != null) + { + throw new UserFriendlyException($"Can not set {nameof(companyName)} if {nameof(companyNamePlaceHolder)} is null."); + } + + _entries = entries; + + _companyNamePlaceHolder = companyNamePlaceHolder; + _projectNamePlaceHolder = projectNamePlaceHolder ?? throw new ArgumentNullException(nameof(projectNamePlaceHolder)); + + _companyName = companyName; + _projectName = projectName ?? throw new ArgumentNullException(nameof(projectName)); + } + + public void Run() + { + if (_companyNamePlaceHolder != null && _companyName != null) + { + RenameHelper.RenameAll(_entries, _companyNamePlaceHolder, _companyName); + } + else if (_companyNamePlaceHolder != null) + { + RenameHelper.RenameAll(_entries, _companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder); + } + + RenameHelper.RenameAll(_entries, _projectNamePlaceHolder, _projectName); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs new file mode 100644 index 0000000000..e673446d9f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs @@ -0,0 +1,19 @@ +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class TemplateCodeDeleteStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + foreach (var file in context.Files) + { + if (file.Name.EndsWith(".cs")) //TODO: Why only cs! + { + file.RemoveTemplateCode(); + file.RemoveTemplateCodeMarkers(); + } + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs new file mode 100644 index 0000000000..4041dc8d7a --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateInfo.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public abstract class TemplateInfo + { + public string Name { get; } + + public DatabaseProvider DefaultDatabaseProvider { get; } + + protected TemplateInfo( + [NotNull] string name, + DatabaseProvider defaultDatabaseProvider = DatabaseProvider.NotSpecified) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + DefaultDatabaseProvider = Check.NotNull(defaultDatabaseProvider, nameof(defaultDatabaseProvider)); + } + + public virtual IEnumerable GetCustomSteps(ProjectBuildContext context) + { + return Array.Empty(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/DownloadInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/DownloadInfo.cs new file mode 100644 index 0000000000..e8f51f5958 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/DownloadInfo.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + [Table("Downloads")] + public class DownloadInfo : CreationAuditedAggregateRoot + { + [Required] + [StringLength(128)] + public string ProjectName { get; set; } + + [Required] + [StringLength(42)] + public string TemplateName { get; set; } + + [Required] + public DatabaseProvider DatabaseProvider { get; set; } + + [Required] + [StringLength(20)] + public string Version { get; set; } + + public int CreationDuration { get; set; } + + public DownloadInfo(string projectName, string templateName, DatabaseProvider databaseProvider, string version, int creationDuration) + { + ProjectName = projectName; + TemplateName = templateName; + DatabaseProvider = databaseProvider; + Version = version; + CreationDuration = creationDuration; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntry.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntry.cs new file mode 100644 index 0000000000..ffa248c9f2 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntry.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Volo.Abp.Cli.ProjectBuilding.Files +{ + public class FileEntry + { + private static string[] BinaryFileExtensions = { + ".exe", ".dll", ".bin", + ".suo", ".obj", ".pdb", + ".png", "jpg", "jpeg", ".ico" + ,".woff", ".woff2", ".eot", ".svg", ".ttf" + }; + + public string Name { get; private set; } + + public bool IsDirectory { get; } + + public Encoding Encoding { get; } + + public byte[] Bytes { get; private set; } + + public string Content { get; private set; } + + public bool IsBinaryFile { get; private set; } + + public FileEntry(string name, byte[] bytes, bool isDirectory) + { + Name = name; + Bytes = bytes; + IsDirectory = isDirectory; + + Encoding = CalculateEncoding(); + Content = Encoding.GetString(bytes); + IsBinaryFile = CalculateIsBinaryFile(); + } + + public void SetContent(string fileContent) + { + Content = fileContent; + Bytes = Encoding.GetBytes(fileContent); + } + + public string[] GetLines() + { + return Content.Split(new[] { Environment.NewLine, "\n" }, StringSplitOptions.None); + } + + public void SetLines(IEnumerable lines) + { + SetContent(lines.JoinAsString(Environment.NewLine)); + } + + public void SetName(string fileName) + { + Name = fileName; + IsBinaryFile = CalculateIsBinaryFile(); + } + + public void NormalizeLineEndings() + { + if (Content.IsNullOrEmpty()) + { + return; + } + + SetContent(Content.NormalizeLineEndings()); + } + + public override string ToString() + { + var str = new StringBuilder(Name); + + if (IsDirectory) + { + str.Append(" [DIR]"); + } + + str.Append($" [{Bytes.Length}]"); + + return str.ToString(); + } + + private Encoding CalculateEncoding() + { + if (Bytes.IsNullOrEmpty()) + { + return Encoding.ASCII; + } + + if (Bytes[0] == 0x2b && Bytes[1] == 0x2f && Bytes[2] == 0x76) + { + return Encoding.UTF7; + } + + if (Bytes[0] == 0xef && Bytes[1] == 0xbb && Bytes[2] == 0xbf) + { + return Encoding.UTF8; + } + + if (Bytes[0] == 0xff && Bytes[1] == 0xfe) + { + return Encoding.Unicode; //UTF-16LE + } + + if (Bytes[0] == 0xfe && Bytes[1] == 0xff) + { + return Encoding.BigEndianUnicode; //UTF-16BE + } + + if (Bytes[0] == 0 && Bytes[1] == 0 && Bytes[2] == 0xfe && Bytes[3] == 0xff) + { + return Encoding.UTF32; + } + + return Encoding.UTF8; + } + + private bool CalculateIsBinaryFile() + { + return BinaryFileExtensions.Any(ext => Name.EndsWith(ext)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs new file mode 100644 index 0000000000..900948ce9c --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.ProjectBuilding.Files +{ + public static class FileEntryExtensions + { + public static FileEntry ReplaceText(this FileEntry file, string oldText, string newText) + { + file.NormalizeLineEndings(); + file.SetContent(file.Content.Replace(oldText, newText)); + return file; + } + + public static void RemoveTemplateCode(this FileEntry file) + { + RemoveMarkedTemplateCode(file, ""); + } + + public static void RemoveTemplateCodeIf(this FileEntry file, string condition) + { + RemoveByCondition(file, "IF", condition); + } + + public static void RemoveTemplateCodeIfNot(this FileEntry file, string condition) + { + RemoveByCondition(file, "IF-NOT", condition); + } + + public static void RemoveTemplateCodeMarkers(this FileEntry file) + { + file.NormalizeLineEndings(); + + var lines = file.GetLines(); + var newLines = new List(); + + for (int i = 0; i < lines.Length; i++) + { + if (lines[i].Contains("")) + { + //TODO: What if we use inline like: some-code + //TODO: This logic skips the code in that case. Should handle it + ++i; + } + + if (i < lines.Length) + { + newLines.Add(lines[i]); + } + } + + file.SetLines(newLines); + } + + private static void RemoveByCondition(this FileEntry file, string conditionName, string condition) + { + RemoveMarkedTemplateCode(file, $""); + } + + private static void RemoveMarkedTemplateCode(this FileEntry file, string beginMark) + { + file.NormalizeLineEndings(); + + var lines = file.GetLines(); + var newLines = new List(); + + for (int i = 0; i < lines.Length; i++) + { + if (lines[i].Contains(beginMark)) + { + while (!lines[i].Contains("") && i < lines.Length) + { + ++i; + } + + ++i; + } + + if (i < lines.Length) + { + newLines.Add(lines[i]); + } + } + + file.SetLines(newLines); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryList.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryList.cs new file mode 100644 index 0000000000..69c7b89d6f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryList.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Cli.ProjectBuilding.Files +{ + public class FileEntryList : List + { + public FileEntryList(IEnumerable entries) + : base(entries) + { + + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryListExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryListExtensions.cs new file mode 100644 index 0000000000..d8eff66813 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryListExtensions.cs @@ -0,0 +1,24 @@ +using System; +using Ionic.Zip; + +namespace Volo.Abp.Cli.ProjectBuilding.Files +{ + public static class FileEntryListExtensions + { + public static void CopyToZipFile(this FileEntryList fileEntryList, ZipFile zipFile) + { + foreach (var entry in fileEntryList) + { + try + { + zipFile.AddEntry(entry.Name, entry.Bytes); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Github/GithubRelease.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Github/GithubRelease.cs new file mode 100644 index 0000000000..9d37befbc2 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Github/GithubRelease.cs @@ -0,0 +1,21 @@ +using System; +using Newtonsoft.Json; + +namespace Volo.Abp.Cli.ProjectBuilding.Github +{ + [JsonObject] + public class GithubRelease + { + [JsonProperty("id")] + public int Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("prerelease")] + public bool IsPrerelease { get; set; } + + [JsonProperty("published_at")] + public DateTime PublishTime { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/IProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/IProjectBuilder.cs new file mode 100644 index 0000000000..5f8caa3252 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/IProjectBuilder.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public interface IProjectBuilder + { + Task BuildAsync(ProjectBuildArgs args); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateInfoProvider.cs new file mode 100644 index 0000000000..5330bc9962 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ITemplateInfoProvider.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Cli.ProjectBuilding.Building; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public interface ITemplateInfoProvider + { + TemplateInfo GetDefault(); + + TemplateInfo Get(string name); + } +} \ No newline at end of file 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/Cli/ProjectBuilding/ProjectBuildArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs new file mode 100644 index 0000000000..ca106c1f6e --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.Cli.ProjectBuilding.Building; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class ProjectBuildArgs + { + [NotNull] + public SolutionName SolutionName { get; } + + [CanBeNull] + public string TemplateName { get; set; } + + public DatabaseProvider DatabaseProvider { get; set; } + + [NotNull] + public Dictionary ExtraProperties { get; set; } + + public ProjectBuildArgs( + [NotNull] SolutionName solutionName, + [CanBeNull] string templateName = null, + DatabaseProvider databaseProvider = DatabaseProvider.NotSpecified, + Dictionary extraProperties = null) + { + DatabaseProvider = databaseProvider; + TemplateName = templateName; + SolutionName = Check.NotNull(solutionName, nameof(solutionName)); + + ExtraProperties = extraProperties ?? new Dictionary(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildResult.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildResult.cs new file mode 100644 index 0000000000..eb5e524f40 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildResult.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class ProjectBuildResult + { + public byte[] ZipContent { get; } + + public string ProjectName { get; } + + public ProjectBuildResult(byte[] zipContent, string projectName) + { + ZipContent = zipContent; + ProjectName = projectName; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuilder.cs new file mode 100644 index 0000000000..0b66ea375d --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuilder.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class ProjectBuilder : IProjectBuilder, ITransientDependency + { + protected ITemplateStore TemplateStore { get; } + protected ITemplateInfoProvider TemplateInfoProvider { get; } + + public ProjectBuilder(ITemplateStore templateStore, ITemplateInfoProvider templateInfoProvider) + { + TemplateStore = templateStore; + TemplateInfoProvider = templateInfoProvider; + } + + public async Task BuildAsync(ProjectBuildArgs args) + { + var templateInfo = GetTemplateInfo(args); + + args.TemplateName = templateInfo.Name; + + if (args.DatabaseProvider == DatabaseProvider.NotSpecified) + { + if (templateInfo.DefaultDatabaseProvider != DatabaseProvider.NotSpecified) + { + args.DatabaseProvider = templateInfo.DefaultDatabaseProvider; + } + } + + var templateFile = await TemplateStore.GetAsync( + args.TemplateName, + args.DatabaseProvider, + args.SolutionName.FullName + ); + + var context = new ProjectBuildContext( + templateInfo, + templateFile, + args + ); + + ProjectBuildPipelineBuilder.Build(context).Execute(context); + + return new ProjectBuildResult(context.Result.ZipContent, args.SolutionName.ProjectName); + } + + private TemplateInfo GetTemplateInfo(ProjectBuildArgs args) + { + if (args.TemplateName.IsNullOrWhiteSpace()) + { + return TemplateInfoProvider.GetDefault(); + } + else + { + return TemplateInfoProvider.Get(args.TemplateName); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs new file mode 100644 index 0000000000..ba93774fbe --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SolutionName.cs @@ -0,0 +1,50 @@ +using System; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class SolutionName + { + public string FullName { get; } + + public string CompanyName { get; } + + public string ProjectName { get; } + + private SolutionName(string fullName, string companyName, string projectName) + { + FullName = fullName; + CompanyName = companyName; + ProjectName = projectName; + } + + public static SolutionName Parse(string fullName) + { + if (fullName.Length < 1) + { + throw new UserFriendlyException("Solution name is not valid. It should be 1 character length at minimum."); + } + + string companyName = null; + var projectName = fullName; + + if (fullName.Contains(".")) + { + var lastDotIndex = fullName.LastIndexOf(".", StringComparison.OrdinalIgnoreCase); + companyName = fullName.Substring(0, lastDotIndex); + projectName = fullName.Substring(lastDotIndex + 1); + + if (companyName.Length < 1) + { + throw new UserFriendlyException("Solution name is not valid. Company name should be 1 character length at minimum."); + } + + if (projectName.Length < 1) + { + throw new UserFriendlyException("Solution name is not valid. Project name should be 1 character length at minimum."); + } + } + + return new SolutionName(fullName, companyName, projectName); + } + } +} \ No newline at end of file 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/Cli/ProjectBuilding/TemplateInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs new file mode 100644 index 0000000000..89a5b654e8 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateInfoProvider.cs @@ -0,0 +1,29 @@ +using System; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Templates.Mvc; +using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class TemplateInfoProvider : ITemplateInfoProvider, ITransientDependency + { + public TemplateInfo GetDefault() + { + return Get(MvcTemplate.TemplateName); + } + + public TemplateInfo Get(string name) + { + switch (name) + { + case MvcTemplate.TemplateName: + return new MvcTemplate(); + case MvcModuleTemplate.TemplateName: + return new MvcModuleTemplate(); + default: + throw new Exception("There is no template found with given name: " + name); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplate.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplate.cs new file mode 100644 index 0000000000..069c5fbf03 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplate.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Building.Steps; + +namespace Volo.Abp.Cli.ProjectBuilding.Templates.Mvc +{ + public class MvcTemplate : TemplateInfo + { + /// + /// "mvc". + /// + public const string TemplateName = "mvc"; + + public MvcTemplate() + : base(TemplateName, DatabaseProvider.EntityFrameworkCore) + { + + } + + public override IEnumerable GetCustomSteps(ProjectBuildContext context) + { + var steps = new List(); + + SwitchDatabaseProvider(context, steps); + DeleteUnrelatedProjects(context, steps); + + return steps; + } + + private static void SwitchDatabaseProvider(ProjectBuildContext context, List steps) + { + if (context.BuildArgs.DatabaseProvider == DatabaseProvider.MongoDb) + { + steps.Add(new MvcTemplateSwitchEntityFrameworkCoreToMongoDbStep()); + } + + if (context.BuildArgs.DatabaseProvider != DatabaseProvider.EntityFrameworkCore) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.Tests", projectFolderPath: "/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests")); + } + + if (context.BuildArgs.DatabaseProvider != DatabaseProvider.MongoDb) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.MongoDB")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.MongoDB.Tests", projectFolderPath: "/test/MyCompanyName.MyProjectName.MongoDB.Tests")); + } + } + + private void DeleteUnrelatedProjects(ProjectBuildContext context, List steps) + { + if (context.BuildArgs.ExtraProperties.ContainsKey("tiered")) + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "/test/MyCompanyName.MyProjectName.Web.Tests")); + steps.Add(new MvcTemplateProjectRenameStep("MyCompanyName.MyProjectName.Web.Host", "MyCompanyName.MyProjectName.Web")); + } + else + { + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host")); + steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer")); + steps.Add(new MyTemplateChangeConsoleTestClientPortSettingsStep()); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplateProjectRenameStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplateProjectRenameStep.cs new file mode 100644 index 0000000000..33a25a80a1 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplateProjectRenameStep.cs @@ -0,0 +1,29 @@ +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Building.Steps; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Templates.Mvc +{ + public class MvcTemplateProjectRenameStep : ProjectBuildPipelineStep + { + private readonly string _oldProjectName; + private readonly string _newProjectName; + + public MvcTemplateProjectRenameStep( + string oldProjectName, + string newProjectName) + { + _oldProjectName = oldProjectName; + _newProjectName = newProjectName; + } + + public override void Execute(ProjectBuildContext context) + { + context + .GetFile("/MyCompanyName.MyProjectName.sln") + .ReplaceText(_oldProjectName, _newProjectName); + + RenameHelper.RenameAll(context.Files, _oldProjectName, _newProjectName); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs new file mode 100644 index 0000000000..cffe19d05f --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MvcTemplateSwitchEntityFrameworkCoreToMongoDbStep.cs @@ -0,0 +1,196 @@ +using System; +using Volo.Abp.Cli.ProjectBuilding.Building; + +namespace Volo.Abp.Cli.ProjectBuilding.Templates.Mvc +{ + public class MvcTemplateSwitchEntityFrameworkCoreToMongoDbStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + //MyCompanyName.MyProjectName.Web + + ChangeProjectReference( + context, + "/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj", + "EntityFrameworkCore.DbMigrations", + "MongoDB" + ); + + ChangeModuleDependency( + context, + "/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs", + "MyCompanyName.MyProjectName.EntityFrameworkCore", + "MyCompanyName.MyProjectName.MongoDb", + "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + "MyProjectNameMongoDbModule" + ); + + ChangeConnectionStringToMongoDb( + context, + "/src/MyCompanyName.MyProjectName.Web/appsettings.json" + ); + + //MyCompanyName.MyProjectName.IdentityServer + + ChangeProjectReference( + context, + "/src/MyCompanyName.MyProjectName.IdentityServer/MyCompanyName.MyProjectName.IdentityServer.csproj", + "EntityFrameworkCore.DbMigrations", + "MongoDB" + ); + + ChangeModuleDependency( + context, + "/src/MyCompanyName.MyProjectName.IdentityServer/MyProjectNameIdentityServerModule.cs", + "MyCompanyName.MyProjectName.EntityFrameworkCore", + "MyCompanyName.MyProjectName.MongoDb", + "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + "MyProjectNameMongoDbModule" + ); + + ChangeConnectionStringToMongoDb( + context, + "/src/MyCompanyName.MyProjectName.IdentityServer/appsettings.json" + ); + + //MyCompanyName.MyProjectName.HttpApi.Host + + ChangeProjectReference( + context, + "/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj", + "EntityFrameworkCore.DbMigrations", + "MongoDB" + ); + + ChangeModuleDependency( + context, + "/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs", + "MyCompanyName.MyProjectName.EntityFrameworkCore", + "MyCompanyName.MyProjectName.MongoDb", + "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + "MyProjectNameMongoDbModule" + ); + + ChangeConnectionStringToMongoDb( + context, + "/src/MyCompanyName.MyProjectName.HttpApi.Host/appsettings.json" + ); + + //MyCompanyName.MyProjectName.DbMigrator + + ChangeProjectReference( + context, + "/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj", + "EntityFrameworkCore.DbMigrations", + "MongoDB" + ); + + ChangeModuleDependency( + context, + "/src/MyCompanyName.MyProjectName.DbMigrator/MyProjectNameDbMigratorModule.cs", + "MyCompanyName.MyProjectName.EntityFrameworkCore", + "MyCompanyName.MyProjectName.MongoDb", + "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + "MyProjectNameMongoDbModule" + ); + + ChangeConnectionStringToMongoDb( + context, + "/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json" + ); + + //MyCompanyName.MyProjectName.Domain.Tests + + ChangeProjectReference( + context, + "/test/MyCompanyName.MyProjectName.Domain.Tests/MyCompanyName.MyProjectName.Domain.Tests.csproj", + "EntityFrameworkCore.Tests", + "MongoDB.Tests" + ); + + ChangeModuleDependency( + context, + "/test/MyCompanyName.MyProjectName.Domain.Tests/MyProjectNameDomainTestModule.cs", + "MyCompanyName.MyProjectName.EntityFrameworkCore", + "MyCompanyName.MyProjectName.MongoDb", + "MyProjectNameEntityFrameworkCoreDbMigrationsModule", + "MyProjectNameMongoDbTestModule" + ); + } + + private void ChangeProjectReference( + ProjectBuildContext context, + string targetProjectFilePath, + string oldReference, + string newReference) + { + var file = context.GetFile(targetProjectFilePath); + + file.NormalizeLineEndings(); + + var lines = file.GetLines(); + for (var i = 0; i < lines.Length; i++) + { + if (lines[i].Contains("ProjectReference") && lines[i].Contains(oldReference)) + { + lines[i] = lines[i].Replace(oldReference, newReference); + file.SetLines(lines); + return; + } + } + + throw new ApplicationException($"Could not find the '{oldReference}' reference in the project '{targetProjectFilePath}'!"); + } + + private void ChangeModuleDependency( + ProjectBuildContext context, + string targetModuleFilePath, + string oldNamespace, + string newNamespace, + string oldModuleName, + string newModuleName) + { + var file = context.GetFile(targetModuleFilePath); + + file.NormalizeLineEndings(); + + var lines = file.GetLines(); + + for (var i = 0; i < lines.Length; i++) + { + if (lines[i].Contains($"using {oldNamespace};")) + { + lines[i] = $"using {newNamespace};"; + } + else if (lines[i].Contains(oldModuleName)) + { + lines[i] = lines[i].Replace(oldModuleName, newModuleName); + } + } + + file.SetLines(lines); + } + + private void ChangeConnectionStringToMongoDb( + ProjectBuildContext context, + string appsettingFilePath) + { + var file = context.GetFile(appsettingFilePath); + + file.NormalizeLineEndings(); + + var lines = file.GetLines(); + for (var i = 0; i < lines.Length; i++) + { + if (lines[i].Contains("Default") && lines[i].Contains("Database")) + { + lines[i] = " \"Default\": \"mongodb://localhost:27017|MyProjectName\""; + file.SetLines(lines); + return; + } + } + + throw new ApplicationException("Could not find the 'Default' connection string in appsettings.json file!"); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MyTemplateChangeConsoleTestClientPortSettingsStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MyTemplateChangeConsoleTestClientPortSettingsStep.cs new file mode 100644 index 0000000000..88e0a562c8 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Mvc/MyTemplateChangeConsoleTestClientPortSettingsStep.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Templates.Mvc +{ + public class MyTemplateChangeConsoleTestClientPortSettingsStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + context + .GetFile("/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/appsettings.json") + .ReplaceText("44395", "44361") + .ReplaceText("44348", "44361"); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcModule/MvcModuleTemplate.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcModule/MvcModuleTemplate.cs new file mode 100644 index 0000000000..e3258ec8a6 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/MvcModule/MvcModuleTemplate.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.Cli.ProjectBuilding.Building.Steps; + +namespace Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule +{ + public class MvcModuleTemplate : TemplateInfo + { + /// + /// "mvc-module". + /// + public const string TemplateName = "mvc-module"; + + public MvcModuleTemplate() + : base(TemplateName) + { + + } + + public override IEnumerable GetCustomSteps(ProjectBuildContext context) + { + var steps = new List(); + + DeleteUnrelatedProjects(context, steps); + + return steps; + } + + private void DeleteUnrelatedProjects(ProjectBuildContext context, List steps) + { + if (context.BuildArgs.ExtraProperties.ContainsKey("no-ui")) + { + steps.Add(new RemoveProjectFromSolutionStep( + "MyCompanyName.MyProjectName.Web" + )); + + steps.Add(new RemoveProjectFromSolutionStep( + "MyCompanyName.MyProjectName.Web.Host", + projectFolderPath: "/host/MyCompanyName.MyProjectName.Web.Host" + )); + + steps.Add(new RemoveProjectFromSolutionStep( + "MyCompanyName.MyProjectName.Web.Unified", + projectFolderPath: "/host/MyCompanyName.MyProjectName.Web.Unified" + )); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Zipping/ZipFileExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Zipping/ZipFileExtensions.cs new file mode 100644 index 0000000000..0160c8390e --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Zipping/ZipFileExtensions.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Ionic.Zip; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Zipping +{ + public static class ZipFileExtensions + { + public static byte[] GetBytes(this ZipFile zipFile) + { + using (var ms = new MemoryStream()) + { + zipFile.Save(ms); + return ms.ToArray(); + } + } + + public static FileEntryList ToFileEntryList(this ZipFile zipFile, string rootFolder = null) + { + var zipEntries = zipFile.Entries; + + if (rootFolder != null) + { + zipEntries = zipEntries.Where(entry => entry.FileName.StartsWith(rootFolder)).ToList(); + } + + var fileEntries = new List(); + + foreach (var zipEntry in zipEntries) + { + using (var entryStream = zipEntry.OpenReader()) + { + var fileName = zipEntry.FileName; + + if (rootFolder != null) + { + fileName = fileName.RemovePreFix(rootFolder); + } + + if (fileName.IsNullOrEmpty()) + { + continue; + } + + fileName = fileName.EnsureStartsWith('/'); + + fileEntries.Add(new FileEntry(fileName, entryStream.GetAllBytes(), zipEntry.IsDirectory)); + } + } + + return new FileEntryList(fileEntries); + } + } +} 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..3a7dab7b3a --- /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 = $"{CliUrls.WwwAbpIo}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..e7a65c0ae8 --- /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 = $"{CliUrls.WwwAbpIo}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/ConsoleHelper.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ConsoleHelper.cs new file mode 100644 index 0000000000..6e05e118d1 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Utils/ConsoleHelper.cs @@ -0,0 +1,26 @@ +using System; +using System.Text; + +namespace Volo.Abp.Cli.Utils +{ + public static class ConsoleHelper + { + public static string ReadSecret() + { + var sb = new StringBuilder(); + + while (true) + { + var keyInfo = Console.ReadKey(true); + if (keyInfo.Key == ConsoleKey.Enter) + { + break; + } + + sb.Append(keyInfo.KeyChar); + } + + return sb.ToString(); + } + } +} \ No newline at end of file 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/Volo.Abp.Cli.csproj b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj new file mode 100644 index 0000000000..f28c2554b2 --- /dev/null +++ b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj @@ -0,0 +1,25 @@ + + + + + + Exe + netcoreapp2.2 + true + abp + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs new file mode 100644 index 0000000000..339041ef8a --- /dev/null +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Cli +{ + [DependsOn( + typeof(AbpCliCoreModule), + typeof(AbpAutofacModule) + )] + public class AbpCliModule : AbpModule + { + + } +} \ 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 new file mode 100644 index 0000000000..8eefc78f9b --- /dev/null +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.DependencyInjection; +using Serilog; +using Serilog.Events; +using System.IO; +using Volo.Abp.Threading; + +namespace Volo.Abp.Cli +{ + public class Program + { + private static void Main(string[] args) + { + 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() + .CreateLogger(); + + using (var application = AbpApplicationFactory.Create( + options => + { + options.UseAutofac(); + options.Services.AddLogging(c => c.AddSerilog()); + })) + { + application.Initialize(); + + AsyncHelper.RunSync( + () => application.ServiceProvider + .GetRequiredService() + .RunAsync(args) + ); + + application.Shutdown(); + } + } + } +} diff --git a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs index a7cf7c763e..31e7f257d4 100644 --- a/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpStringExtensions.cs @@ -84,11 +84,11 @@ namespace System } /// - /// Gets index of nth occurence of a char in a string. + /// Gets index of nth occurrence of a char in a string. /// /// source string to be searched /// Char to search in - /// Count of the occurence + /// Count of the occurrence public static int NthIndexOf(this string str, char c, int n) { Check.NotNull(str, nameof(str)); diff --git a/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs index a90e4212e1..a930096404 100644 --- a/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs @@ -1,4 +1,7 @@ -namespace System +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace System { public static class AbpTypeExtensions { @@ -31,5 +34,36 @@ { return targetType.IsAssignableFrom(type); } + + /// + /// Gets all base classes of this type. + /// + /// The type to get its base classes. + /// True, to include the standard type in the returned array. + public static Type[] GetBaseClasses(this Type type, bool includeObject = true) + { + var types = new List(); + AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject); + return types.ToArray(); + } + + private static void AddTypeAndBaseTypesRecursively( + [NotNull] List types, + [CanBeNull] Type type, + bool includeObject) + { + if (type == null) + { + return; + } + + if (!includeObject && type == typeof(object)) + { + return; + } + + AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject); + types.Add(type); + } } } 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/System/Reflection/AbpAssemblyExtensions.cs b/framework/src/Volo.Abp.Core/System/Reflection/AbpAssemblyExtensions.cs new file mode 100644 index 0000000000..0e61770618 --- /dev/null +++ b/framework/src/Volo.Abp.Core/System/Reflection/AbpAssemblyExtensions.cs @@ -0,0 +1,12 @@ +using System.Diagnostics; + +namespace System.Reflection +{ + public static class AbpAssemblyExtensions + { + public static string GetFileVersion(this Assembly assembly) + { + return FileVersionInfo.GetVersionInfo(assembly.Location).FileVersion; + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj index 71d48038e2..e5355a19c1 100644 --- a/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj +++ b/framework/src/Volo.Abp.Core/Volo.Abp.Core.csproj @@ -14,16 +14,16 @@ - + - + - + diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs index a4862789f2..8036760f6a 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs @@ -56,7 +56,7 @@ namespace Volo.Abp public virtual void Dispose() { - + //TODO: Shutdown if not done before? } protected virtual void SetServiceProvider(IServiceProvider serviceProvider) 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/DependencyInjection/AutoRegistrationHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AutoRegistrationHelper.cs deleted file mode 100644 index dd8795855e..0000000000 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AutoRegistrationHelper.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.Extensions.DependencyInjection; - -namespace Volo.Abp.DependencyInjection -{ - public static class AutoRegistrationHelper - { - public static IEnumerable GetExposedServices(IServiceCollection services, Type type) - { - var typeInfo = type.GetTypeInfo(); - - var customExposedServices = typeInfo - .GetCustomAttributes() - .OfType() - .SelectMany(p => p.GetExposedServiceTypes(type)) - .ToList(); - - if (customExposedServices.Any()) - { - return customExposedServices; - } - - return GetDefaultExposedServices(services, type); - } - - private static IEnumerable GetDefaultExposedServices(IServiceCollection services, Type type) - { - var serviceTypes = new List(); - - serviceTypes.Add(type); - - foreach (var interfaceType in type.GetTypeInfo().GetInterfaces()) - { - var interfaceName = interfaceType.Name; - - if (interfaceName.StartsWith("I")) - { - interfaceName = interfaceName.Right(interfaceName.Length - 1); - } - - if (type.Name.EndsWith(interfaceName)) - { - serviceTypes.Add(interfaceType); - } - } - - var exposeActions = services.GetExposingActionList(); - if (exposeActions.Any()) - { - var args = new OnServiceExposingContext(type, serviceTypes); - foreach (var action in services.GetExposingActionList()) - { - action(args); - } - } - - return serviceTypes; - } - } -} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs index 19740d6ab6..9c5ee06d81 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/DefaultConventionalRegistrar.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; @@ -24,7 +26,11 @@ namespace Volo.Abp.DependencyInjection return; } - foreach (var serviceType in AutoRegistrationHelper.GetExposedServices(services, type)) + var serviceTypes = ExposedServiceExplorer.GetExposedServices(type); + + TriggerServiceExposing(services, type, serviceTypes); + + foreach (var serviceType in serviceTypes) { var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value); @@ -43,6 +49,19 @@ namespace Volo.Abp.DependencyInjection } } + protected virtual void TriggerServiceExposing(IServiceCollection services, Type type, List serviceTypes) + { + var exposeActions = services.GetExposingActionList(); + if (exposeActions.Any()) + { + var args = new OnServiceExposingContext(type, serviceTypes); + foreach (var action in exposeActions) + { + action(args); + } + } + } + protected virtual bool IsConventionalRegistrationDisabled(Type type) { return type.IsDefined(typeof(DisableConventionalRegistrationAttribute), true); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs index f46be345af..ad808e6026 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposeServicesAttribute.cs @@ -1,19 +1,67 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; namespace Volo.Abp.DependencyInjection { public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider { - public Type[] ExposedServiceTypes { get; } + public Type[] ServiceTypes { get; } - public ExposeServicesAttribute(params Type[] exposedServiceTypes) + public bool? IncludeDefaults { get; set; } + + public bool? IncludeSelf { get; set; } + + public ExposeServicesAttribute(params Type[] serviceTypes) { - ExposedServiceTypes = exposedServiceTypes ?? new Type[0]; + ServiceTypes = serviceTypes ?? new Type[0]; } public Type[] GetExposedServiceTypes(Type targetType) { - return ExposedServiceTypes; + var serviceList = ServiceTypes.ToList(); + + if (IncludeDefaults == true) + { + foreach (var type in GetDefaultServices(targetType)) + { + serviceList.AddIfNotContains(type); + } + + if (IncludeSelf != false) + { + serviceList.AddIfNotContains(targetType); + } + } + else if (IncludeSelf == true) + { + serviceList.AddIfNotContains(targetType); + } + + return serviceList.ToArray(); + } + + private static List GetDefaultServices(Type type) + { + var serviceTypes = new List(); + + foreach (var interfaceType in type.GetTypeInfo().GetInterfaces()) + { + var interfaceName = interfaceType.Name; + + if (interfaceName.StartsWith("I")) + { + interfaceName = interfaceName.Right(interfaceName.Length - 1); + } + + if (type.Name.EndsWith(interfaceName)) + { + serviceTypes.Add(interfaceType); + } + } + + return serviceTypes; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs new file mode 100644 index 0000000000..f7324014d4 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Volo.Abp.DependencyInjection +{ + public static class ExposedServiceExplorer + { + private static readonly ExposeServicesAttribute DefaultExposeServicesAttribute = + new ExposeServicesAttribute + { + IncludeDefaults = true + }; + + public static List GetExposedServices(Type type) + { + return type + .GetCustomAttributes() + .OfType() + .DefaultIfEmpty(DefaultExposeServicesAttribute) + .SelectMany(p => p.GetExposedServiceTypes(type)) + .ToList(); + } + } +} 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.Core/Volo/Abp/Modularity/IAbpModule.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs index b58fdc8eed..32c8980c54 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModule.cs @@ -1,6 +1,4 @@ -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.DependencyInjection; +using Volo.Abp.DependencyInjection; namespace Volo.Abp.Modularity { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs index d79ae0b843..934948dc03 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs @@ -42,12 +42,12 @@ namespace Volo.Abp.Modularity } } - _logger.LogInformation("Initialized all modules."); + _logger.LogInformation("Initialized all ABP modules."); } private void LogListOfModules() { - _logger.LogInformation("Loaded modules:"); + _logger.LogInformation("Loaded ABP modules:"); foreach (var module in _moduleContainer.Modules) { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs index 1c4b162b21..b314ca6b84 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/ReflectionHelper.cs @@ -165,5 +165,41 @@ namespace Volo.Abp.Reflection property = currentType.GetProperty(properties.Last()); property.SetValue(obj, value); } + + + /// + /// Get all the constant values in the specified type (including the base type). + /// + /// + /// + public static string[] GetPublicConstantsRecursively(Type type) + { + const int maxRecursiveParameterValidationDepth = 8; + + var publicConstants = new List(); + + void Recursively(List constants, Type targetType, int currentDepth) + { + if (currentDepth > maxRecursiveParameterValidationDepth) + { + return; + } + + constants.AddRange(targetType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) + .Where(x => x.IsLiteral && !x.IsInitOnly) + .Select(x => x.GetValue(null).ToString())); + + var nestedTypes = targetType.GetNestedTypes(BindingFlags.Public); + + foreach (var nestedType in nestedTypes) + { + Recursively(constants, nestedType, currentDepth + 1); + } + } + + Recursively(publicConstants, type, 1); + + return publicConstants.ToArray(); + } } } diff --git a/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj b/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj index 51670ab1ee..d2734cb119 100644 --- a/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj +++ b/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj @@ -15,6 +15,7 @@ + diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs index 428d9d6313..584298f2d4 100644 --- a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs @@ -2,9 +2,13 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.Data { + [DependsOn( + typeof(AbpUnitOfWorkModule) + )] public class AbpDataModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/DataSeeder.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/DataSeeder.cs index e55f6c633b..a4d6c0886b 100644 --- a/framework/src/Volo.Abp.Data/Volo/Abp/Data/DataSeeder.cs +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/DataSeeder.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; namespace Volo.Abp.Data { @@ -19,7 +20,8 @@ namespace Volo.Abp.Data Options = options.Value; } - public async Task SeedAsync(DataSeedContext context) + [UnitOfWork] + public virtual async Task SeedAsync(DataSeedContext context) { using (var scope = ServiceScopeFactory.CreateScope()) { diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/LimitedResultRequestDto.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/LimitedResultRequestDto.cs index b3ed6a23cb..0931d090f3 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/LimitedResultRequestDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Dtos/LimitedResultRequestDto.cs @@ -9,7 +9,9 @@ namespace Volo.Abp.Application.Dtos [Serializable] public class LimitedResultRequestDto : ILimitedResultRequest { + public static int DefaultMaxResultCount { get; set; } = 10; + [Range(1, int.MaxValue)] - public virtual int MaxResultCount { get; set; } = 10; + public virtual int MaxResultCount { get; set; } = DefaultMaxResultCount; } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs index 3b2bbc7582..909ad40940 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ApplicationService.cs @@ -1,5 +1,6 @@ using JetBrains.Annotations; using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -31,31 +32,59 @@ namespace Volo.Abp.Application.Services IAuditingEnabled, ITransientDependency { + public IServiceProvider ServiceProvider { get; set; } + protected readonly object ServiceProviderLock = new object(); + protected TService LazyGetRequiredService(ref TService reference) + { + if (reference == null) + { + lock (ServiceProviderLock) + { + if (reference == null) + { + reference = ServiceProvider.GetRequiredService(); + } + } + } + + return reference; + } + public static string[] CommonPostfixes { get; set; } = { "AppService", "ApplicationService", "Service" }; public List AppliedCrossCuttingConcerns { get; } = new List(); - public IUnitOfWorkManager UnitOfWorkManager { get; set; } + public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager); + private IUnitOfWorkManager _unitOfWorkManager; - public IObjectMapper ObjectMapper { get; set; } + public IObjectMapper ObjectMapper => LazyGetRequiredService(ref _objectMapper); + private IObjectMapper _objectMapper; public IGuidGenerator GuidGenerator { get; set; } - public ILoggerFactory LoggerFactory { get; set; } + public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); + private ILoggerFactory _loggerFactory; - public ICurrentTenant CurrentTenant { get; set; } + public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant); + private ICurrentTenant _currentTenant; - public ICurrentUser CurrentUser { get; set; } + public ICurrentUser CurrentUser => LazyGetRequiredService(ref _currentUser); + private ICurrentUser _currentUser; - public ISettingProvider SettingProvider { get; set; } + public ISettingProvider SettingProvider => LazyGetRequiredService(ref _settingProvider); + private ISettingProvider _settingProvider; - public IClock Clock { get; set; } + public IClock Clock => LazyGetRequiredService(ref _clock); + private IClock _clock; - public IAuthorizationService AuthorizationService { get; set; } + public IAuthorizationService AuthorizationService => LazyGetRequiredService(ref _authorizationService); + private IAuthorizationService _authorizationService; - public IFeatureChecker FeatureChecker { get; set; } + public IFeatureChecker FeatureChecker => LazyGetRequiredService(ref _featureChecker); + private IFeatureChecker _featureChecker; - public IStringLocalizerFactory StringLocalizerFactory { get; set; } + public IStringLocalizerFactory StringLocalizerFactory => LazyGetRequiredService(ref _stringLocalizerFactory); + private IStringLocalizerFactory _stringLocalizerFactory; public IStringLocalizer L => _localizer ?? (_localizer = StringLocalizerFactory.Create(LocalizationResource)); private IStringLocalizer _localizer; 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 36c4780672..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 { @@ -20,8 +20,8 @@ namespace Volo.Abp.Application.Services } } - public abstract class AsyncCrudAppService - : AsyncCrudAppService + public abstract class AsyncCrudAppService + : AsyncCrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { @@ -32,8 +32,8 @@ namespace Volo.Abp.Application.Services } } - public abstract class AsyncCrudAppService - : AsyncCrudAppService + public abstract class AsyncCrudAppService + : AsyncCrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { @@ -44,9 +44,9 @@ namespace Volo.Abp.Application.Services } } - public abstract class AsyncCrudAppService - : CrudAppServiceBase, - IAsyncCrudAppService + public abstract class AsyncCrudAppService + : CrudAppServiceBase, + IAsyncCrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { @@ -66,9 +66,9 @@ namespace Volo.Abp.Application.Services return MapToEntityDto(entity); } - public virtual async Task> GetListAsync(TGetAllInput input) + public virtual async Task> GetListAsync(TGetListInput input) { - await CheckGetAllPolicyAsync(); + await CheckGetListPolicyAsync(); var query = CreateFilteredQuery(input); @@ -90,9 +90,13 @@ namespace Volo.Abp.Application.Services await CheckCreatePolicyAsync(); var entity = MapToEntity(input); - - await Repository.InsertAsync(entity); - await CurrentUnitOfWork.SaveChangesAsync(); + + if(entity is IMultiTenant && !HasTenantIdProperty(entity)) + { + TryToSetTenantId(entity); + } + + await Repository.InsertAsync(entity, autoSave: true); return MapToEntityDto(entity); } @@ -102,11 +106,9 @@ namespace Volo.Abp.Application.Services await CheckUpdatePolicyAsync(); var entity = await GetEntityByIdAsync(id); - //TODO: Check if input has id different than given id and normalize if it's default value, throw ex otherwise - MapToEntity(input, entity); - await CurrentUnitOfWork.SaveChangesAsync(); + await Repository.UpdateAsync(entity, autoSave: true); return MapToEntityDto(entity); } @@ -128,9 +130,9 @@ namespace Volo.Abp.Application.Services await CheckPolicyAsync(GetPolicyName); } - protected virtual async Task CheckGetAllPolicyAsync() + protected virtual async Task CheckGetListPolicyAsync() { - await CheckPolicyAsync(GetAllPolicyName); + await CheckPolicyAsync(GetListPolicyName); } protected virtual async Task CheckCreatePolicyAsync() 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 8127e88f23..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 { @@ -17,8 +18,8 @@ namespace Volo.Abp.Application.Services } } - public abstract class CrudAppService - : CrudAppService + public abstract class CrudAppService + : CrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { @@ -29,8 +30,8 @@ namespace Volo.Abp.Application.Services } } - public abstract class CrudAppService - : CrudAppService + public abstract class CrudAppService + : CrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto where TCreateInput : IEntityDto @@ -42,9 +43,9 @@ namespace Volo.Abp.Application.Services } } - public abstract class CrudAppService - : CrudAppServiceBase, - ICrudAppService + public abstract class CrudAppService + : CrudAppServiceBase, + ICrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { @@ -62,9 +63,9 @@ namespace Volo.Abp.Application.Services return MapToEntityDto(entity); } - public virtual PagedResultDto GetAll(TGetAllInput input) + public virtual PagedResultDto GetList(TGetListInput input) { - CheckGetAllPolicy(); + CheckGetListPolicy(); var query = CreateFilteredQuery(input); @@ -86,9 +87,13 @@ namespace Volo.Abp.Application.Services CheckCreatePolicy(); var entity = MapToEntity(input); - - Repository.Insert(entity); - CurrentUnitOfWork.SaveChanges(); + + if(entity is IMultiTenant && !HasTenantIdProperty(entity)) + { + TryToSetTenantId(entity); + } + + Repository.Insert(entity, autoSave: true); return MapToEntityDto(entity); } @@ -98,9 +103,8 @@ namespace Volo.Abp.Application.Services CheckUpdatePolicy(); var entity = GetEntityById(id); - MapToEntity(input, entity); - CurrentUnitOfWork.SaveChanges(); + Repository.Update(entity, autoSave: true); return MapToEntityDto(entity); } @@ -122,9 +126,9 @@ namespace Volo.Abp.Application.Services CheckPolicy(GetPolicyName); } - protected virtual void CheckGetAllPolicy() + protected virtual void CheckGetListPolicy() { - CheckPolicy(GetAllPolicyName); + CheckPolicy(GetListPolicyName); } protected virtual void CheckCreatePolicy() 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 eb4c7a200f..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 @@ -12,7 +13,7 @@ namespace Volo.Abp.Application.Services /// This is a common base class for CrudAppService and AsyncCrudAppService classes. /// Inherit either from CrudAppService or AsyncCrudAppService, not from this class. /// - public abstract class CrudAppServiceBase : + public abstract class CrudAppServiceBase : ApplicationService where TEntity : class, IEntity where TEntityDto : IEntityDto @@ -21,7 +22,7 @@ namespace Volo.Abp.Application.Services protected virtual string GetPolicyName { get; set; } - protected virtual string GetAllPolicyName { get; set; } + protected virtual string GetListPolicyName { get; set; } protected virtual string CreatePolicyName { get; set; } @@ -39,7 +40,7 @@ namespace Volo.Abp.Application.Services /// /// The query. /// The input. - protected virtual IQueryable ApplySorting(IQueryable query, TGetAllInput input) + protected virtual IQueryable ApplySorting(IQueryable query, TGetListInput input) { //Try to sort query if available var sortInput = input as ISortedResultRequest; @@ -66,7 +67,7 @@ namespace Volo.Abp.Application.Services /// /// The query. /// The input. - protected virtual IQueryable ApplyPaging(IQueryable query, TGetAllInput input) + protected virtual IQueryable ApplyPaging(IQueryable query, TGetListInput input) { //Try to use paging if available var pagedInput = input as IPagedResultRequest; @@ -93,7 +94,7 @@ namespace Volo.Abp.Application.Services /// methods. /// /// The input. - protected virtual IQueryable CreateFilteredQuery(TGetAllInput input) + protected virtual IQueryable CreateFilteredQuery(TGetListInput input) { return Repository; } @@ -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.Ddd.Application/Volo/Abp/Application/Services/ICrudAppService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ICrudAppService.cs index 9f06c63aea..7e8b96b9f8 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ICrudAppService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/ICrudAppService.cs @@ -29,7 +29,7 @@ namespace Volo.Abp.Application.Services { TEntityDto Get(TKey id); - PagedResultDto GetAll(TGetListInput input); + PagedResultDto GetList(TGetListInput input); TEntityDto Create(TCreateInput input); diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityEto.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityEto.cs index aa2a6e1c8f..d5f0a7dc2d 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityEto.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityEto.cs @@ -5,6 +5,8 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed [Serializable] public class EntityEto : EtoBase { + public string EntityType { get; set; } + public string KeysAsString { get; set; } public EntityEto() @@ -12,8 +14,9 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed } - public EntityEto(string keysAsString) + public EntityEto(string entityType, string keysAsString) { + EntityType = entityType; KeysAsString = keysAsString; } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs index 354f72dddc..5b386599ef 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/EntityToEtoMapper.cs @@ -1,6 +1,5 @@ -using System; +using Microsoft.Extensions.Options; using System.Collections.Generic; -using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; using Volo.Abp.EventBus.Distributed; @@ -28,7 +27,7 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed var entity = entityObj as IEntity; if (entity == null) { - throw new ArgumentException($"{nameof(entityObj)} should be an entity and implement the '{typeof(IEntity).AssemblyQualifiedName}' interface!"); + return null; } var entityType = ProxyHelper.UnProxy(entity).GetType(); @@ -36,10 +35,10 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed if (etoType == null) { var keys = entity.GetKeys().JoinAsString(","); - return new EntityEto(keys); + return new EntityEto(entityType.FullName, keys); } - //TODO: Also add KeysAsString property to resulting json for compability with the EntityEto! + //TODO: Also add KeysAsString property to resulting json for compatibility with the EntityEto! return ObjectMapper.Map(entityType, etoType, entityObj); } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IEntityToEtoMapper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IEntityToEtoMapper.cs index e33e6751e8..ceefb311ef 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IEntityToEtoMapper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/Distributed/IEntityToEtoMapper.cs @@ -1,7 +1,10 @@ -namespace Volo.Abp.Domain.Entities.Events.Distributed +using JetBrains.Annotations; + +namespace Volo.Abp.Domain.Entities.Events.Distributed { public interface IEntityToEtoMapper { + [CanBeNull] object Map(object entityObj); } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs index dc4edea364..c9219feffc 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Events/EntityChangeEventHelper.cs @@ -48,35 +48,95 @@ namespace Volo.Abp.Domain.Entities.Events public virtual async Task TriggerEntityCreatingEventAsync(object entity) { - await TriggerEventWithEntity(LocalEventBus, typeof(EntityCreatingEventData<>), entity, true); + await TriggerEventWithEntity( + LocalEventBus, + typeof(EntityCreatingEventData<>), + entity, + true + ); } public virtual async Task TriggerEntityCreatedEventOnUowCompletedAsync(object entity) { - await TriggerEventWithEntity(LocalEventBus, typeof(EntityCreatedEventData<>), entity, false); - await TriggerEventWithEntity(DistributedEventBus, typeof(EntityCreatedEto<>), EntityToEtoMapper.Map(entity), false); + await TriggerEventWithEntity( + LocalEventBus, + typeof(EntityCreatedEventData<>), + entity, + false + ); + + var eto = EntityToEtoMapper.Map(entity); + if (eto != null) + { + await TriggerEventWithEntity( + DistributedEventBus, + typeof(EntityCreatedEto<>), + eto, + false + ); + } } public virtual async Task TriggerEntityUpdatingEventAsync(object entity) { - await TriggerEventWithEntity(LocalEventBus, typeof(EntityUpdatingEventData<>), entity, true); + await TriggerEventWithEntity( + LocalEventBus, + typeof(EntityUpdatingEventData<>), + entity, + true + ); } public virtual async Task TriggerEntityUpdatedEventOnUowCompletedAsync(object entity) { - await TriggerEventWithEntity(LocalEventBus, typeof(EntityUpdatedEventData<>), entity, false); - await TriggerEventWithEntity(DistributedEventBus, typeof(EntityUpdatedEto<>), EntityToEtoMapper.Map(entity), false); + await TriggerEventWithEntity( + LocalEventBus, + typeof(EntityUpdatedEventData<>), + entity, + false + ); + + var eto = EntityToEtoMapper.Map(entity); + if (eto != null) + { + await TriggerEventWithEntity( + DistributedEventBus, + typeof(EntityUpdatedEto<>), + eto, + false + ); + } } public virtual async Task TriggerEntityDeletingEventAsync(object entity) { - await TriggerEventWithEntity(LocalEventBus, typeof(EntityDeletingEventData<>), entity, true); + await TriggerEventWithEntity( + LocalEventBus, + typeof(EntityDeletingEventData<>), + entity, + true + ); } public virtual async Task TriggerEntityDeletedEventOnUowCompletedAsync(object entity) { - await TriggerEventWithEntity(LocalEventBus, typeof(EntityDeletedEventData<>), entity, false); - await TriggerEventWithEntity(DistributedEventBus, typeof(EntityDeletedEto<>), EntityToEtoMapper.Map(entity), false); + await TriggerEventWithEntity( + LocalEventBus, + typeof(EntityDeletedEventData<>), + entity, + false + ); + + var eto = EntityToEtoMapper.Map(entity); + if (eto != null) + { + await TriggerEventWithEntity( + DistributedEventBus, + typeof(EntityDeletedEto<>), + EntityToEtoMapper.Map(entity), + false + ); + } } protected virtual async Task TriggerEventsInternalAsync(EntityChangeReport changeReport) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Services/DomainService.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Services/DomainService.cs index ea94690d61..14e8a64131 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Services/DomainService.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Services/DomainService.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Guids; @@ -9,13 +10,34 @@ namespace Volo.Abp.Domain.Services { public abstract class DomainService : IDomainService { - public IClock Clock { get; set; } + public IServiceProvider ServiceProvider { get; set; } + protected readonly object ServiceProviderLock = new object(); + protected TService LazyGetRequiredService(ref TService reference) + { + if (reference == null) + { + lock (ServiceProviderLock) + { + if (reference == null) + { + reference = ServiceProvider.GetRequiredService(); + } + } + } + + return reference; + } + + public IClock Clock => LazyGetRequiredService(ref _clock); + private IClock _clock; public IGuidGenerator GuidGenerator { get; set; } - public ILoggerFactory LoggerFactory { get; set; } + public ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); + private ILoggerFactory _loggerFactory; - public ICurrentTenant CurrentTenant { get; set; } + public ICurrentTenant CurrentTenant => LazyGetRequiredService(ref _currentTenant); + private ICurrentTenant _currentTenant; protected ILogger Logger => _lazyLogger.Value; private Lazy _lazyLogger => new Lazy(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true); diff --git a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj index ab1b4ed370..b2394ea229 100644 --- a/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj +++ b/framework/src/Volo.Abp.Emailing/Volo.Abp.Emailing.csproj @@ -14,8 +14,12 @@ - - + + + + + + diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs index b861a3898c..d3a9598da1 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/AbpEmailingModule.cs @@ -1,6 +1,8 @@ -using Volo.Abp.BackgroundJobs; +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.BackgroundJobs; using Volo.Abp.Emailing.Templates; -using Volo.Abp.Emailing.Templates.Virtual; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.Settings; @@ -16,6 +18,11 @@ namespace Volo.Abp.Emailing )] public class AbpEmailingModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + AutoAddDefinitionProviders(context.Services); + } + public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => @@ -27,18 +34,42 @@ namespace Volo.Abp.Emailing { options.AddJob(); }); + } - Configure(options => + private static void AutoAddDefinitionProviders(IServiceCollection services) + { + var definitionProviders = new List(); + + services.OnRegistred(context => { - options.Templates - .Add( - new EmailTemplateDefinition(StandardEmailTemplates.DefaultLayout, isLayout: true, layout: null) - .SetVirtualFilePath("/Volo/Abp/Emailing/Templates/DefaultLayout.html") - ).Add( - new EmailTemplateDefinition(StandardEmailTemplates.SimpleMessage) - .SetVirtualFilePath("/Volo/Abp/Emailing/Templates/SimpleMessageTemplate.html") - ); + + if (typeof(IEmailTemplateDefinitionProvider).IsAssignableFrom(context.ImplementationType)) + { + definitionProviders.Add(context.ImplementationType); + } }); + + services.Configure(options => + { + options.DefinitionProviders.AddIfNotContains(definitionProviders); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + using (var scope = context.ServiceProvider.CreateScope()) + { + var emailTemplateDefinitionManager = + scope.ServiceProvider.GetRequiredService(); + + foreach (var templateDefinition in emailTemplateDefinitionManager.GetAll()) + { + foreach (var contributor in templateDefinition.Contributors) + { + contributor.Initialize(new EmailTemplateInitializationContext(templateDefinition, scope.ServiceProvider)); + } + } + } } } } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs new file mode 100644 index 0000000000..fdc7fa5375 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplateProvider.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Emailing.Templates.VirtualFiles; + +namespace Volo.Abp.Emailing.Templates +{ + public class DefaultEmailTemplateProvider : EmailTemplateDefinitionProvider + { + public override void Define(IEmailTemplateDefinitionContext context) + { + context.Add(new EmailTemplateDefinition(StandardEmailTemplates.DefaultLayout, isLayout: true, layout: null) + .AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout")); + + context.Add(new EmailTemplateDefinition(StandardEmailTemplates.SimpleMessage) + .AddTemplateVirtualFiles("/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message")); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultLayout.html b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl similarity index 100% rename from framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultLayout.html rename to framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Layout/en.tpl diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/SimpleMessageTemplate.html b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl similarity index 100% rename from framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/SimpleMessageTemplate.html rename to framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/DefaultEmailTemplates/Message/en.tpl diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs new file mode 100644 index 0000000000..44a91212ea --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateContributorList.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateContributorList : List + { + public string GetOrNull(string cultureName) + { + foreach (var contributor in this.AsQueryable().Reverse()) + { + var templateString = contributor.GetOrNull(cultureName); + if (templateString != null) + { + return templateString; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs index 9ef2ae3b1b..19f0eb2fa7 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinition.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using JetBrains.Annotations; namespace Volo.Abp.Emailing.Templates @@ -16,29 +15,22 @@ namespace Volo.Abp.Emailing.Templates public Type LocalizationResource { get; set; } - public Dictionary Properties { get; } - - /// - /// Gets/sets a key-value on the . - /// - /// Name of the property - /// - /// Returns the value in the dictionary by given . - /// Returns null if given is not present in the dictionary. - /// - public object this[string name] - { - get => Properties.GetOrDefault(name); - set => Properties[name] = value; - } + public EmailTemplateContributorList Contributors { get; } + + public string DefaultCultureName { get; } + + public bool SingleTemplateFile { get; } - public EmailTemplateDefinition([NotNull]string name, Type localizationResource = null, bool isLayout = false, string layout = DefaultLayoutPlaceHolder) + public EmailTemplateDefinition([NotNull] string name, Type localizationResource = null, bool isLayout = false, + string layout = DefaultLayoutPlaceHolder, string defaultCultureName = null, bool singleTemplateFile = false) { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); - Properties = new Dictionary(); LocalizationResource = localizationResource; + Contributors = new EmailTemplateContributorList(); IsLayout = isLayout; Layout = layout; + DefaultCultureName = defaultCultureName; + SingleTemplateFile = singleTemplateFile; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs new file mode 100644 index 0000000000..03a6c95d8b --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionContext.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateDefinitionContext : IEmailTemplateDefinitionContext + { + protected Dictionary EmailTemplates { get; } + + public EmailTemplateDefinitionContext(Dictionary emailTemplates) + { + EmailTemplates = emailTemplates; + } + + public virtual EmailTemplateDefinition GetOrNull(string name) + { + return EmailTemplates.GetOrDefault(name); + } + + public virtual IReadOnlyList GetAll() + { + return EmailTemplates.Values.ToImmutableList(); + } + + public virtual void Add(params EmailTemplateDefinition[] definitions) + { + if (definitions.IsNullOrEmpty()) + { + return; + } + + foreach (var definition in definitions) + { + EmailTemplates[definition.Name] = definition; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs new file mode 100644 index 0000000000..a6a58aef27 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionManager.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateDefinitionManager : IEmailTemplateDefinitionManager, ISingletonDependency + { + protected Lazy> EmailTemplateDefinitions { get; } + + protected EmailTemplateOptions Options { get; } + + protected IServiceProvider ServiceProvider { get; } + + public EmailTemplateDefinitionManager( + IOptions options, + IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + + EmailTemplateDefinitions = + new Lazy>(CreateEmailTemplateDefinitions, true); + } + + public virtual EmailTemplateDefinition Get(string name) + { + Check.NotNull(name, nameof(name)); + + var template = GetOrNull(name); + + if (template == null) + { + throw new AbpException("Undefined template: " + name); + } + + return template; + } + + public virtual IReadOnlyList GetAll() + { + return EmailTemplateDefinitions.Value.Values.ToImmutableList(); + } + + public virtual EmailTemplateDefinition GetOrNull(string name) + { + return EmailTemplateDefinitions.Value.GetOrDefault(name); + } + + protected virtual IDictionary CreateEmailTemplateDefinitions() + { + var templates = new Dictionary(); + + using (var scope = ServiceProvider.CreateScope()) + { + var providers = Options + .DefinitionProviders + .Select(p => scope.ServiceProvider.GetRequiredService(p) as IEmailTemplateDefinitionProvider) + .ToList(); + + foreach (var provider in providers) + { + provider.Define(new EmailTemplateDefinitionContext(templates)); + } + } + + return templates; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs new file mode 100644 index 0000000000..e53505fafc --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateDefinitionProvider.cs @@ -0,0 +1,9 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Emailing.Templates +{ + public abstract class EmailTemplateDefinitionProvider : IEmailTemplateDefinitionProvider, ITransientDependency + { + public abstract void Define(IEmailTemplateDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs new file mode 100644 index 0000000000..8cd4b95bbd --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateInitializationContext.cs @@ -0,0 +1,18 @@ +using System; + +namespace Volo.Abp.Emailing.Templates +{ + public class EmailTemplateInitializationContext + { + public EmailTemplateDefinition EmailTemplateDefinition { get; } + + public IServiceProvider ServiceProvider { get; } + + public EmailTemplateInitializationContext(EmailTemplateDefinition emailTemplateDefinition, + IServiceProvider serviceProvider) + { + EmailTemplateDefinition = emailTemplateDefinition; + ServiceProvider = serviceProvider; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateOptions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateOptions.cs index 6d10bd562e..57533b1aff 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateOptions.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateOptions.cs @@ -1,26 +1,18 @@ -using System.Collections.Generic; -using Volo.Abp.Emailing.Templates.Virtual; +using Volo.Abp.Collections; namespace Volo.Abp.Emailing.Templates { public class EmailTemplateOptions { - public List Providers { get; } - - public EmailTemplateDefinitionDictionary Templates { get; } - public string DefaultLayout { get; set; } + public ITypeList DefinitionProviders { get; } + public EmailTemplateOptions() { - Providers = new List - { - new VirtualFileEmailTemplateProviderContributor() - }; - - Templates = new EmailTemplateDefinitionDictionary(); - DefaultLayout = StandardEmailTemplates.DefaultLayout; + + DefinitionProviders = new TypeList(); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs index c5fb44e0ed..28abdfc213 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProvider.cs @@ -1,6 +1,6 @@ using System; +using System.Globalization; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -10,80 +10,105 @@ namespace Volo.Abp.Emailing.Templates { public class EmailTemplateProvider : IEmailTemplateProvider, ITransientDependency { - protected IServiceProvider ServiceProvider { get; } + protected IEmailTemplateDefinitionManager EmailTemplateDefinitionManager; protected ITemplateLocalizer TemplateLocalizer { get; } protected EmailTemplateOptions Options { get; } + protected IStringLocalizerFactory StringLocalizerFactory; - public EmailTemplateProvider( - IOptions options, - IServiceProvider serviceProvider, - ITemplateLocalizer templateLocalizer) + public EmailTemplateProvider(IEmailTemplateDefinitionManager emailTemplateDefinitionManager, + ITemplateLocalizer templateLocalizer, IStringLocalizerFactory stringLocalizerFactory, + IOptions options) { - ServiceProvider = serviceProvider; + EmailTemplateDefinitionManager = emailTemplateDefinitionManager; TemplateLocalizer = templateLocalizer; + StringLocalizerFactory = stringLocalizerFactory; Options = options.Value; } - public async Task GetAsync(string name) + public async Task GetAsync(string name, string cultureName) { - using (var scope = ServiceProvider.CreateScope()) - { - return await GetInternalAsync(scope.ServiceProvider, name); - } + return await GetInternalAsync(name, cultureName); } - protected virtual async Task GetInternalAsync(IServiceProvider serviceProvider, string name) + protected virtual async Task GetInternalAsync(string name, string cultureName) { - var context = new EmailTemplateProviderContributorContext(name, serviceProvider); + var emailTemplateDefinition = EmailTemplateDefinitionManager.GetOrNull(name); + if (emailTemplateDefinition == null) + { + // TODO: Localized message + throw new AbpException($"email template {name} not definition"); + } - foreach (var provider in Options.Providers) + var emailTemplateString = emailTemplateDefinition.Contributors.GetOrNull(cultureName); + if (emailTemplateString == null && emailTemplateDefinition.DefaultCultureName != null) { - await provider.ProvideAsync(context); + emailTemplateString = + emailTemplateDefinition.Contributors.GetOrNull(emailTemplateDefinition.DefaultCultureName); + if (emailTemplateString != null) + { + cultureName = emailTemplateDefinition.DefaultCultureName; + } } - if (context.Template == null) + if (emailTemplateString != null) { - throw new AbpException($"Could not found the template: {name}"); + var emailTemplate = new EmailTemplate(emailTemplateString, emailTemplateDefinition); + + await SetLayoutAsync(emailTemplateDefinition, emailTemplate, cultureName); + + if (emailTemplateDefinition.SingleTemplateFile) + { + await LocalizeAsync(emailTemplateDefinition, emailTemplate, cultureName); + } + + return emailTemplate; } - await SetLayoutAsync(serviceProvider, context); - await LocalizeAsync(serviceProvider, context); - - return context.Template; + // TODO: Localized message + throw new AbpException($"{cultureName} template not exist!"); } - protected virtual async Task SetLayoutAsync(IServiceProvider serviceProvider, EmailTemplateProviderContributorContext context) + protected virtual async Task SetLayoutAsync(EmailTemplateDefinition emailTemplateDefinition, + EmailTemplate emailTemplate, string cultureName) { - var layout = context.Template.Definition.Layout; - - if (layout.IsNullOrEmpty()) + var layout = emailTemplateDefinition.Layout; + if (layout.IsNullOrWhiteSpace()) { return; } - + if (layout == EmailTemplateDefinition.DefaultLayoutPlaceHolder) { layout = Options.DefaultLayout; } - var layoutTemplate = await GetInternalAsync(serviceProvider, layout); - context.Template.SetLayout(layoutTemplate); + var layoutTemplate = await GetInternalAsync(layout, cultureName); + + emailTemplate.SetLayout(layoutTemplate); } - protected virtual Task LocalizeAsync(IServiceProvider serviceProvider, EmailTemplateProviderContributorContext context) + protected virtual Task LocalizeAsync(EmailTemplateDefinition emailTemplateDefinition, + EmailTemplate emailTemplate, string cultureName) { - if (context.Template.Definition.LocalizationResource == null) + if (emailTemplateDefinition.LocalizationResource == null) { return Task.CompletedTask; } - var localizer = serviceProvider - .GetRequiredService() - .Create(context.Template.Definition.LocalizationResource); - - context.Template.SetContent( - TemplateLocalizer.Localize(localizer, context.Template.Content) - ); + var localizer = StringLocalizerFactory.Create(emailTemplateDefinition.LocalizationResource); + if (cultureName != null) + { + emailTemplate.SetContent( + TemplateLocalizer.Localize(localizer.WithCulture(new CultureInfo(cultureName)), + emailTemplate.Content) + ); + } + else + { + emailTemplate.SetContent( + TemplateLocalizer.Localize(localizer, emailTemplate.Content) + ); + } return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProviderContributorContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProviderContributorContext.cs deleted file mode 100644 index 9893ddc789..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/EmailTemplateProviderContributorContext.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Emailing.Templates -{ - public class EmailTemplateProviderContributorContext : IServiceProviderAccessor - { - public string Name { get; } - - public IServiceProvider ServiceProvider { get; } - - public EmailTemplate Template { get; set; } - - public EmailTemplateProviderContributorContext(string name, IServiceProvider serviceProvider) - { - Name = name; - ServiceProvider = serviceProvider; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs new file mode 100644 index 0000000000..d2c2775845 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateContributor.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Emailing.Templates +{ + public interface IEmailTemplateContributor + { + void Initialize(EmailTemplateInitializationContext context); + + string GetOrNull(string cultureName); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs new file mode 100644 index 0000000000..1641562ccf --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionContext.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Emailing.Templates +{ + public interface IEmailTemplateDefinitionContext + { + EmailTemplateDefinition GetOrNull(string name); + + void Add(params EmailTemplateDefinition[] definitions); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs new file mode 100644 index 0000000000..0936a2fe93 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionManager.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.Emailing.Templates +{ + public interface IEmailTemplateDefinitionManager + { + [NotNull] + EmailTemplateDefinition Get([NotNull] string name); + + IReadOnlyList GetAll(); + + EmailTemplateDefinition GetOrNull(string name); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs new file mode 100644 index 0000000000..691d3874d6 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateDefinitionProvider.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Emailing.Templates +{ + public interface IEmailTemplateDefinitionProvider + { + void Define(IEmailTemplateDefinitionContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs index aad1fe4f05..4fc8381833 100644 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProvider.cs @@ -4,6 +4,6 @@ namespace Volo.Abp.Emailing.Templates { public interface IEmailTemplateProvider { - Task GetAsync(string name); + Task GetAsync(string name, string cultureName); } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProviderContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProviderContributor.cs deleted file mode 100644 index 6bc63f9661..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/IEmailTemplateProviderContributor.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.Emailing.Templates -{ - public interface IEmailTemplateProviderContributor - { - Task ProvideAsync(EmailTemplateProviderContributorContext contributorContext); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs new file mode 100644 index 0000000000..35ac14c8fd --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/ITemplateRender.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Emailing.Templates +{ + public interface ITemplateRender + { + Task RenderAsync(string template, object model = null); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs new file mode 100644 index 0000000000..8c4e24017c --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/TemplateRender.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using Scriban; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Emailing.Templates +{ + public class TemplateRender : ITemplateRender, ITransientDependency + { + public async Task RenderAsync(string template, object model = null) + { + var scribanTemplate = Template.Parse(template); + return await scribanTemplate.RenderAsync(model); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/EmailTemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/EmailTemplateDefinitionExtensions.cs deleted file mode 100644 index cb49491b0f..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/EmailTemplateDefinitionExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Volo.Abp.Emailing.Templates.Virtual -{ - public static class EmailTemplateDefinitionExtensions - { - public static EmailTemplateDefinition SetVirtualFilePath(this EmailTemplateDefinition emailTemplateDefinition, string path) - { - emailTemplateDefinition[VirtualFileEmailTemplateProviderContributor.VirtualFilePathKey] = path; - return emailTemplateDefinition; - } - - public static string GetVirtualFilePathOrNull(this EmailTemplateDefinition emailTemplateDefinition) - { - return emailTemplateDefinition[VirtualFileEmailTemplateProviderContributor.VirtualFilePathKey] as string; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/VirtualFileEmailTemplateOptions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/VirtualFileEmailTemplateOptions.cs deleted file mode 100644 index 0103ac27dc..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/VirtualFileEmailTemplateOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; - -namespace Volo.Abp.Emailing.Templates.Virtual -{ - public class VirtualFileEmailTemplateOptions - { - public IDictionary Templates { get; } - - public VirtualFileEmailTemplateOptions() - { - Templates = new Dictionary(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/VirtualFileEmailTemplateProviderContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/VirtualFileEmailTemplateProviderContributor.cs deleted file mode 100644 index d54f4c94cb..0000000000 --- a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Virtual/VirtualFileEmailTemplateProviderContributor.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Options; -using Volo.Abp.VirtualFileSystem; - -namespace Volo.Abp.Emailing.Templates.Virtual -{ - public class VirtualFileEmailTemplateProviderContributor : IEmailTemplateProviderContributor - { - public const string VirtualFilePathKey = "VirtualFilePath"; - - public Task ProvideAsync(EmailTemplateProviderContributorContext contributorContext) - { - var templateDefinition = FindTemplateDefinition(contributorContext); - if (templateDefinition == null) - { - return Task.CompletedTask; - } - - var fileInfo = FindVirtualFileInfo(contributorContext, templateDefinition); - if (fileInfo == null) - { - return Task.CompletedTask; - } - - contributorContext.Template = new EmailTemplate(fileInfo.ReadAsString(), templateDefinition); - return Task.CompletedTask; - } - - protected virtual EmailTemplateDefinition FindTemplateDefinition(EmailTemplateProviderContributorContext contributorContext) - { - return contributorContext - .ServiceProvider - .GetRequiredService>() - .Value - .Templates - .GetOrDefault(contributorContext.Name); - } - - protected virtual IFileInfo FindVirtualFileInfo(EmailTemplateProviderContributorContext contributorContext, EmailTemplateDefinition templateDefinition) - { - var virtualFilePath = templateDefinition?.GetVirtualFilePathOrNull(); - if (virtualFilePath == null) - { - return null; - } - - var virtualFileProvider = contributorContext.ServiceProvider.GetRequiredService(); - - var fileInfo = virtualFileProvider.GetFileInfo(virtualFilePath); - if (fileInfo?.Exists != true) - { - return null; - } - - return fileInfo; - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs new file mode 100644 index 0000000000..7bee611d28 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/EmailTemplateDefinitionExtensions.cs @@ -0,0 +1,19 @@ +namespace Volo.Abp.Emailing.Templates.VirtualFiles +{ + public static class EmailTemplateDefinitionExtensions + { + public static EmailTemplateDefinition AddTemplateVirtualFile( + this EmailTemplateDefinition emailTemplateDefinition, string path) + { + emailTemplateDefinition.Contributors.Add(new SingleVirtualFileEmailTemplateContributor(path)); + return emailTemplateDefinition; + } + + public static EmailTemplateDefinition AddTemplateVirtualFiles( + this EmailTemplateDefinition emailTemplateDefinition, string path) + { + emailTemplateDefinition.Contributors.Add(new MultipleVirtualFilesEmailTemplateContributor(path)); + return emailTemplateDefinition; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs new file mode 100644 index 0000000000..2ae5f88cc5 --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/MultipleVirtualFilesEmailTemplateContributor.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.Emailing.Templates.VirtualFiles +{ + public class MultipleVirtualFilesEmailTemplateContributor : IEmailTemplateContributor + { + private readonly string _virtualPath; + + private IVirtualFileProvider _virtualFileProvider; + + private Dictionary _templateDictionary; + + private readonly object _syncObj = new object(); + + public MultipleVirtualFilesEmailTemplateContributor(string virtualPath) + { + _virtualPath = virtualPath; + } + + public void Initialize(EmailTemplateInitializationContext context) + { + _virtualFileProvider = context.ServiceProvider.GetRequiredService(); + } + + public string GetOrNull(string cultureName) + { + return GetTemplateDictionary().GetOrDefault(cultureName); + } + + private Dictionary GetTemplateDictionary() + { + var dictionaries = _templateDictionary; + if (dictionaries != null) + { + return dictionaries; + } + + lock (_syncObj) + { + dictionaries = _templateDictionary; + if (dictionaries != null) + { + return dictionaries; + } + + _templateDictionary = new Dictionary(); + foreach (var file in _virtualFileProvider.GetDirectoryContents(_virtualPath)) + { + if (file.IsDirectory) + { + continue; + } + + // TODO: How to normalize file names? + _templateDictionary.Add(file.Name.RemovePostFix(".tpl"), file.ReadAsString()); + } + + dictionaries = _templateDictionary; + } + + return dictionaries; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs new file mode 100644 index 0000000000..d72d18e99a --- /dev/null +++ b/framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/VirtualFiles/SingleVirtualFileEmailTemplateContributor.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.Emailing.Templates.VirtualFiles +{ + public class SingleVirtualFileEmailTemplateContributor : IEmailTemplateContributor + { + private readonly string _virtualPath; + + private IVirtualFileProvider _virtualFileProvider; + + public SingleVirtualFileEmailTemplateContributor(string virtualPath) + { + _virtualPath = virtualPath; + } + + public void Initialize(EmailTemplateInitializationContext context) + { + _virtualFileProvider = context.ServiceProvider.GetRequiredService(); + } + + public string GetOrNull(string cultureName) + { + var file = _virtualFileProvider.GetFileInfo(_virtualPath); + if (file == null || !file.Exists || file.IsDirectory) + { + return null; + } + + return file.ReadAsString(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj index 5b33186509..2f5cdfb4fb 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo.Abp.EntityFrameworkCore.SqlServer.csproj @@ -18,7 +18,7 @@ - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj new file mode 100644 index 0000000000..cf6dad2b43 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo.Abp.EntityFrameworkCore.Sqlite.csproj @@ -0,0 +1,24 @@ + + + + + + netstandard2.0 + Volo.Abp.EntityFrameworkCore.Sqlite + Volo.Abp.EntityFrameworkCore.Sqlite + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqliteExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqliteExtensions.cs new file mode 100644 index 0000000000..6fd69fa42e --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextConfigurationContextSqliteExtensions.cs @@ -0,0 +1,25 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Volo.Abp.EntityFrameworkCore.DependencyInjection; + +namespace Volo.Abp.EntityFrameworkCore +{ + public static class AbpDbContextConfigurationContextSqliteExtensions + { + public static DbContextOptionsBuilder UseSqlite( + [NotNull] this AbpDbContextConfigurationContext context, + [CanBeNull] Action sqliteOptionsAction = null) + { + if (context.ExistingConnection != null) + { + return context.DbContextOptions.UseSqlite(context.ExistingConnection, sqliteOptionsAction); + } + else + { + return context.DbContextOptions.UseSqlite(context.ConnectionString, sqliteOptionsAction); + } + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqliteExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqliteExtensions.cs new file mode 100644 index 0000000000..68944194d1 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsSqliteExtensions.cs @@ -0,0 +1,30 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Volo.Abp.EntityFrameworkCore +{ + public static class AbpDbContextOptionsSqliteExtensions + { + public static void UseSqlite( + [NotNull] this AbpDbContextOptions options, + [CanBeNull] Action sqliteOptionsAction = null) + { + options.Configure(context => + { + context.UseSqlite(sqliteOptionsAction); + }); + } + + public static void UseSqlite( + [NotNull] this AbpDbContextOptions options, + [CanBeNull] Action sqliteOptionsAction = null) + where TDbContext : AbpDbContext + { + options.Configure(context => + { + context.UseSqlite(sqliteOptionsAction); + }); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs new file mode 100644 index 0000000000..8be6927e79 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.EntityFrameworkCore.Sqlite +{ + [DependsOn( + typeof(AbpEntityFrameworkCoreModule) + )] + public class AbpEntityFrameworkCoreSqliteModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj index 65dda178a5..bfbf0cdf49 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs index dcdcb404e8..fd7a3d495b 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using Volo.Abp.Auditing; using Volo.Abp.Data; using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.MultiTenancy; namespace Volo.Abp.EntityFrameworkCore.Modeling @@ -260,6 +261,15 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling } } + public static void ConfigureFullAuditedAggregateRoot(this EntityTypeBuilder b) + where T : class + { + b.As().TryConfigureFullAudited(); + b.As().TryConfigureExtraProperties(); + b.As().TryConfigureConcurrencyStamp(); + } + + //TODO: Add other interfaces (IAuditedObject...) } } diff --git a/framework/src/Volo.Abp.FluentValidation/Volo.Abp.FluentValidation.csproj b/framework/src/Volo.Abp.FluentValidation/Volo.Abp.FluentValidation.csproj new file mode 100644 index 0000000000..fe8a6a4192 --- /dev/null +++ b/framework/src/Volo.Abp.FluentValidation/Volo.Abp.FluentValidation.csproj @@ -0,0 +1,24 @@ + + + + + + netstandard2.0 + Volo.Abp.FluentValidation + Volo.Abp.FluentValidation + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/AbpFluentValidationConventionalRegistrar.cs b/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/AbpFluentValidationConventionalRegistrar.cs new file mode 100644 index 0000000000..9641b438b0 --- /dev/null +++ b/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/AbpFluentValidationConventionalRegistrar.cs @@ -0,0 +1,46 @@ +using System; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.FluentValidation +{ + public class AbpFluentValidationConventionalRegistrar : DefaultConventionalRegistrar + { + public override void AddType(IServiceCollection services, Type type) + { + if (!typeof(IValidator).IsAssignableFrom(type)) + { + return; + } + + var validatingType = GetFirstGenericArgumentOrNull(type, 1); + if (validatingType == null) + { + return; + } + + services.AddTransient( + typeof(IValidator<>).MakeGenericType(validatingType), + type + ); + } + + private static Type GetFirstGenericArgumentOrNull(Type type, int depth) + { + const int maxFindDepth = 8; + + if (depth >= maxFindDepth) + { + return null; + } + + if (type.IsGenericType && type.GetGenericArguments().Length >= 1) + { + return type.GetGenericArguments()[0]; + } + + return GetFirstGenericArgumentOrNull(type.BaseType, depth + 1); + } + } +} diff --git a/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/AbpFluentValidationModule.cs b/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/AbpFluentValidationModule.cs new file mode 100644 index 0000000000..ffe6162197 --- /dev/null +++ b/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/AbpFluentValidationModule.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.Validation; + +namespace Volo.Abp.FluentValidation +{ + [DependsOn( + typeof(AbpValidationModule) + )] + public class AbpFluentValidationModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddConventionalRegistrar(new AbpFluentValidationConventionalRegistrar()); + } + } +} diff --git a/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs b/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs new file mode 100644 index 0000000000..f926782e2d --- /dev/null +++ b/framework/src/Volo.Abp.FluentValidation/Volo/Abp/FluentValidation/FluentObjectValidationContributor.cs @@ -0,0 +1,41 @@ +using FluentValidation; +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Validation; + +namespace Volo.Abp.FluentValidation +{ + public class FluentObjectValidationContributor : IObjectValidationContributor, ITransientDependency + { + private readonly IServiceProvider _serviceProvider; + + public FluentObjectValidationContributor( + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public void AddErrors(ObjectValidationContext context) + { + var serviceType = typeof(IValidator<>).MakeGenericType(context.ValidatingObject.GetType()); + var validator = _serviceProvider.GetService(serviceType) as IValidator; + if (validator == null) + { + return; + } + + var result = validator.Validate(context.ValidatingObject); + if (!result.IsValid) + { + context.Errors.AddRange( + result.Errors.Select( + error => + new ValidationResult(error.ErrorMessage) + ) + ); + } + } + } +} diff --git a/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj b/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj index e418ca03e5..e4b9db1c7b 100644 --- a/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj +++ b/framework/src/Volo.Abp.HangFire/Volo.Abp.HangFire.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs index e7b41784c7..91d1c4cb9b 100644 --- a/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs +++ b/framework/src/Volo.Abp.Http.Client.IdentityModel/Volo/Abp/Http/Client/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs @@ -13,11 +13,12 @@ namespace Volo.Abp.Http.Client.IdentityModel { public IHttpContextAccessor HttpContextAccessor { get; set; } - protected IIdentityModelHttpClientAuthenticator IdentityModelHttpClientAuthenticator { get; } + protected IIdentityModelAuthenticationService IdentityModelAuthenticationService { get; } - public IdentityModelRemoteServiceHttpClientAuthenticator(IIdentityModelHttpClientAuthenticator identityModelHttpClientAuthenticator) + public IdentityModelRemoteServiceHttpClientAuthenticator( + IIdentityModelAuthenticationService identityModelAuthenticationService) { - IdentityModelHttpClientAuthenticator = identityModelHttpClientAuthenticator; + IdentityModelAuthenticationService = identityModelAuthenticationService; } public async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context) @@ -32,7 +33,7 @@ namespace Volo.Abp.Http.Client.IdentityModel } } - await IdentityModelHttpClientAuthenticator.AuthenticateAsync( + await IdentityModelAuthenticationService.TryAuthenticateAsync( context.Client, context.RemoteService.GetIdentityClient() ); diff --git a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj index d16fac5ba0..fcfff3fcb5 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj +++ b/framework/src/Volo.Abp.IdentityModel/Volo.Abp.IdentityModel.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IIdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IIdentityModelAuthenticationService.cs new file mode 100644 index 0000000000..4acfac2ad7 --- /dev/null +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IIdentityModelAuthenticationService.cs @@ -0,0 +1,17 @@ +using System.Net.Http; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.IdentityModel +{ + //TODO: Re-consider this interface! + public interface IIdentityModelAuthenticationService + { + Task TryAuthenticateAsync( + [NotNull] HttpClient client, + string identityClientName = null); + + Task GetAccessTokenAsync( + IdentityClientConfiguration configuration); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IIdentityModelHttpClientAuthenticator.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IIdentityModelHttpClientAuthenticator.cs deleted file mode 100644 index 09a16735ff..0000000000 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IIdentityModelHttpClientAuthenticator.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Volo.Abp.IdentityModel -{ - public interface IIdentityModelHttpClientAuthenticator - { - Task AuthenticateAsync(IdentityModelHttpClientAuthenticateContext context); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs index 9e891ccafa..35ca881f7f 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityClientConfiguration.cs @@ -77,12 +77,16 @@ namespace Volo.Abp.IdentityModel } public IdentityClientConfiguration( + string authority, + string scope, string clientId, string clientSecret, string grantType = OidcConstants.GrantTypes.ClientCredentials, string userName = null, string userPassword = null) { + this[nameof(Authority)] = authority; + this[nameof(Scope)] = scope; this[nameof(ClientId)] = clientId; this[nameof(ClientSecret)] = clientSecret; this[nameof(GrantType)] = grantType; diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs new file mode 100644 index 0000000000..4f316ec357 --- /dev/null +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs @@ -0,0 +1,118 @@ +using IdentityModel; +using IdentityModel.Client; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.IdentityModel +{ + [Dependency(ReplaceServices = true)] + public class IdentityModelAuthenticationService : IIdentityModelAuthenticationService, ITransientDependency + { + public ILogger Logger { get; set; } + protected IdentityClientOptions ClientOptions { get; } + + public IdentityModelAuthenticationService( + IOptions options) + { + ClientOptions = options.Value; + Logger = NullLogger.Instance; + } + + public async Task TryAuthenticateAsync( + [NotNull] HttpClient client, + string identityClientName = null) + { + var accessToken = await GetAccessTokenOrNullAsync(identityClientName); + if (accessToken == null) + { + return false; + } + + SetAccessToken(client, accessToken); + return true; + + } + + protected virtual async Task GetAccessTokenOrNullAsync(string identityClientName) + { + var configuration = GetClientConfiguration(identityClientName); + if (configuration == null) + { + Logger.LogWarning($"Could not find {nameof(IdentityClientConfiguration)} for {identityClientName}. Either define a configuration for {identityClientName} or set a default configuration."); + return null; + } + + return await GetAccessTokenAsync(configuration); + } + + public virtual async Task GetAccessTokenAsync(IdentityClientConfiguration configuration) + { + var discoveryResponse = await GetDiscoveryResponse(configuration); + if (discoveryResponse.IsError) + { + throw new AbpException($"Could not retrieve the OpenId Connect discovery document! ErrorType: {discoveryResponse.ErrorType}. Error: {discoveryResponse.Error}"); + } + + var tokenResponse = await GetTokenResponse(discoveryResponse, configuration); + if (tokenResponse.IsError) + { + throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}"); + } + + return tokenResponse.AccessToken; + } + + protected virtual void SetAccessToken(HttpClient client, string accessToken) + { + //TODO: "Bearer" should be configurable + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + } + + private IdentityClientConfiguration GetClientConfiguration(string identityClientName = null) + { + if (identityClientName.IsNullOrEmpty()) + { + return ClientOptions.IdentityClients.Default; + } + + return ClientOptions.IdentityClients.GetOrDefault(identityClientName) ?? + ClientOptions.IdentityClients.Default; + } + + protected virtual async Task GetDiscoveryResponse(IdentityClientConfiguration configuration) + { + return await DiscoveryClient.GetAsync(configuration.Authority); + } + + protected virtual async Task GetTokenResponse(DiscoveryResponse discoveryResponse, IdentityClientConfiguration configuration) + { + //TODO: Pass cancellation token + + var tokenClient = new TokenClient(discoveryResponse.TokenEndpoint, configuration.ClientId, configuration.ClientSecret); + + switch (configuration.GrantType) + { + case OidcConstants.GrantTypes.ClientCredentials: + return await tokenClient.RequestClientCredentialsAsync( + configuration.Scope + ); + case OidcConstants.GrantTypes.Password: + return await tokenClient.RequestResourceOwnerPasswordAsync( + configuration.UserName, + configuration.UserPassword, + configuration.Scope + ); + default: + throw new AbpException("Grant type was not implemented: " + configuration.GrantType); + } + } + } +} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelHttpClientAuthenticateContext.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelHttpClientAuthenticateContext.cs deleted file mode 100644 index 441129aee7..0000000000 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelHttpClientAuthenticateContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Net.Http; -using JetBrains.Annotations; - -namespace Volo.Abp.IdentityModel -{ - public class IdentityModelHttpClientAuthenticateContext - { - public HttpClient Client { get; } - - /// - /// The identity client name configured with the . - /// - public string IdentityClientName { get; } - - /// object to be authenticated - /// The identity client name configured with the . - public IdentityModelHttpClientAuthenticateContext( - [NotNull] HttpClient client, - string identityClientName = null) - { - Client = Check.NotNull(client, nameof(client)); - IdentityClientName = identityClientName; - } - } -} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelHttpClientAuthenticatorExtensions.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelHttpClientAuthenticatorExtensions.cs deleted file mode 100644 index c40b0d8398..0000000000 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelHttpClientAuthenticatorExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Net.Http; -using System.Threading.Tasks; -using JetBrains.Annotations; - -namespace Volo.Abp.IdentityModel -{ - public static class IdentityModelHttpClientAuthenticatorExtensions - { - /// Authenticator object - /// object to be authenticated - /// The identity client name configured with the . - public static Task AuthenticateAsync( - [NotNull] this IIdentityModelHttpClientAuthenticator authenticator, - [NotNull] HttpClient client, - string identityClientName = null) - { - Check.NotNull(authenticator, nameof(authenticator)); - - return authenticator.AuthenticateAsync( - new IdentityModelHttpClientAuthenticateContext( - client, - identityClientName - ) - ); - } - } -} diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs deleted file mode 100644 index ad956aee1e..0000000000 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelRemoteServiceHttpClientAuthenticator.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using IdentityModel; -using IdentityModel.Client; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.IdentityModel -{ - [Dependency(ReplaceServices = true)] - public class IdentityModelHttpClientAuthenticator : IIdentityModelHttpClientAuthenticator, ITransientDependency - { - public ILogger Logger { get; set; } - protected IdentityClientOptions ClientOptions { get; } - - public IdentityModelHttpClientAuthenticator( - IOptions options) - { - ClientOptions = options.Value; - Logger = NullLogger.Instance; - } - - public async Task AuthenticateAsync(IdentityModelHttpClientAuthenticateContext context) - { - var accessToken = await GetAccessTokenFromServerOrNullAsync(context); - - if (accessToken != null) - { - //TODO: "Bearer" should be configurable - context.Client.DefaultRequestHeaders.Authorization - = new AuthenticationHeaderValue("Bearer", accessToken); - } - } - - protected virtual async Task GetAccessTokenFromServerOrNullAsync(IdentityModelHttpClientAuthenticateContext context) - { - //TODO: Better logging - - var configuration = GetClientConfiguration(context); - - if (configuration == null) - { - Logger.LogWarning($"Could not find {nameof(IdentityClientConfiguration)} for {context.IdentityClientName}. Either define a configuration for {context.IdentityClientName} or set a default configuration."); - return null; - } - - var discoveryResponse = await GetDiscoveryResponse(configuration); - if (discoveryResponse.IsError) - { - Logger.LogError($"Could not retrieve the OpenId Connect discovery document! ErrorType: {discoveryResponse.ErrorType}. Error: {discoveryResponse.Error}"); - return null; - } - - var tokenResponse = await GetTokenResponse(discoveryResponse, configuration); - if (tokenResponse.IsError) - { - Logger.LogError($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}"); - return null; - } - - return tokenResponse.AccessToken; - } - - private IdentityClientConfiguration GetClientConfiguration(IdentityModelHttpClientAuthenticateContext context) - { - if (context.IdentityClientName.IsNullOrEmpty()) - { - return ClientOptions.IdentityClients.Default; - } - - return ClientOptions.IdentityClients.GetOrDefault(context.IdentityClientName) ?? - ClientOptions.IdentityClients.Default; - } - - protected virtual async Task GetDiscoveryResponse(IdentityClientConfiguration configuration) - { - return await DiscoveryClient.GetAsync(configuration.Authority); - } - - protected virtual async Task GetTokenResponse(DiscoveryResponse discoveryResponse, IdentityClientConfiguration configuration) - { - //TODO: Pass cancellation token - - var tokenClient = new TokenClient(discoveryResponse.TokenEndpoint, configuration.ClientId, configuration.ClientSecret); - - switch (configuration.GrantType) - { - case OidcConstants.GrantTypes.ClientCredentials: - return await tokenClient.RequestClientCredentialsAsync( - configuration.Scope - ); - case OidcConstants.GrantTypes.Password: - return await tokenClient.RequestResourceOwnerPasswordAsync( - configuration.UserName, - configuration.UserPassword, - configuration.Scope - ); - default: - throw new AbpException("Grant type was not implemented: " + configuration.GrantType); - } - } - } -} diff --git a/framework/src/Volo.Abp.Json/Volo.Abp.Json.csproj b/framework/src/Volo.Abp.Json/Volo.Abp.Json.csproj index 39b92cc428..8964f2f4fe 100644 --- a/framework/src/Volo.Abp.Json/Volo.Abp.Json.csproj +++ b/framework/src/Volo.Abp.Json/Volo.Abp.Json.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj index b429defa2d..65ceed101e 100644 --- a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj +++ b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj @@ -18,7 +18,7 @@ - + diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDatabase.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDatabase.cs index 1e6cd6f54c..9f2bf33a40 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDatabase.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDatabase.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +using Volo.Abp.Domain.Entities; namespace Volo.Abp.Domain.Repositories.MemoryDb { public interface IMemoryDatabase { - List Collection(); + IMemoryDatabaseCollection Collection() where TEntity : class, IEntity; TKey GenerateNextId(); } diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDatabaseCollection.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDatabaseCollection.cs new file mode 100644 index 0000000000..a2f4d5f270 --- /dev/null +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDatabaseCollection.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Domain.Repositories.MemoryDb +{ + public interface IMemoryDatabaseCollection : IEnumerable + { + void Add(TEntity entity); + + void Update(TEntity entity); + + void Remove(TEntity entity); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDbRepository.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDbRepository.cs index dd241b25a5..c179b68931 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDbRepository.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDbRepository.cs @@ -8,7 +8,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb { IMemoryDatabase Database { get; } - List Collection { get; } + IMemoryDatabaseCollection Collection { get; } } public interface IMemoryDbRepository : IMemoryDbRepository, IRepository diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDbSerializer.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDbSerializer.cs new file mode 100644 index 0000000000..2854a7893f --- /dev/null +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/IMemoryDbSerializer.cs @@ -0,0 +1,11 @@ +using System; + +namespace Volo.Abp.Domain.Repositories.MemoryDb +{ + public interface IMemoryDbSerializer + { + byte[] Serialize(object obj); + + object Deserialize(byte[] value, Type type); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabase.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabase.cs index be16beef85..6c2aa1417c 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabase.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabase.cs @@ -1,24 +1,33 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Entities; namespace Volo.Abp.Domain.Repositories.MemoryDb { - public class MemoryDatabase : IMemoryDatabase + public class MemoryDatabase : IMemoryDatabase, ITransientDependency { private readonly ConcurrentDictionary _sets; private readonly ConcurrentDictionary _entityIdGenerators; - public MemoryDatabase() + private readonly IServiceProvider _serviceProvider; + + public MemoryDatabase(IServiceProvider serviceProvider) { + _serviceProvider = serviceProvider; _sets = new ConcurrentDictionary(); _entityIdGenerators = new ConcurrentDictionary(); } - public List Collection() + public IMemoryDatabaseCollection Collection() + where TEntity : class, IEntity { - return _sets.GetOrAdd(typeof(TEntity), _ => new List()) as List; + return _sets.GetOrAdd(typeof(TEntity), + _ => _serviceProvider.GetRequiredService>()) as + IMemoryDatabaseCollection; } public TKey GenerateNextId() diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs new file mode 100644 index 0000000000..11c7f992a9 --- /dev/null +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.Domain.Repositories.MemoryDb +{ + public class MemoryDatabaseCollection : IMemoryDatabaseCollection + where TEntity : class, IEntity + { + private readonly Dictionary _dictionary = new Dictionary(); + + private readonly IMemoryDbSerializer _memoryDbSerializer; + + public MemoryDatabaseCollection(IMemoryDbSerializer memoryDbSerializer) + { + _memoryDbSerializer = memoryDbSerializer; + } + + public IEnumerator GetEnumerator() + { + foreach (var entity in _dictionary.Values) + { + yield return _memoryDbSerializer.Deserialize(entity, typeof(TEntity)).As(); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Add(TEntity entity) + { + _dictionary.Add(GetEntityKey(entity), _memoryDbSerializer.Serialize(entity)); + } + + public void Update(TEntity entity) + { + if (_dictionary.ContainsKey(GetEntityKey(entity))) + { + _dictionary[GetEntityKey(entity)] = _memoryDbSerializer.Serialize(entity); + } + } + + public void Remove(TEntity entity) + { + _dictionary.Remove(GetEntityKey(entity)); + } + + private string GetEntityKey(TEntity entity) + { + return entity.GetKeys().JoinAsString(","); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseManager.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseManager.cs index 4926abebf3..7507f3d625 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseManager.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseManager.cs @@ -1,19 +1,25 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; namespace Volo.Abp.Domain.Repositories.MemoryDb { - public static class MemoryDatabaseManager + public class MemoryDatabaseManager : ISingletonDependency { - private static ConcurrentDictionary _databases; + private readonly ConcurrentDictionary _databases = + new ConcurrentDictionary(); - static MemoryDatabaseManager() + private readonly IServiceProvider _serviceProvider; + + public MemoryDatabaseManager(IServiceProvider serviceProvider) { - _databases = new ConcurrentDictionary(); + _serviceProvider = serviceProvider; } - public static IMemoryDatabase Get(string databaseName) + public IMemoryDatabase Get(string databaseName) { - return _databases.GetOrAdd(databaseName, _ => new MemoryDatabase()); + return _databases.GetOrAdd(databaseName, _ => _serviceProvider.GetRequiredService()); } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs index c658a34ef4..0f82aa798b 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs @@ -14,7 +14,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb { //TODO: Add dbcontext just like mongodb implementation! - public virtual List Collection => Database.Collection(); + public virtual IMemoryDatabaseCollection Collection => Database.Collection(); public virtual IMemoryDatabase Database => DatabaseProvider.GetDatabase(); @@ -33,6 +33,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb public override TEntity Update(TEntity entity, bool autoSave = false) { + Collection.Update(entity); return entity; } @@ -48,7 +49,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb public override long GetCount() { - return Collection.Count; + return Collection.Count(); } protected override IQueryable GetQueryable() diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializer.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializer.cs new file mode 100644 index 0000000000..9964e6219b --- /dev/null +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/Utf8JsonMemoryDbSerializer.cs @@ -0,0 +1,27 @@ +using System; +using System.Text; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace Volo.Abp.Domain.Repositories.MemoryDb +{ + public class Utf8JsonMemoryDbSerializer : IMemoryDbSerializer, ITransientDependency + { + private readonly IJsonSerializer _jsonSerializer; + + public Utf8JsonMemoryDbSerializer(IJsonSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + } + + byte[] IMemoryDbSerializer.Serialize(object obj) + { + return Encoding.UTF8.GetBytes(_jsonSerializer.Serialize(obj)); + } + + public object Deserialize(byte[] value, Type type) + { + return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDbCoreRepositoryExtensions.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDbCoreRepositoryExtensions.cs index 6f940ca146..1547a581e5 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDbCoreRepositoryExtensions.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDbCoreRepositoryExtensions.cs @@ -13,7 +13,7 @@ namespace Volo.Abp.Domain.Repositories return repository.ToMemoryDbRepository().Database; } - public static List GetCollection(this IBasicRepository repository) + public static IMemoryDatabaseCollection GetCollection(this IBasicRepository repository) where TEntity : class, IEntity { return repository.ToMemoryDbRepository().Collection; diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/MemoryDb/AbpMemoryDbModule.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/MemoryDb/AbpMemoryDbModule.cs index d4f679a5b7..bd8b4b98a4 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/MemoryDb/AbpMemoryDbModule.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/MemoryDb/AbpMemoryDbModule.cs @@ -1,19 +1,19 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Domain; +using Volo.Abp.Domain.Repositories.MemoryDb; +using Volo.Abp.Json; using Volo.Abp.Modularity; using Volo.Abp.Uow.MemoryDb; namespace Volo.Abp.MemoryDb { - /* TODO: Consider to store objects as binary serialized in the memory, which makes unit tests more realistic. - */ - [DependsOn(typeof(AbpDddDomainModule))] public class AbpMemoryDbModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.TryAddTransient(typeof(IMemoryDatabaseProvider<>), typeof(UnitOfWorkMemoryDatabaseProvider<>)); + context.Services.TryAddTransient(typeof(IMemoryDatabaseCollection<>), typeof(MemoryDatabaseCollection<>)); } } } diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs index 386ec77b12..24c1cdaeb9 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Uow/MemoryDb/UnitOfWorkMemoryDatabaseProvider.cs @@ -11,15 +11,18 @@ namespace Volo.Abp.Uow.MemoryDb private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IConnectionStringResolver _connectionStringResolver; + private readonly MemoryDatabaseManager _memoryDatabaseManager; public UnitOfWorkMemoryDatabaseProvider( IUnitOfWorkManager unitOfWorkManager, IConnectionStringResolver connectionStringResolver, - TMemoryDbContext dbContext) + TMemoryDbContext dbContext, + MemoryDatabaseManager memoryDatabaseManager) { _unitOfWorkManager = unitOfWorkManager; _connectionStringResolver = connectionStringResolver; DbContext = dbContext; + _memoryDatabaseManager = memoryDatabaseManager; } public IMemoryDatabase GetDatabase() @@ -36,7 +39,7 @@ namespace Volo.Abp.Uow.MemoryDb var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, () => new MemoryDbDatabaseApi( - MemoryDatabaseManager.Get(connectionString) + _memoryDatabaseManager.Get(connectionString) )); return ((MemoryDbDatabaseApi)databaseApi).Database; diff --git a/framework/src/Volo.Abp.MongoDB/Volo.Abp.MongoDB.csproj b/framework/src/Volo.Abp.MongoDB/Volo.Abp.MongoDB.csproj index f2f59a9a64..9492e6fbf3 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo.Abp.MongoDB.csproj +++ b/framework/src/Volo.Abp.MongoDB/Volo.Abp.MongoDB.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs index 991db17cef..50155c6df5 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/IMongoDbRepository.cs @@ -1,4 +1,5 @@ using MongoDB.Driver; +using MongoDB.Driver.Linq; using Volo.Abp.Domain.Entities; namespace Volo.Abp.Domain.Repositories.MongoDB @@ -9,6 +10,8 @@ namespace Volo.Abp.Domain.Repositories.MongoDB IMongoDatabase Database { get; } IMongoCollection Collection { get; } + + IMongoQueryable GetMongoQueryable(); } public interface IMongoDbRepository : IMongoDbRepository, IRepository diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index 194f604847..2e993ebecb 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -263,12 +263,10 @@ namespace Volo.Abp.Domain.Repositories.MongoDB protected override IQueryable GetQueryable() { - return ApplyDataFilters( - Collection.AsQueryable() - ); + return GetMongoQueryable(); } - protected virtual IMongoQueryable GetMongoQueryable() + public virtual IMongoQueryable GetMongoQueryable() { return ApplyDataFilters( Collection.AsQueryable() diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs index b33a8d8444..12da0afaa2 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDbCoreRepositoryExtensions.cs @@ -1,5 +1,6 @@ using System; using MongoDB.Driver; +using MongoDB.Driver.Linq; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories.MongoDB; @@ -19,6 +20,12 @@ namespace Volo.Abp.Domain.Repositories return repository.ToMongoDbRepository().Collection; } + public static IMongoQueryable GetMongoQueryable(this IBasicRepository repository) + where TEntity : class, IEntity + { + return repository.ToMongoDbRepository().GetMongoQueryable(); + } + public static IMongoDbRepository ToMongoDbRepository(this IBasicRepository repository) where TEntity : class, IEntity { diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpBsonClassMapExtensions.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpBsonClassMapExtensions.cs index f53e524914..a450a4bc43 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpBsonClassMapExtensions.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpBsonClassMapExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using MongoDB.Bson.Serialization; using Volo.Abp.Data; @@ -9,34 +10,50 @@ namespace Volo.Abp.MongoDB public static void ConfigureAbpConventions(this BsonClassMap map) { map.AutoMap(); - map.ConfigureExtraProperties(); + map.TryConfigureExtraProperties(); } - public static void ConfigureExtraProperties(this BsonClassMap map) + public static bool TryConfigureExtraProperties(this BsonClassMap map) where TEntity : class, IHasExtraProperties { + var property = map.ClassType.GetProperty( + nameof(IHasExtraProperties.ExtraProperties), + BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty + ); + + if (property?.DeclaringType != map.ClassType) + { + return false; + } + map.SetExtraElementsMember(new BsonMemberMap( map, typeof(TEntity).GetMember(nameof(IHasExtraProperties.ExtraProperties))[0]) ); + + return true; } - /// - /// Configures SetExtraElementsMember if the - /// implements the interface. - /// Otherwise, does nothing - /// - public static void ConfigureExtraProperties(this BsonClassMap map) + public static bool TryConfigureExtraProperties(this BsonClassMap map) { - if (map.ClassType.IsAssignableTo()) + if (!map.ClassType.IsAssignableTo()) { - map.SetExtraElementsMember( - new BsonMemberMap( - map, - map.ClassType.GetMember(nameof(IHasExtraProperties.ExtraProperties))[0] - ) - ); + return false; } + + var property = map.ClassType.GetProperty( + nameof(IHasExtraProperties.ExtraProperties), + BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty + ); + + if (property?.DeclaringType != map.ClassType) + { + return false; + } + + map.MapExtraElementsMember(property); + + return true; } } } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs index 788d0bdbea..43ffbabf4a 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Volo.Abp.Domain.Entities; namespace Volo.Abp.MongoDB { @@ -19,24 +20,40 @@ namespace Volo.Abp.MongoDB public MongoDbContextModel Build() { - var entityModels = _entityModelBuilders - .Select(x => x.Value) - .Cast() - .ToImmutableDictionary(x => x.EntityType, x => x); - - foreach (var entityModel in entityModels.Values) + lock (SyncObj) { - var map = entityModel.As().GetMap(); - lock (SyncObj) + var entityModels = _entityModelBuilders + .Select(x => x.Value) + .Cast() + .ToImmutableDictionary(x => x.EntityType, x => x); + + var baseClasses = new List(); + + foreach (var entityModel in entityModels.Values) { + var map = entityModel.As().GetMap(); if (!BsonClassMap.IsClassMapRegistered(map.ClassType)) { BsonClassMap.RegisterClassMap(map); } + + baseClasses.AddRange(entityModel.EntityType.GetBaseClasses(includeObject: false)); } - } - return new MongoDbContextModel(entityModels); + baseClasses = baseClasses.Distinct().ToList(); + + foreach (var baseClass in baseClasses) + { + if (!BsonClassMap.IsClassMapRegistered(baseClass)) + { + var map = new BsonClassMap(baseClass); + map.ConfigureAbpConventions(); + BsonClassMap.RegisterClassMap(map); + } + } + + return new MongoDbContextModel(entityModels); + } } public virtual void Entity(Action> buildAction = null) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionOptions.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionOptions.cs index 91768d37e8..67eae167c3 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionOptions.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Encryption/StringEncryptionOptions.cs @@ -15,7 +15,7 @@ namespace Volo.Abp.Security.Encryption /// /// Default password to encrypt/decrypt texts. - /// It's recommented to set to another value for security. + /// It's recommended to set to another value for security. /// Default value: "gsKnGZ041HLL4IM8" /// public string DefaultPassPhrase { get; set; } 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.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/zh-Hans.json b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/zh-Hans.json new file mode 100644 index 0000000000..bfac148af4 --- /dev/null +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/Localization/Resource/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hans", + "texts": { + "Menu:Administration": "管理" + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json index 54c573857a..67d6fa7d3c 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/pt-BR.json @@ -45,6 +45,7 @@ "PagerShowMenuEntries": "Mostrando _MENU_ registros", "DatatableActionDropdownDefaultText": "Ações", "ChangePassword": "Alterar Senha", - "PersonalInfo": "Perfil" + "PersonalInfo": "Perfil", + "AreYouSureYouWantToCancelEditingWarningMessage": "Você tem alterações não salvas." } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json index f1814efdb3..8747d1dc8d 100644 --- a/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json +++ b/framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/zh-Hans.json @@ -45,6 +45,7 @@ "PagerShowMenuEntries": "显示 _MENU_ 实体", "DatatableActionDropdownDefaultText": "操作", "ChangePassword": "修改密码", - "PersonalInfo": "个人信息" + "PersonalInfo": "个人信息", + "AreYouSureYouWantToCancelEditingWarningMessage": "你有未保存的更改." } } diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs index 4218a9693f..df867c2320 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs @@ -9,7 +9,7 @@ namespace Volo.Abp.Uow public class UnitOfWorkFailedEventArgs : UnitOfWorkEventArgs { /// - /// Exception that caused failure. This is set only if an error occured during . + /// Exception that caused failure. This is set only if an error occurred during . /// Can be null if there is no exception, but is not called. /// Can be null if another exception occurred during the UOW. /// diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs index 03efe9d66a..bb2d41f927 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationModule.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; namespace Volo.Abp.Validation @@ -8,6 +10,25 @@ namespace Volo.Abp.Validation public override void PreConfigureServices(ServiceConfigurationContext context) { context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded); + AutoAddObjectValidationContributors(context.Services); + } + + private static void AutoAddObjectValidationContributors(IServiceCollection services) + { + var contributorTypes = new List(); + + services.OnRegistred(context => + { + if (typeof(IObjectValidationContributor).IsAssignableFrom(context.ImplementationType)) + { + contributorTypes.Add(context.ImplementationType); + } + }); + + services.Configure(options => + { + options.ObjectValidationContributors.AddIfNotContains(contributorTypes); + }); } } } diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationOptions.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationOptions.cs new file mode 100644 index 0000000000..d9a690181e --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/AbpValidationOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Collections; + +namespace Volo.Abp.Validation +{ + public class AbpValidationOptions + { + public List IgnoredTypes { get; } + + public ITypeList ObjectValidationContributors { get; set; } + + public AbpValidationOptions() + { + IgnoredTypes = new List(); + ObjectValidationContributors = new TypeList(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs new file mode 100644 index 0000000000..5598fe7827 --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationObjectValidationContributor.cs @@ -0,0 +1,122 @@ +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Validation +{ + public class DataAnnotationObjectValidationContributor : IObjectValidationContributor, ITransientDependency + { + public const int MaxRecursiveParameterValidationDepth = 8; + + protected AbpValidationOptions Options { get; } + + public DataAnnotationObjectValidationContributor(IOptions options) + { + Options = options.Value; + } + + public void AddErrors(ObjectValidationContext context) + { + ValidateObjectRecursively(context.Errors, context.ValidatingObject, currentDepth: 1); + } + + protected virtual void ValidateObjectRecursively(List errors, object validatingObject, int currentDepth) + { + if (currentDepth > MaxRecursiveParameterValidationDepth) + { + return; + } + + if (validatingObject == null) + { + return; + } + + AddErrors(errors, validatingObject); + + //Validate items of enumerable + if (validatingObject is IEnumerable) + { + if (!(validatingObject is IQueryable)) + { + foreach (var item in (validatingObject as IEnumerable)) + { + ValidateObjectRecursively(errors, item, currentDepth + 1); + } + } + + return; + } + + var validatingObjectType = validatingObject.GetType(); + + //Do not recursively validate for primitive objects + if (TypeHelper.IsPrimitiveExtended(validatingObjectType)) + { + return; + } + + if (Options.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject))) + { + return; + } + + var properties = TypeDescriptor.GetProperties(validatingObject).Cast(); + foreach (var property in properties) + { + if (property.Attributes.OfType().Any()) + { + continue; + } + + ValidateObjectRecursively(errors, property.GetValue(validatingObject), currentDepth + 1); + } + } + + public void AddErrors(List errors, object validatingObject) + { + var properties = TypeDescriptor.GetProperties(validatingObject).Cast(); + + foreach (var property in properties) + { + AddPropertyErrors(validatingObject, property, errors); + } + + if (validatingObject is IValidatableObject validatableObject) + { + errors.AddRange( + validatableObject.Validate(new ValidationContext(validatableObject)) + ); + } + } + + protected virtual void AddPropertyErrors(object validatingObject, PropertyDescriptor property, List errors) + { + var validationAttributes = property.Attributes.OfType().ToArray(); + if (validationAttributes.IsNullOrEmpty()) + { + return; + } + + var validationContext = new ValidationContext(validatingObject) + { + DisplayName = property.DisplayName, + MemberName = property.Name + }; + + foreach (var attribute in validationAttributes) + { + var result = attribute.GetValidationResult(property.GetValue(validatingObject), validationContext); + if (result != null) + { + errors.Add(result); + } + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationValidator.cs deleted file mode 100644 index 3333354937..0000000000 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/DataAnnotationValidator.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.Validation -{ - public class DataAnnotationValidator : IDataAnnotationValidator, ITransientDependency - { - public void Validate(object validatingObject) - { - var errors = GetErrors(validatingObject); - - if (errors.Any()) - { - throw new AbpValidationException( - "Object state is not valid! See ValidationErrors for details.", - errors - ); - } - } - - /// - /// Gets all errors from properties for DataAnnotations attributes and IValidatableObject interface. - /// - public virtual List GetErrors(object validatingObject) - { - var errors = new List(); - var properties = TypeDescriptor.GetProperties(validatingObject).Cast(); - - foreach (var property in properties) - { - AddPropertyErrors(validatingObject, property, errors); - } - - if (validatingObject is IValidatableObject validatableObject) - { - errors.AddRange( - validatableObject.Validate(new ValidationContext(validatableObject)) - ); - } - - return errors; - } - - protected virtual void AddPropertyErrors(object validatingObject, PropertyDescriptor property, List errors) - { - var validationAttributes = property.Attributes.OfType().ToArray(); - if (validationAttributes.IsNullOrEmpty()) - { - return; - } - - var validationContext = new ValidationContext(validatingObject) - { - DisplayName = property.DisplayName, - MemberName = property.Name - }; - - foreach (var attribute in validationAttributes) - { - var result = attribute.GetValidationResult(property.GetValue(validatingObject), validationContext); - if (result != null) - { - errors.Add(result); - } - } - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IDataAnnotationValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IDataAnnotationValidator.cs deleted file mode 100644 index 6b14ff66b6..0000000000 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IDataAnnotationValidator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Volo.Abp.Validation -{ - public interface IDataAnnotationValidator - { - void Validate(object validatingObject); - - List GetErrors(object validatingObject); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IMethodInvocationValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IMethodInvocationValidator.cs index 218ad9313d..fd413bc498 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IMethodInvocationValidator.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IMethodInvocationValidator.cs @@ -1,7 +1,7 @@ -namespace Volo.Abp.Validation +namespace Volo.Abp.Validation { public interface IMethodInvocationValidator { void Validate(MethodInvocationValidationContext context); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs new file mode 100644 index 0000000000..ca50901bcd --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidationContributor.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Validation +{ + public interface IObjectValidationContributor + { + void AddErrors(ObjectValidationContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs index 8388605e62..0ce723bab8 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IObjectValidator.cs @@ -11,6 +11,10 @@ namespace Volo.Abp.Validation bool allowNull = false ); - List GetErrors(object validatingObject, string name = null, bool allowNull = false); + List GetErrors( + object validatingObject, + string name = null, + bool allowNull = false + ); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IValidationConfiguration.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IValidationConfiguration.cs deleted file mode 100644 index 7040be55bc..0000000000 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/IValidationConfiguration.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Volo.Abp.Validation -{ - public class AbpValidationOptions - { - public List IgnoredTypes { get; } - - public AbpValidationOptions() - { - IgnoredTypes = new List(); - } - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidationContext.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidationContext.cs new file mode 100644 index 0000000000..f2e9cd215c --- /dev/null +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidationContext.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; + +namespace Volo.Abp.Validation +{ + public class ObjectValidationContext + { + [NotNull] + public object ValidatingObject { get; } + + public List Errors { get; } + + public ObjectValidationContext([NotNull] object validatingObject) + { + ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject)); + Errors = new List(); + } + } +} diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs index 2d92af7221..8ff3998f7e 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ObjectValidator.cs @@ -1,25 +1,21 @@ -using System.Collections; +using Microsoft.Extensions.Options; using System.Collections.Generic; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; -using Volo.Abp.Reflection; namespace Volo.Abp.Validation { public class ObjectValidator : IObjectValidator, ITransientDependency { - private const int MaxRecursiveParameterValidationDepth = 8; + protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected AbpValidationOptions Options { get; } - private readonly AbpValidationOptions _options; - private readonly IDataAnnotationValidator _dataAnnotationValidator; - - public ObjectValidator(IOptions options, IDataAnnotationValidator dataAnnotationValidator) + public ObjectValidator(IOptions options, IHybridServiceScopeFactory serviceScopeFactory) { - _dataAnnotationValidator = dataAnnotationValidator; - _options = options.Value; + ServiceScopeFactory = serviceScopeFactory; + Options = options.Value; } public virtual void Validate(object validatingObject, string name = null, bool allowNull = false) @@ -37,75 +33,35 @@ namespace Volo.Abp.Validation public virtual List GetErrors(object validatingObject, string name = null, bool allowNull = false) { - var errors = new List(); - - if (validatingObject == null && !allowNull) - { - errors.Add( - name == null - ? new ValidationResult("Given object is null!") - : new ValidationResult(name + " is null!", new[] { name }) - ); - - return errors; - } - - ValidateObjectRecursively(errors, validatingObject, currentDepth: 1); - - return errors; - } - - protected virtual void ValidateObjectRecursively(List errors, object validatingObject, int currentDepth) - { - if (currentDepth > MaxRecursiveParameterValidationDepth) - { - return; - } - if (validatingObject == null) { - return; - } - - errors.AddRange(_dataAnnotationValidator.GetErrors(validatingObject)); - - //Validate items of enumerable - if (validatingObject is IEnumerable) - { - if (!(validatingObject is IQueryable)) + if (allowNull) { - foreach (var item in (validatingObject as IEnumerable)) + return new List(); //TODO: Returning an array would be more performent + } + else + { + return new List { - ValidateObjectRecursively(errors, item, currentDepth + 1); - } + name == null + ? new ValidationResult("Given object is null!") + : new ValidationResult(name + " is null!", new[] {name}) + }; } - - return; } - var validatingObjectType = validatingObject.GetType(); + var context = new ObjectValidationContext(validatingObject); - //Do not recursively validate for primitive objects - if (TypeHelper.IsPrimitiveExtended(validatingObjectType)) + using (var scope = ServiceScopeFactory.CreateScope()) { - return; - } - - if (_options.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject))) - { - return; - } - - var properties = TypeDescriptor.GetProperties(validatingObject).Cast(); - foreach (var property in properties) - { - if (property.Attributes.OfType().Any()) + foreach (var contributorType in Options.ObjectValidationContributors) { - continue; + var contributor = (IObjectValidationContributor) scope.ServiceProvider.GetRequiredService(contributorType); + contributor.AddErrors(context); } - - ValidateObjectRecursively(errors, property.GetValue(validatingObject), currentDepth + 1); } + + return context.Errors; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs index 86d58e89a3..c7fedec8db 100644 --- a/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs +++ b/framework/src/Volo.Abp.Validation/Volo/Abp/Validation/ValidationInterceptor.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; using Volo.Abp.Aspects; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; @@ -7,11 +10,11 @@ namespace Volo.Abp.Validation { public class ValidationInterceptor : AbpInterceptor, ITransientDependency { - private readonly IMethodInvocationValidator _validator; + private readonly IMethodInvocationValidator _methodInvocationValidator; - public ValidationInterceptor(IMethodInvocationValidator validator) + public ValidationInterceptor(IMethodInvocationValidator methodInvocationValidator) { - _validator = validator; + _methodInvocationValidator = methodInvocationValidator; } public override void Intercept(IAbpMethodInvocation invocation) @@ -42,7 +45,7 @@ namespace Volo.Abp.Validation protected virtual void Validate(IAbpMethodInvocation invocation) { - _validator.Validate( + _methodInvocationValidator.Validate( new MethodInvocationValidationContext( invocation.TargetObject, invocation.Method, 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/src/Volo.Abp/README.md b/framework/src/Volo.Abp/README.md new file mode 100644 index 0000000000..3dee8799a1 --- /dev/null +++ b/framework/src/Volo.Abp/README.md @@ -0,0 +1,3 @@ +# Volo.Abp + +This package is a name holder. It just references to the `Volo.Abp.Core` package. \ No newline at end of file diff --git a/framework/src/Volo.Abp/Volo.Abp.csproj b/framework/src/Volo.Abp/Volo.Abp.csproj new file mode 100644 index 0000000000..b3cf4f5752 --- /dev/null +++ b/framework/src/Volo.Abp/Volo.Abp.csproj @@ -0,0 +1,19 @@ + + + + + + netstandard2.0 + Volo.Abp + Volo.Abp + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + \ No newline at end of file diff --git a/framework/test/AbpTestBase/AbpTestBase.csproj b/framework/test/AbpTestBase/AbpTestBase.csproj index 5e9761d304..4a25e99676 100644 --- a/framework/test/AbpTestBase/AbpTestBase.csproj +++ b/framework/test/AbpTestBase/AbpTestBase.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/framework/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj index 1ca29422b9..806e426f82 100644 --- a/framework/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj index b7ba4ddd36..b0d3baaad9 100644 --- a/framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.MultiTenancy.Tests/Volo.Abp.AspNetCore.MultiTenancy.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj index 551dfd9f2a..bd0a8c1ca0 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj @@ -21,7 +21,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs index b9d01a86c4..6bb9a5ae94 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Shouldly; @@ -16,6 +17,14 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation return Task.FromResult(model.Value1); } + [HttpGet] + [Route("object-result-action-with-custom_validate")] + public Task ObjectResultActionWithCustomValidate(CustomValidateModel model) + { + ModelState.IsValid.ShouldBeTrue(); //AbpValidationFilter throws exception otherwise + return Task.FromResult(model.Value1); + } + [HttpGet] [Route("action-result-action")] public IActionResult ActionResultAction(ValidationTest1Model model) @@ -29,5 +38,19 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation [MinLength(2)] public string Value1 { get; set; } } + + public class CustomValidateModel : IValidatableObject + { + public string Value1 { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (Value1 != "hello") + { + yield return new ValidationResult("Value1 should be hello"); + } + } + } + } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs index 19a4bcd608..5177868843 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs @@ -47,5 +47,16 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation var result = await GetResponseAsStringAsync("/api/validation-test/action-result-action"); //Missed the value1 result.ShouldBe("ModelState.IsValid: false"); } + + [Fact] + public async Task Should_Return_Custom_Validate_Errors() + { + var result = await GetResponseAsObjectAsync( + "/api/validation-test/object-result-action-with-custom_validate?value1=abc", HttpStatusCode.BadRequest); //value1 should be hello. + + result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); + result.Error.ValidationErrors.ShouldContain(x => x.Message == "Value1 should be hello"); + } + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests.csproj index 9d5b607bfd..a25b7d7d8f 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Tests.csproj @@ -20,7 +20,7 @@ - + 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 3b6d429dd0..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.AspNetCore.Mvc.UI.Tests/Volo.Abp.AspNetCore.Mvc.UI.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo.Abp.AspNetCore.Mvc.UI.Tests.csproj index 7ee7949c19..74c0285f34 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo.Abp.AspNetCore.Mvc.UI.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo.Abp.AspNetCore.Mvc.UI.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj index 7ffebd4224..d32284ed87 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo.Abp.AspNetCore.Mvc.Versioning.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AbpAspNetCoreMvcVersioningTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AbpAspNetCoreMvcVersioningTestModule.cs index 728b119a19..3ea9103e9e 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AbpAspNetCoreMvcVersioningTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/AbpAspNetCoreMvcVersioningTestModule.cs @@ -16,26 +16,34 @@ namespace Volo.Abp.AspNetCore.Mvc.Versioning )] public class AbpAspNetCoreMvcVersioningTestModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) + public override void PreConfigureServices(ServiceConfigurationContext context) { - Configure(options => + PreConfigure(options => { //2.0 Version options.ConventionalControllers.Create(typeof(AbpAspNetCoreMvcVersioningTestModule).Assembly, opts => { - opts.TypePredicate = t => t.Namespace == typeof(Volo.Abp.AspNetCore.Mvc.Versioning.App.TodoAppService).Namespace; + opts.TypePredicate = t => t.Namespace == typeof(Volo.Abp.AspNetCore.Mvc.Versioning.App.v2.TodoAppService).Namespace; opts.ApiVersions.Add(new ApiVersion(2, 0)); }); - //1.0 Compatability version + //1.0 Compatibility version options.ConventionalControllers.Create(typeof(AbpAspNetCoreMvcVersioningTestModule).Assembly, opts => { - opts.TypePredicate = t => t.Namespace == typeof(Volo.Abp.AspNetCore.Mvc.Versioning.App.Compat.TodoAppService).Namespace; + opts.TypePredicate = t => t.Namespace == typeof(Volo.Abp.AspNetCore.Mvc.Versioning.App.v1.TodoAppService).Namespace; opts.ApiVersions.Add(new ApiVersion(1, 0)); }); }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + context.Services.ExecutePreConfiguredActions(options); + }); - context.Services.AddApiVersioning(options => + context.Services.AddAbpApiVersioning(options => { options.ReportApiVersions = true; options.AssumeDefaultVersionWhenUnspecified = true; @@ -43,7 +51,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Versioning //options.ApiVersionReader = new HeaderApiVersionReader("api-version"); //Supports header too //options.ApiVersionReader = new MediaTypeApiVersionReader(); //Supports accept header too - options.ConfigureAbp(context.Services); + var mvcOptions = context.Services.ExecutePreConfiguredActions(); + options.ConfigureAbp(mvcOptions); }); context.Services.AddHttpClientProxies(typeof(AbpAspNetCoreMvcVersioningTestModule).Assembly); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/ITodoAppService.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/ITodoAppService.cs deleted file mode 100644 index 0a8ed290ce..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/ITodoAppService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Volo.Abp.Application.Services; - -namespace Volo.Abp.AspNetCore.Mvc.Versioning.App.Compat -{ - public interface ITodoAppService : IApplicationService - { - string Get(int id); - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/TodoAppService.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/TodoAppService.cs deleted file mode 100644 index 8d25b6facd..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/Compat/TodoAppService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Volo.Abp.ApiVersioning; -using Volo.Abp.Application.Services; - -namespace Volo.Abp.AspNetCore.Mvc.Versioning.App.Compat -{ - public class TodoAppService : ApplicationService, ITodoAppService - { - private readonly IRequestedApiVersion _requestedApiVersion; - - public TodoAppService(IRequestedApiVersion requestedApiVersion) - { - _requestedApiVersion = requestedApiVersion; - } - - public string Get(int id) - { - return "Compat-" + id + "-" + GetVersionOrNone(); - } - - private string GetVersionOrNone() - { - return _requestedApiVersion.Current ?? "NONE"; - } - } -} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/HelloController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/HelloController.cs index fb39ae53ed..db652e95bd 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/HelloController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/HelloController.cs @@ -5,13 +5,33 @@ namespace Volo.Abp.AspNetCore.Mvc.Versioning.App { [ApiVersion("1.0")] [ApiVersion("2.0")] + [ApiController] [Route("api/v{apiVersion:apiVersion}/[controller]")] public class HelloController : AbpController, IHelloController { + [HttpGet] + public Task GetAsync() + { + return Task.FromResult($"Get-{HttpContext.GetRequestedApiVersion().ToString()}"); + } + + [HttpPost] + [MapToApiVersion("1.0")] + public Task PostAsyncV1() + { + return PostAsync(); + } + [HttpPost] - public Task PostAsync() + [MapToApiVersion("2.0")] + public Task PostAsyncV2() + { + return PostAsync(); + } + + private Task PostAsync() { - return Task.FromResult($"42-{HttpContext.GetRequestedApiVersion().ToString()}"); + return Task.FromResult($"Post-{HttpContext.GetRequestedApiVersion().ToString()}"); } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/IHelloController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/IHelloController.cs index bd4871aac6..17c2226edd 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/IHelloController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/IHelloController.cs @@ -4,6 +4,10 @@ namespace Volo.Abp.AspNetCore.Mvc.Versioning.App { public interface IHelloController : IRemoteService { - Task PostAsync(); + Task GetAsync(); + + Task PostAsyncV1(); + + Task PostAsyncV2(); } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/ITodoAppService.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/ITodoAppService.cs deleted file mode 100644 index f4de87629d..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/ITodoAppService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Volo.Abp.Application.Services; - -namespace Volo.Abp.AspNetCore.Mvc.Versioning.App -{ - public interface ITodoAppService : IApplicationService - { - string Get(int id); - } -} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/TodoAppService.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/TodoAppService.cs deleted file mode 100644 index e4b08eed99..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/TodoAppService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Volo.Abp.ApiVersioning; -using Volo.Abp.Application.Services; - -namespace Volo.Abp.AspNetCore.Mvc.Versioning.App -{ - public class TodoAppService : ApplicationService, ITodoAppService - { - private readonly IRequestedApiVersion _requestedApiVersion; - - public TodoAppService(IRequestedApiVersion requestedApiVersion) - { - _requestedApiVersion = requestedApiVersion; - } - - public string Get(int id) - { - return id + "-" + GetVersionOrNone(); - } - - private string GetVersionOrNone() - { - return _requestedApiVersion.Current ?? "NONE"; - } - } -} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v1/ITodoAppService.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v1/ITodoAppService.cs new file mode 100644 index 0000000000..3e82da0329 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v1/ITodoAppService.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Services; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App.v1 +{ + public interface ITodoAppService : IApplicationService + { + string Get(int id); + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v1/TodoAppService.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v1/TodoAppService.cs new file mode 100644 index 0000000000..f3f900148d --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v1/TodoAppService.cs @@ -0,0 +1,25 @@ +using Volo.Abp.ApiVersioning; +using Volo.Abp.Application.Services; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App.v1 +{ + public class TodoAppService : ApplicationService, ITodoAppService + { + private readonly IRequestedApiVersion _requestedApiVersion; + + public TodoAppService(IRequestedApiVersion requestedApiVersion) + { + _requestedApiVersion = requestedApiVersion; + } + + public string Get(int id) + { + return $"Compat-{id}-{GetVersionOrNone()}"; + } + + private string GetVersionOrNone() + { + return _requestedApiVersion.Current ?? "NONE"; + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v2/ITodoAppService.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v2/ITodoAppService.cs new file mode 100644 index 0000000000..ae4afea6e1 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v2/ITodoAppService.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Services; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App.v2 +{ + public interface ITodoAppService : IApplicationService + { + string Get(int id); + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v2/TodoAppService.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v2/TodoAppService.cs new file mode 100644 index 0000000000..b97af873b7 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/App/v2/TodoAppService.cs @@ -0,0 +1,25 @@ +using Volo.Abp.ApiVersioning; +using Volo.Abp.Application.Services; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.App.v2 +{ + public class TodoAppService : ApplicationService, ITodoAppService + { + private readonly IRequestedApiVersion _requestedApiVersion; + + public TodoAppService(IRequestedApiVersion requestedApiVersion) + { + _requestedApiVersion = requestedApiVersion; + } + + public string Get(int id) + { + return id + "-" + GetVersionOrNone(); + } + + private string GetVersionOrNone() + { + return _requestedApiVersion.Current ?? "NONE"; + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/Compat/TodoAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/Compat/TodoAppService_Tests.cs deleted file mode 100644 index 3d88941997..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/Compat/TodoAppService_Tests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Shouldly; -using Volo.Abp.AspNetCore.Mvc.Versioning.App.Compat; -using Xunit; - -namespace Volo.Abp.AspNetCore.Mvc.Versioning.Test.Compat -{ - public class TodoAppService_Tests : AspNetCoreMvcVersioningTestBase - { - private readonly ITodoAppService _todoAppService; - - public TodoAppService_Tests() - { - _todoAppService = ServiceProvider.GetRequiredService(); - } - - [Fact(Skip = "It stopped working after ASP.NET Core 2.2 Upgrade. Should work on that.")] - public void Get() - { - _todoAppService.Get(42).ShouldBe("Compat-42-1.0"); - } - } -} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/HelloController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/HelloController_Tests.cs index 519bfffd11..0dc4057bde 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/HelloController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/HelloController_Tests.cs @@ -8,17 +8,29 @@ namespace Volo.Abp.AspNetCore.Mvc.Versioning.Test { public class HelloController_Tests: AspNetCoreMvcVersioningTestBase { - private readonly IHelloController _todoAppService; + private readonly IHelloController _helloController; public HelloController_Tests() { - _todoAppService = ServiceProvider.GetRequiredService(); + _helloController = ServiceProvider.GetRequiredService(); } [Fact] - public async Task PostAsync() + public async Task GetAsync() { - (await _todoAppService.PostAsync()).ShouldBe("42-2.0"); + (await _helloController.GetAsync()).ShouldBe("Get-2.0"); + } + + [Fact] + public async Task PostAsyncV1() + { + (await _helloController.PostAsyncV1()).ShouldBe("Post-1.0"); + } + + [Fact] + public async Task PostAsyncV2() + { + (await _helloController.PostAsyncV2()).ShouldBe("Post-2.0"); } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/TodoAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/TodoAppService_Tests.cs deleted file mode 100644 index 2bccbd41b2..0000000000 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/TodoAppService_Tests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Shouldly; -using Volo.Abp.AspNetCore.Mvc.Versioning.App; -using Xunit; - -namespace Volo.Abp.AspNetCore.Mvc.Versioning.Test -{ - public class TodoAppService_Tests : AspNetCoreMvcVersioningTestBase - { - private readonly ITodoAppService _todoAppService; - - public TodoAppService_Tests() - { - _todoAppService = ServiceProvider.GetRequiredService(); - } - - [Fact(Skip = "It stopped working after ASP.NET Core 2.2 Upgrade. Should work on that.")] - public void Get() - { - _todoAppService.Get(42).ShouldBe("42-2.0"); - } - } -} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/v1/TodoAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/v1/TodoAppService_Tests.cs new file mode 100644 index 0000000000..7b461a3902 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/v1/TodoAppService_Tests.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc.Versioning.App.v1; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.Test.v1 +{ + public class TodoAppService_Tests : AspNetCoreMvcVersioningTestBase + { + private readonly ITodoAppService _todoAppService; + + public TodoAppService_Tests() + { + _todoAppService = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Get() + { + _todoAppService.Get(42).ShouldBe("Compat-42-1.0"); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/v2/TodoAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/v2/TodoAppService_Tests.cs new file mode 100644 index 0000000000..f4cd122afc --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Versioning.Tests/Volo/Abp/AspNetCore/Mvc/Versioning/Test/v2/TodoAppService_Tests.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc.Versioning.App.v2; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Versioning.Test.v2 +{ + public class TodoAppService_Tests : AspNetCoreMvcVersioningTestBase + { + private readonly ITodoAppService _todoAppService; + + public TodoAppService_Tests() + { + _todoAppService = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Get() + { + _todoAppService.Get(42).ShouldBe("42-2.0"); + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Tests/Volo.Abp.AspNetCore.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Tests/Volo.Abp.AspNetCore.Tests.csproj index 7ade7ea81b..9c7544a2f6 100644 --- a/framework/test/Volo.Abp.AspNetCore.Tests/Volo.Abp.AspNetCore.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Tests/Volo.Abp.AspNetCore.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo.Abp.Auditing.Tests.csproj b/framework/test/Volo.Abp.Auditing.Tests/Volo.Abp.Auditing.Tests.csproj index 35b66f5bfe..cb099c5168 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo.Abp.Auditing.Tests.csproj +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo.Abp.Auditing.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingInterceptor_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingInterceptor_Tests.cs index 141bf01688..23eba6c53b 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingInterceptor_Tests.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingInterceptor_Tests.cs @@ -37,7 +37,7 @@ namespace Volo.Abp.Auditing using (var scope = _auditingManager.BeginScope()) { - await myAuditedObject1.DoItAsync(new InputObject { Value1 = "fourty-two", Value2 = 42 }); + await myAuditedObject1.DoItAsync(new InputObject { Value1 = "forty-two", Value2 = 42 }); await scope.SaveAsync(); } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj b/framework/test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj index 160262de19..88925bb67d 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj b/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj index 1f2757b34e..9eb89d4af9 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj @@ -14,7 +14,7 @@ - + \ No newline at end of file diff --git a/framework/test/Volo.Abp.Autofac.Tests/Volo.Abp.Autofac.Tests.csproj b/framework/test/Volo.Abp.Autofac.Tests/Volo.Abp.Autofac.Tests.csproj index 26a16f83cc..c92d40f8be 100644 --- a/framework/test/Volo.Abp.Autofac.Tests/Volo.Abp.Autofac.Tests.csproj +++ b/framework/test/Volo.Abp.Autofac.Tests/Volo.Abp.Autofac.Tests.csproj @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo.Abp.BackgroundJobs.Tests.csproj b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo.Abp.BackgroundJobs.Tests.csproj index 8c6a8170b8..7fa0474264 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo.Abp.BackgroundJobs.Tests.csproj +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo.Abp.BackgroundJobs.Tests.csproj @@ -16,7 +16,7 @@ - + \ No newline at end of file diff --git a/framework/test/Volo.Abp.Caching.Tests/Volo.Abp.Caching.Tests.csproj b/framework/test/Volo.Abp.Caching.Tests/Volo.Abp.Caching.Tests.csproj index 49f5d2454a..2b4696092c 100644 --- a/framework/test/Volo.Abp.Caching.Tests/Volo.Abp.Caching.Tests.csproj +++ b/framework/test/Volo.Abp.Caching.Tests/Volo.Abp.Caching.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/test/Volo.Abp.Castle.Core.Tests/Volo.Abp.Castle.Core.Tests.csproj b/framework/test/Volo.Abp.Castle.Core.Tests/Volo.Abp.Castle.Core.Tests.csproj index c96b26d272..c5a45236c2 100644 --- a/framework/test/Volo.Abp.Castle.Core.Tests/Volo.Abp.Castle.Core.Tests.csproj +++ b/framework/test/Volo.Abp.Castle.Core.Tests/Volo.Abp.Castle.Core.Tests.csproj @@ -14,7 +14,7 @@ - + \ No newline at end of file diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo.Abp.Cli.Core.Tests.csproj b/framework/test/Volo.Abp.Cli.Core.Tests/Volo.Abp.Cli.Core.Tests.csproj new file mode 100644 index 0000000000..bb2664cdc1 --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo.Abp.Cli.Core.Tests.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.2 + latest + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestBase.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestBase.cs new file mode 100644 index 0000000000..6f6393b7ae --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestBase.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Cli +{ + public class AbpCliTestBase : AbpIntegratedTest + { + + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestModule.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestModule.cs new file mode 100644 index 0000000000..3fc7dcd457 --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/AbpCliTestModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.Cli +{ + [DependsOn( + typeof(AbpTestBaseModule), + typeof(AbpCliCoreModule) + )] + public class AbpCliTestModule : AbpModule + { + + } +} diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs new file mode 100644 index 0000000000..c8d31990cf --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs @@ -0,0 +1,68 @@ +using Shouldly; +using System.Linq; +using Volo.Abp.Cli.Args; +using Xunit; + +namespace Volo.Abp.Cli +{ + public class CommandLineArgumentParser_Tests : AbpCliTestBase + { + private readonly ICommandLineArgumentParser _commandLineArgumentParser; + + public CommandLineArgumentParser_Tests() + { + _commandLineArgumentParser = GetRequiredService(); + } + + [Theory] + [InlineData(null)] + [InlineData(new object[] { new string[0] })] + public void Should_Parse_Empty_Arguments(string[] args) + { + var commandLineArgs = _commandLineArgumentParser.Parse(args); + commandLineArgs.Command.ShouldBeNull(); + commandLineArgs.Target.ShouldBeNull(); + commandLineArgs.Options.Any().ShouldBeFalse(); + } + + [Fact] + public void Should_Work_With_Only_Command() + { + var commandLineArgs = _commandLineArgumentParser.Parse(new[] { "mycommand" }); + commandLineArgs.Command.ShouldBe("mycommand"); + commandLineArgs.Target.ShouldBeNull(); + commandLineArgs.Options.Any().ShouldBeFalse(); + } + + [Fact] + public void Should_Work_With_Command_And_Target() + { + var commandLineArgs = _commandLineArgumentParser.Parse(new[] { "mycommand", "mytarget" }); + commandLineArgs.Command.ShouldBe("mycommand"); + commandLineArgs.Target.ShouldBe("mytarget"); + commandLineArgs.Options.Any().ShouldBeFalse(); + } + + [Fact] + public void Should_Work_With_Command_And_Target_And_Options() + { + var commandLineArgs = _commandLineArgumentParser.Parse(new[] + { + "mycommand", "mytarget", "-a", "value1", "-b", "--optionC", "value2" + }); + + commandLineArgs.Command.ShouldBe("mycommand"); + commandLineArgs.Target.ShouldBe("mytarget"); + commandLineArgs.Options.Any().ShouldBeTrue(); + + commandLineArgs.Options.ShouldContainKey("a"); + commandLineArgs.Options["a"].ShouldBe("value1"); + + commandLineArgs.Options.ShouldContainKey("b"); + commandLineArgs.Options["b"].ShouldBeNull(); + + commandLineArgs.Options.ShouldContainKey("optionC"); + commandLineArgs.Options["optionC"].ShouldBe("value2"); + } + } +} 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.Core.Tests/Microsoft/Extensions/DependencyInjection/AbpConventionalDependencyInjectionExtensions_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/AbpConventionalDependencyInjectionExtensions_Tests.cs index 369ee98cb9..dc02cf4e2d 100644 --- a/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/AbpConventionalDependencyInjectionExtensions_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Microsoft/Extensions/DependencyInjection/AbpConventionalDependencyInjectionExtensions_Tests.cs @@ -85,7 +85,7 @@ namespace Microsoft.Extensions.DependencyInjection //Assert - //Check descriptons in service collection + //Check descriptions in service collection var descriptions = _services.Where(s => s.ServiceType == typeof(IMyService)).ToList(); descriptions.Count.ShouldBe(2); descriptions[0].ImplementationType.ShouldBe(typeof(FirstImplOfMyService)); @@ -113,7 +113,7 @@ namespace Microsoft.Extensions.DependencyInjection //Assert - //Check descriptons in service collection + //Check descriptions in service collection var descriptions = _services.Where(s => s.ServiceType == typeof(IMyService)).ToList(); descriptions.Count.ShouldBe(1); descriptions[0].ImplementationType.ShouldBe(typeof(FirstImplOfMyService)); @@ -139,7 +139,7 @@ namespace Microsoft.Extensions.DependencyInjection //Assert - //Check descriptons in service collection + //Check descriptions in service collection var descriptions = _services.Where(s => s.ServiceType == typeof(IMyService)).ToList(); descriptions.Count.ShouldBe(1); descriptions[0].ImplementationType.ShouldBe(typeof(MyServiceReplacesIMyService)); diff --git a/framework/test/Volo.Abp.Core.Tests/System/AbpTypeExtensions_Tests.cs b/framework/test/Volo.Abp.Core.Tests/System/AbpTypeExtensions_Tests.cs new file mode 100644 index 0000000000..382440a3f4 --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/System/AbpTypeExtensions_Tests.cs @@ -0,0 +1,32 @@ +using Shouldly; +using Xunit; + +namespace System +{ + public class AbpTypeExtensions_Tests + { + [Fact] + public void GetBaseClasses() + { + var baseClasses = typeof(MyClass).GetBaseClasses(includeObject: false); + baseClasses.Length.ShouldBe(2); + baseClasses[0].ShouldBe(typeof(MyBaseClass1)); + baseClasses[1].ShouldBe(typeof(MyBaseClass2)); + } + + public abstract class MyBaseClass1 + { + + } + + public class MyBaseClass2 : MyBaseClass1 + { + + } + + public class MyClass : MyBaseClass2 + { + + } + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/Volo.Abp.Core.Tests.csproj b/framework/test/Volo.Abp.Core.Tests/Volo.Abp.Core.Tests.csproj index f043c10c13..bd58cb0d71 100644 --- a/framework/test/Volo.Abp.Core.Tests/Volo.Abp.Core.Tests.csproj +++ b/framework/test/Volo.Abp.Core.Tests/Volo.Abp.Core.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/AutoRegistrationHelper_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/AutoRegistrationHelper_Tests.cs index 529290d0a1..16dec3ea7d 100644 --- a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/AutoRegistrationHelper_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/DependencyInjection/AutoRegistrationHelper_Tests.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Shouldly; -using System.Linq; +using Shouldly; using Xunit; namespace Volo.Abp.DependencyInjection @@ -12,7 +10,7 @@ namespace Volo.Abp.DependencyInjection { //Act - var exposedServices = AutoRegistrationHelper.GetExposedServices(new ServiceCollection(), typeof(DefaultDerivedService)).ToList(); + var exposedServices = ExposedServiceExplorer.GetExposedServices(typeof(DefaultDerivedService)); //Assert @@ -27,7 +25,7 @@ namespace Volo.Abp.DependencyInjection { //Act - var exposedServices = AutoRegistrationHelper.GetExposedServices(new ServiceCollection(), typeof(ExplicitDerivedService)).ToList(); + var exposedServices = ExposedServiceExplorer.GetExposedServices(typeof(ExplicitDerivedService)); //Assert diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/ReflectionHelper_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/ReflectionHelper_Tests.cs index 842940cfaf..333ea2e84a 100644 --- a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/ReflectionHelper_Tests.cs +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/ReflectionHelper_Tests.cs @@ -1,7 +1,102 @@ -namespace Volo.Abp.Reflection +using System.Linq; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Reflection { public class ReflectionHelper_Tests { //TODO: ... + + + [Fact] + public void GetPublicConstantsRecursively_Test() + { + var constants = ReflectionHelper.GetPublicConstantsRecursively(typeof(BaseRole)); + + constants.ShouldNotBeEmpty(); + constants.Length.ShouldBe(1); + constants.ShouldContain(x => x == "DefaultBaseRoleName"); + } + + [Fact] + public void GetPublicConstantsRecursively_Inherit_Test() + { + var constants = ReflectionHelper.GetPublicConstantsRecursively(typeof(Roles)); + + constants.ShouldNotBeEmpty(); + constants.Length.ShouldBe(2); + constants.ShouldContain(x => x == "DefaultBaseRoleName"); + constants.ShouldContain(x => x == "DefaultRoleName"); + } + + + [Fact] + public void GetPublicConstantsRecursively_NestedTypes_Test() + { + var constants = ReflectionHelper.GetPublicConstantsRecursively(typeof(IdentityPermissions)); + + constants.ShouldNotBeEmpty(); + constants.Except(IdentityPermissions.GetAll()).Count().ShouldBe(0); + } } -} + + + public class BaseRole + { + public const string BaseRoleName = "DefaultBaseRoleName"; + } + + public class Roles : BaseRole + { + public const string RoleName = "DefaultRoleName"; + } + + public static class IdentityPermissions + { + public const string GroupName = "AbpIdentity"; + + public static class Roles + { + public const string Default = GroupName + ".Roles"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + public const string ManagePermissions = Default + ".ManagePermissions"; + } + + public static class Users + { + public const string Default = GroupName + ".Users"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + public const string ManagePermissions = Default + ".ManagePermissions"; + } + + public static class UserLookup + { + public const string Default = GroupName + ".UserLookup"; + } + + public static string[] GetAll() + { + return new[] + { + GroupName, + Roles.Default, + Roles.Create, + Roles.Update, + Roles.Delete, + Roles.ManagePermissions, + Users.Default, + Users.Create, + Users.Update, + Users.Delete, + Users.ManagePermissions, + UserLookup.Default + }; + } + } + +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Data.Tests/Volo.Abp.Data.Tests.csproj b/framework/test/Volo.Abp.Data.Tests/Volo.Abp.Data.Tests.csproj index 1cef1431c2..13c9cb2ef3 100644 --- a/framework/test/Volo.Abp.Data.Tests/Volo.Abp.Data.Tests.csproj +++ b/framework/test/Volo.Abp.Data.Tests/Volo.Abp.Data.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/test/Volo.Abp.Ddd.Tests/Volo.Abp.Ddd.Tests.csproj b/framework/test/Volo.Abp.Ddd.Tests/Volo.Abp.Ddd.Tests.csproj index ae203870fe..ad4141f96f 100644 --- a/framework/test/Volo.Abp.Ddd.Tests/Volo.Abp.Ddd.Tests.csproj +++ b/framework/test/Volo.Abp.Ddd.Tests/Volo.Abp.Ddd.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj b/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj index 5083782360..ca155ab951 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj @@ -11,14 +11,17 @@ - - + + + + + - + diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs index a36c664e8a..831258c5d5 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs @@ -1,6 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Emailing.Templates; -using Volo.Abp.Emailing.Templates.Virtual; +using Volo.Abp.Emailing.Localization; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -18,12 +17,13 @@ namespace Volo.Abp.Emailing options.FileSets.AddEmbedded(); }); - Configure(options => + Configure(options => { - options.Templates["template1"] = - new EmailTemplateDefinition("template1") - .SetVirtualFilePath("/Volo/Abp/Emailing/TestTemplates/template1.html"); + options.Resources + .Add() + .AddVirtualJson("/Volo/Abp/Emailing/Localization"); }); + } } -} +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs new file mode 100644 index 0000000000..403aaad0bd --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateRender_Tests.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Emailing.Templates; +using Xunit; + +namespace Volo.Abp.Emailing +{ + public class EmailTemplateRender_Tests : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + private readonly ITemplateRender _templateRender; + + public EmailTemplateRender_Tests() + { + _templateRender = GetRequiredService(); + } + + [Fact] + public async Task RenderAsync() + { + var template = "Hello {{email}} {{ for order in orders }}{{ order.id }}:{{ order.name }},{{ end }}"; + + var model = new ModelClass + { + Email = "john@abp.io", + Orders = new List + { + new ModelClass.Order + { + Id = "1", + Name = "iphone" + }, + new ModelClass.Order + { + Id = "2", + Name = "ipad" + } + } + }; + + var result = await _templateRender.RenderAsync(template, model); + result.ShouldBe("Hello john@abp.io 1:iphone,2:ipad,"); + } + + public class ModelClass + { + public string Email { get; set; } + + public List Orders { get; set; } + + public class Order + { + public string Id { get; set; } + + public string Name { get; set; } + } + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs index a09fb48ed1..62a2cb3eb6 100644 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/EmailTemplateStore_Tests.cs @@ -7,6 +7,11 @@ namespace Volo.Abp.Emailing { public class EmailTemplateStore_Tests : AbpIntegratedTest { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + private readonly IEmailTemplateProvider _emailTemplateProvider; public EmailTemplateStore_Tests() @@ -17,8 +22,31 @@ namespace Volo.Abp.Emailing [Fact] public async Task Should_Get_Registered_Template() { - var template = await _emailTemplateProvider.GetAsync("template1"); - template.Content.ShouldContain("This is a test template!"); + var template = await _emailTemplateProvider.GetAsync("template1", "tr"); + template.Content.ShouldContain("Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın."); + } + + [Fact] + public async Task Should_Get_Default_Culture_Template() + { + var template = await _emailTemplateProvider.GetAsync("template1", "zh-Hans"); + template.Content.ShouldContain("Please confirm your email address by clicking the link below."); + } + + [Fact] + public async Task Should_Get_Registered_Template_With_Layout() + { + var template = await _emailTemplateProvider.GetAsync("template2", "en"); + + template.Content.ShouldContain("\r\n " + "Please confirm your email address by clicking the link below."); + } + + + [Fact] + public async Task Should_Get_Registered_Template_With_Localize() + { + var template = await _emailTemplateProvider.GetAsync("template3", "tr"); + template.Content.ShouldContain("Merhaba Abp"); } } } diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs new file mode 100644 index 0000000000..bdc75f9f2f --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/AbpEmailingTestResource.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Abp.Emailing.Localization +{ + public class AbpEmailingTestResource + { + } +} diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json new file mode 100644 index 0000000000..38f7cf7e5b --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "hello": "hello" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json new file mode 100644 index 0000000000..6c3c94cfdf --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Localization/tr.json @@ -0,0 +1,6 @@ +{ + "culture": "tr", + "texts": { + "hello": "Merhaba" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs new file mode 100644 index 0000000000..60c9a2ccf3 --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestEmailTemplateProvider.cs @@ -0,0 +1,24 @@ +using Volo.Abp.Emailing.Localization; +using Volo.Abp.Emailing.Templates; +using Volo.Abp.Emailing.Templates.VirtualFiles; + +namespace Volo.Abp.Emailing +{ + public class TestEmailTemplateProvider : EmailTemplateDefinitionProvider + { + public override void Define(IEmailTemplateDefinitionContext context) + { + var template1 = new EmailTemplateDefinition("template1", defaultCultureName: "en", layout: null) + .AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template1"); + context.Add(template1); + + var template2 = new EmailTemplateDefinition("template2", layout: StandardEmailTemplates.DefaultLayout) + .AddTemplateVirtualFiles("/Volo/Abp/Emailing/TestTemplates/Template2"); + context.Add(template2); + + var template3 = new EmailTemplateDefinition("template3", layout: null, singleTemplateFile: true, localizationResource: typeof(AbpEmailingTestResource)) + .AddTemplateVirtualFile("/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl"); + context.Add(template3); + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl new file mode 100644 index 0000000000..49a951e8c0 --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/en.tpl @@ -0,0 +1,4 @@ +Please confirm your email address by clicking the link below. +We may need to send you critical information about our service and it is important that we have an accurate email address. + +Confirm email address \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl new file mode 100644 index 0000000000..3e572aedbb --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template1/tr.tpl @@ -0,0 +1,4 @@ +Lütfen aşağıdaki bağlantıya tıklayarak e-posta adresinizi onaylayın. +Size hizmetimizle ilgili kritik bilgileri göndermemiz gerekebilir ve doğru bir e-posta adresimizin olması önemlidir. + +E-posta adresini onayla \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl new file mode 100644 index 0000000000..49a951e8c0 --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template2/en.tpl @@ -0,0 +1,4 @@ +Please confirm your email address by clicking the link below. +We may need to send you critical information about our service and it is important that we have an accurate email address. + +Confirm email address \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl new file mode 100644 index 0000000000..f30f512848 --- /dev/null +++ b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/Template3/Template.tpl @@ -0,0 +1 @@ +{{#L:hello}} Abp \ No newline at end of file diff --git a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/template1.html b/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/template1.html deleted file mode 100644 index 5c98393c69..0000000000 --- a/framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/TestTemplates/template1.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - This is a test template! - - \ No newline at end of file diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj index 4162f8319b..3f32baa6aa 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj @@ -20,8 +20,8 @@ - - + + \ No newline at end of file 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.EventBus.Tests/Volo.Abp.EventBus.Tests.csproj b/framework/test/Volo.Abp.EventBus.Tests/Volo.Abp.EventBus.Tests.csproj index dc8ab54f51..e810d5ef76 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo.Abp.EventBus.Tests.csproj +++ b/framework/test/Volo.Abp.EventBus.Tests/Volo.Abp.EventBus.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj b/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj index 3632aa2bf3..ece1f124d5 100644 --- a/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj +++ b/framework/test/Volo.Abp.Features.Tests/Volo.Abp.Features.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/framework/test/Volo.Abp.FluentValidation.Tests/Volo.Abp.FluentValidation.Tests.csproj b/framework/test/Volo.Abp.FluentValidation.Tests/Volo.Abp.FluentValidation.Tests.csproj new file mode 100644 index 0000000000..b0bc864896 --- /dev/null +++ b/framework/test/Volo.Abp.FluentValidation.Tests/Volo.Abp.FluentValidation.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.2 + Volo.Abp.FluentValidation.Tests + Volo.Abp.FluentValidation.Tests + true + false + false + false + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs new file mode 100644 index 0000000000..0cde05efd4 --- /dev/null +++ b/framework/test/Volo.Abp.FluentValidation.Tests/Volo/Abp/FluentValidation/ApplicationService_FluentValidation_Tests.cs @@ -0,0 +1,205 @@ +using System.Threading.Tasks; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Autofac; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.Validation; +using Xunit; + +namespace Volo.Abp.FluentValidation +{ + public class ApplicationService_FluentValidation_Tests : AbpIntegratedTest + { + private readonly IMyAppService _myAppService; + + public ApplicationService_FluentValidation_Tests() + { + _myAppService = ServiceProvider.GetRequiredService(); + } + + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + [Fact] + public async Task Should_Work_Proper_With_Right_Inputs() + { + // MyStringValue should be aaa, MyStringValue2 should be bbb. MyStringValue3 should be ccc + var output = _myAppService.MyMethod(new MyMethodInput + { + MyStringValue = "aaa", + MyMethodInput2 = new MyMethodInput2 + { + MyStringValue2 = "bbb" + }, + MyMethodInput3 = new MyMethodInput3 + { + MyStringValue3 = "ccc" + } + }); + output.ShouldBe("aaabbbccc"); + + var asyncOutput = await _myAppService.MyMethodAsync(new MyMethodInput + { + MyStringValue = "aaa", + MyMethodInput2 = new MyMethodInput2 + { + MyStringValue2 = "bbb" + }, + MyMethodInput3 = new MyMethodInput3 + { + MyStringValue3 = "ccc" + } + }); + + asyncOutput.ShouldBe("aaabbbccc"); + } + + [Fact] + public async Task Should_Not_Work_With_Wrong_Inputs() + { + // MyStringValue should be aaa, MyStringValue2 should be bbb. MyStringValue3 should be ccc + + Assert.Throws(() => _myAppService.MyMethod(new MyMethodInput + { + MyStringValue = "a", + MyMethodInput2 = new MyMethodInput2 + { + MyStringValue2 = "b" + }, + MyMethodInput3 = new MyMethodInput3 + { + MyStringValue3 = "c" + } + })); + + await Assert.ThrowsAsync(async () => await _myAppService.MyMethodAsync( + new MyMethodInput + { + MyStringValue = "a", + MyMethodInput2 = new MyMethodInput2 + { + MyStringValue2 = "b" + }, + MyMethodInput3 = new MyMethodInput3 + { + MyStringValue3 = "c" + } + })); + } + + [Fact] + public void NotValidateMyMethod_Test() + { + var output = _myAppService.NotValidateMyMethod(new MyMethodInput4 + { + MyStringValue4 = "444" + }); + + output.ShouldBe("444"); + } + + [DependsOn(typeof(AbpAutofacModule))] + [DependsOn(typeof(AbpFluentValidationModule))] + public class TestModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.OnRegistred(onServiceRegistredContext => + { + if (typeof(IMyAppService).IsAssignableFrom(onServiceRegistredContext.ImplementationType)) + { + onServiceRegistredContext.Interceptors.TryAdd(); + } + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddType(); + } + } + + public interface IMyAppService + { + string MyMethod(MyMethodInput input); + + Task MyMethodAsync(MyMethodInput input); + + string NotValidateMyMethod(MyMethodInput4 input); + } + + public class MyAppService : IMyAppService, ITransientDependency + { + public string MyMethod(MyMethodInput input) + { + return input.MyStringValue + input.MyMethodInput2.MyStringValue2 + input.MyMethodInput3.MyStringValue3; + } + + public Task MyMethodAsync(MyMethodInput input) + { + return Task.FromResult(input.MyStringValue + input.MyMethodInput2.MyStringValue2 + + input.MyMethodInput3.MyStringValue3); + } + + public string NotValidateMyMethod(MyMethodInput4 input) + { + return input.MyStringValue4; + } + } + + public class MyMethodInput + { + public string MyStringValue { get; set; } + + public MyMethodInput2 MyMethodInput2 { get; set; } + + public MyMethodInput3 MyMethodInput3 { get; set; } + } + + public class MyMethodInput2 + { + public string MyStringValue2 { get; set; } + } + + public class MyMethodInput3 + { + + public string MyStringValue3 { get; set; } + } + + public class MyMethodInput4 + { + public string MyStringValue4 { get; set; } + } + + public class MyMethodInputValidator : AbstractValidator + { + public MyMethodInputValidator() + { + RuleFor(x => x.MyStringValue).Equal("aaa"); + RuleFor(x => x.MyMethodInput2.MyStringValue2).Equal("bbb"); + RuleFor(customer => customer.MyMethodInput3).SetValidator(new MyMethodInput3Validator()); + } + } + + public class MethodInputBaseValidator : AbstractValidator + { + public MethodInputBaseValidator() + { + RuleFor(x => x.MyStringValue3).NotNull(); + } + } + + public class MyMethodInput3Validator : MethodInputBaseValidator + { + public MyMethodInput3Validator() + { + RuleFor(x => x.MyStringValue3).Equal("ccc"); + } + } + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj b/framework/test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj index e0a8ea60de..4997863830 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo.Abp.Localization.Tests.csproj b/framework/test/Volo.Abp.Localization.Tests/Volo.Abp.Localization.Tests.csproj index f5b87ab423..98a01e2fc7 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo.Abp.Localization.Tests.csproj +++ b/framework/test/Volo.Abp.Localization.Tests/Volo.Abp.Localization.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo.Abp.MemoryDb.Tests.csproj b/framework/test/Volo.Abp.MemoryDb.Tests/Volo.Abp.MemoryDb.Tests.csproj index 0a61a38420..062cfaf730 100644 --- a/framework/test/Volo.Abp.MemoryDb.Tests/Volo.Abp.MemoryDb.Tests.csproj +++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo.Abp.MemoryDb.Tests.csproj @@ -16,7 +16,7 @@ - + \ 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.Tests.csproj b/framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj index f2508f81ca..5ca5401bac 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo.Abp.MongoDB.Tests.csproj @@ -19,8 +19,8 @@ - - + + \ 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.MultiTenancy.Tests/Volo.Abp.MultiTenancy.Tests.csproj b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo.Abp.MultiTenancy.Tests.csproj index 6b18627f7e..631ad1ed81 100644 --- a/framework/test/Volo.Abp.MultiTenancy.Tests/Volo.Abp.MultiTenancy.Tests.csproj +++ b/framework/test/Volo.Abp.MultiTenancy.Tests/Volo.Abp.MultiTenancy.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj b/framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj index 7fabca78ba..5339246c52 100644 --- a/framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj +++ b/framework/test/Volo.Abp.Security.Tests/Volo.Abp.Security.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/test/Volo.Abp.Serialization.Tests/Volo.Abp.Serialization.Tests.csproj b/framework/test/Volo.Abp.Serialization.Tests/Volo.Abp.Serialization.Tests.csproj index a9fe791822..99db6083c2 100644 --- a/framework/test/Volo.Abp.Serialization.Tests/Volo.Abp.Serialization.Tests.csproj +++ b/framework/test/Volo.Abp.Serialization.Tests/Volo.Abp.Serialization.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj b/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj index 7f83c36c9a..1e9bd3f4a0 100644 --- a/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj +++ b/framework/test/Volo.Abp.Settings.Tests/Volo.Abp.Settings.Tests.csproj @@ -16,7 +16,7 @@ - + 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.Tests/Volo.Abp.TestApp.Tests.csproj b/framework/test/Volo.Abp.TestApp.Tests/Volo.Abp.TestApp.Tests.csproj index e63096f8c4..3985910708 100644 --- a/framework/test/Volo.Abp.TestApp.Tests/Volo.Abp.TestApp.Tests.csproj +++ b/framework/test/Volo.Abp.TestApp.Tests/Volo.Abp.TestApp.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/framework/test/Volo.Abp.TestApp.Tests/Volo/Abp/TestApp/Application/PersonAppService_Tests.cs b/framework/test/Volo.Abp.TestApp.Tests/Volo/Abp/TestApp/Application/PersonAppService_Tests.cs index 9dbfc8936e..c58b1b5f85 100644 --- a/framework/test/Volo.Abp.TestApp.Tests/Volo/Abp/TestApp/Application/PersonAppService_Tests.cs +++ b/framework/test/Volo.Abp.TestApp.Tests/Volo/Abp/TestApp/Application/PersonAppService_Tests.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.TestApp.Application } [Fact] - public async Task GetAll() + public async Task GetList() { var people = await _peopleAppService.GetListAsync(new PagedAndSortedResultRequestDto()); people.Items.Count.ShouldBeGreaterThan(0); diff --git a/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj b/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj index 243bd70a8c..4e7b58ee84 100644 --- a/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj +++ b/framework/test/Volo.Abp.TestApp/Volo.Abp.TestApp.csproj @@ -16,8 +16,9 @@ + - + diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index 05c6afb7dc..ef76becbc5 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -35,7 +35,7 @@ namespace Volo.Abp.TestApp.Application var phone = new Phone(person.Id, phoneDto.Number, phoneDto.Type); person.Phones.Add(phone); - + Repository.Update(person); return ObjectMapper.Map(phone); } @@ -43,6 +43,7 @@ namespace Volo.Abp.TestApp.Application { var person = await GetEntityByIdAsync(id); person.Phones.RemoveAll(p => p.Number == number); + Repository.Update(person); } public Task GetWithComplexType(GetWithComplexTypeInput input) 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/framework/test/Volo.Abp.UI.Navigation.Tests/Volo.Abp.UI.Navigation.Tests.csproj b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo.Abp.UI.Navigation.Tests.csproj index b308eae3a2..10318fbf90 100644 --- a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo.Abp.UI.Navigation.Tests.csproj +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo.Abp.UI.Navigation.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/framework/test/Volo.Abp.Uow.Tests/Volo.Abp.Uow.Tests.csproj b/framework/test/Volo.Abp.Uow.Tests/Volo.Abp.Uow.Tests.csproj index 40575e7b1c..f3dede4b21 100644 --- a/framework/test/Volo.Abp.Uow.Tests/Volo.Abp.Uow.Tests.csproj +++ b/framework/test/Volo.Abp.Uow.Tests/Volo.Abp.Uow.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/test/Volo.Abp.Validation.Tests/Volo.Abp.Validation.Tests.csproj b/framework/test/Volo.Abp.Validation.Tests/Volo.Abp.Validation.Tests.csproj index 5891413963..60308b96ae 100644 --- a/framework/test/Volo.Abp.Validation.Tests/Volo.Abp.Validation.Tests.csproj +++ b/framework/test/Volo.Abp.Validation.Tests/Volo.Abp.Validation.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs index 3f7b49c8b4..8ff4aa8bfd 100644 --- a/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs +++ b/framework/test/Volo.Abp.Validation.Tests/Volo/Abp/Validation/ApplicationService_Validation_Tests.cs @@ -16,7 +16,7 @@ namespace Volo.Abp.Validation public ApplicationService_Validation_Tests() { - _myAppService = ServiceProvider.GetRequiredService(); + _myAppService = GetRequiredService(); } protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) diff --git a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj index 7f0be985bf..895b8231ce 100644 --- a/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj +++ b/framework/test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj @@ -18,7 +18,7 @@ - + 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/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj index 247752f848..32ee40e070 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj index a080318e11..8134b0c512 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.HangFire/Volo.Abp.BackgroundJobs.DemoApp.HangFire.csproj @@ -6,7 +6,7 @@ - + diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj index c823e9a53a..3540aa8e9c 100644 --- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj +++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj @@ -6,7 +6,10 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobRecord.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobRecord.cs index 0c18e9f877..c3684d9cfe 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobRecord.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobRecord.cs @@ -39,7 +39,7 @@ namespace Volo.Abp.BackgroundJobs public virtual DateTime? LastTryTime { get; set; } /// - /// This is true if this job is continously failed and will not be executed again. + /// This is true if this job is continuously failed and will not be executed again. /// public virtual bool IsAbandoned { get; set; } diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobStore.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobStore.cs index 5277b5f4f4..fed3718f85 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobStore.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobStore.cs @@ -19,33 +19,33 @@ namespace Volo.Abp.BackgroundJobs BackgroundJobRepository = backgroundJobRepository; } - public async Task FindAsync(Guid jobId) + public virtual async Task FindAsync(Guid jobId) { return ObjectMapper.Map( await BackgroundJobRepository.FindAsync(jobId) ); } - public async Task InsertAsync(BackgroundJobInfo jobInfo) + public virtual async Task InsertAsync(BackgroundJobInfo jobInfo) { await BackgroundJobRepository.InsertAsync( ObjectMapper.Map(jobInfo) ); } - public async Task> GetWaitingJobsAsync(int maxResultCount) + public virtual async Task> GetWaitingJobsAsync(int maxResultCount) { return ObjectMapper.Map, List>( await BackgroundJobRepository.GetWaitingListAsync(maxResultCount) ); } - public async Task DeleteAsync(Guid jobId) + public virtual async Task DeleteAsync(Guid jobId) { await BackgroundJobRepository.DeleteAsync(jobId); } - public async Task UpdateAsync(BackgroundJobInfo jobInfo) + public virtual async Task UpdateAsync(BackgroundJobInfo jobInfo) { await BackgroundJobRepository.UpdateAsync( ObjectMapper.Map(jobInfo) diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.Domain.Tests/Volo.Abp.BackgroundJobs.Domain.Tests.csproj b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.Domain.Tests/Volo.Abp.BackgroundJobs.Domain.Tests.csproj index 07501ec85f..a8ff824305 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.Domain.Tests/Volo.Abp.BackgroundJobs.Domain.Tests.csproj +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.Domain.Tests/Volo.Abp.BackgroundJobs.Domain.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj index 6fad02af58..722d94f88e 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests.csproj @@ -13,10 +13,10 @@ - - - - + + + + diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj index 00c9d6ffc2..f3fb433d10 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.TestBase/Volo.Abp.BackgroundJobs.TestBase.csproj b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.TestBase/Volo.Abp.BackgroundJobs.TestBase.csproj index 9c9e420c65..1f4db1af41 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.TestBase/Volo.Abp.BackgroundJobs.TestBase.csproj +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.TestBase/Volo.Abp.BackgroundJobs.TestBase.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs index 248d1433cc..84ed5d1910 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs @@ -98,7 +98,7 @@ namespace Volo.BloggingTestApp options.CustomSchemaIds(type => type.FullName); }); - var cultures = new List { new CultureInfo("en"), new CultureInfo("tr") }; + var cultures = new List { new CultureInfo("en"), new CultureInfo("tr"),new CultureInfo("zh-Hans") }; Configure(options => { options.DefaultRequestCulture = new RequestCulture("en"); diff --git a/modules/blogging/app/Volo.BloggingTestApp/Branding/BloggingTestAppBrandingProvider.cs b/modules/blogging/app/Volo.BloggingTestApp/Branding/BloggingTestAppBrandingProvider.cs new file mode 100644 index 0000000000..e5a8046ff7 --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp/Branding/BloggingTestAppBrandingProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components; +using Volo.Abp.DependencyInjection; +using Volo.Blogging.Localization; + +namespace Volo.BloggingTestApp.Branding +{ + [Dependency(ReplaceServices = true)] + public class BloggingTestAppBrandingProvider : DefaultBrandingProvider + { + public IHtmlLocalizer L { get; set; } + public BloggingTestAppBrandingProvider(IHtmlLocalizer localizer) + { + L = localizer; + } + public override string AppName => L["Blogs"].Value; + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj index 040cfaf4a8..7b88e297f4 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj @@ -16,7 +16,7 @@ - + diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs index e73dc334da..fae85acf88 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs @@ -1,4 +1,6 @@ -namespace Volo.Blogging +using Volo.Abp.Reflection; + +namespace Volo.Blogging { public class BloggingPermissions { @@ -39,27 +41,7 @@ public static string[] GetAll() { - return new[] - { - GroupName, - Blogs.Default, - Blogs.Management, - Blogs.Delete, - Blogs.Update, - Blogs.Create, - Posts.Default, - Posts.Delete, - Posts.Update, - Posts.Create, - Tags.Default, - Tags.Delete, - Tags.Update, - Tags.Create, - Comments.Default, - Comments.Delete, - Comments.Update, - Comments.Create - }; + return ReflectionHelper.GetPublicConstantsRecursively(typeof(BloggingPermissions)); } } } diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs index d8b9273f0c..b188fe4c3e 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs @@ -23,6 +23,17 @@ namespace Volo.Blogging.Files { Check.NotNullOrWhiteSpace(name, nameof(name)); + if (!Directory.Exists(Options.FileUploadLocalFolder)) + { + Directory.CreateDirectory(Options.FileUploadLocalFolder); + return Task.FromResult( + new RawFileDto + { + Bytes = new byte[0] + } + ); + } + var filePath = Path.Combine(Options.FileUploadLocalFolder, name); return Task.FromResult( @@ -53,6 +64,11 @@ namespace Volo.Blogging.Files var uniqueFileName = GenerateUniqueFileName(Path.GetExtension(input.Name)); var filePath = Path.Combine(Options.FileUploadLocalFolder, uniqueFileName); + if (!Directory.Exists(Options.FileUploadLocalFolder)) + { + Directory.CreateDirectory(Options.FileUploadLocalFolder); + } + File.WriteAllBytes(filePath, input.Bytes); //TODO: Previously was using WriteAllBytesAsync, but it's only in .netcore. return Task.FromResult(new FileUploadOutputDto 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 397739263a..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 @@ -35,8 +35,7 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "Blogs", options.Schema); - b.ConfigureFullAudited(); - b.ConfigureExtraProperties(); + b.ConfigureFullAuditedAggregateRoot(); b.Property(x => x.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength).HasColumnName(nameof(Blog.Name)); b.Property(x => x.ShortName).IsRequired().HasMaxLength(BlogConsts.MaxShortNameLength).HasColumnName(nameof(Blog.ShortName)); @@ -47,8 +46,7 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "Posts", options.Schema); - b.ConfigureFullAudited(); - b.ConfigureExtraProperties(); + b.ConfigureFullAuditedAggregateRoot(); b.Property(x => x.BlogId).HasColumnName(nameof(Post.BlogId)); b.Property(x => x.Title).IsRequired().HasMaxLength(PostConsts.MaxTitleLength).HasColumnName(nameof(Post.Title)); @@ -56,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); }); @@ -65,8 +63,7 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "Comments", options.Schema); - b.ConfigureFullAudited(); - b.ConfigureExtraProperties(); + b.ConfigureFullAuditedAggregateRoot(); b.Property(x => x.Text).IsRequired().HasMaxLength(CommentConsts.MaxTextLength).HasColumnName(nameof(Comment.Text)); b.Property(x => x.RepliedCommentId).HasColumnName(nameof(Comment.RepliedCommentId)); @@ -80,8 +77,7 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "Tags", options.Schema); - b.ConfigureFullAudited(); - b.ConfigureExtraProperties(); + b.ConfigureFullAuditedAggregateRoot(); b.Property(x => x.Name).IsRequired().HasMaxLength(TagConsts.MaxNameLength).HasColumnName(nameof(Tag.Name)); b.Property(x => x.Description).HasMaxLength(TagConsts.MaxDescriptionLength).HasColumnName(nameof(Tag.Description)); diff --git a/modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs b/modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs index ac7cc9e210..20678ddc37 100644 --- a/modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs +++ b/modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; @@ -44,10 +45,13 @@ namespace Volo.Blogging Configure(options => { + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("tr", "tr", "Türkçe")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); options.Resources .Get() .AddBaseTypes(typeof(AbpValidationResource)) - .AddBaseTypes(typeof(AbpUiModule)) + .AddBaseTypes(typeof(AbpUiResource)) .AddVirtualJson("/Localization/Resources/Blogging/Web"); }); diff --git a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json index 444c3d80af..b84b1f2483 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json +++ b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json @@ -36,6 +36,12 @@ "CoverImage": "Cover Image", "CreateANewPost": "Create A New Post", "CreateANewBlog": "Create A New Blog", - "WhatIsNew": "What is new?" + "WhatIsNew": "What is new?", + "Name": "Name", + "ShortName": "Short Name", + "CreationTime": "Creation Time", + "Description": "Description", + "Blogs": "Blogs", + "Tags": "Tags" } } \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/pt-BR.json b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/pt-BR.json index cf98e56b06..62416f49a5 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/pt-BR.json +++ b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/pt-BR.json @@ -36,6 +36,12 @@ "CoverImage": "Imagem de Capa", "CreateANewPost": "Criar uma nova Postagem", "CreateANewBlog": "Criar um novo Blog", - "WhatIsNew": "O que há de novo?" + "WhatIsNew": "O que há de novo?", + "Name": "Nome", + "ShortName": "Nome Curto", + "CreationTime": "Hora da criação", + "Description": "Descrição", + "Blogs": "Blogs", + "Tags": "Etiquetas" } } \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json index dc5df7f0d0..7747638587 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json +++ b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json @@ -36,6 +36,12 @@ "CoverImage": "Kapak resmi", "CreateANewPost": "Yeni Yazı oluştur", "CreateANewBlog": "Yeni Blog Ekle", - "WhatIsNew": "Yeniler" + "WhatIsNew": "Yeniler", + "Name": "İsim", + "ShortName": "Kısa isim", + "CreationTime": "Oluşturulma zamanı", + "Description": "Açıklama", + "Blogs": "Bloglar", + "Tags": "Etiketler" } } \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/zh-Hans.json b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/zh-Hans.json index a67e325497..2ea614b648 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/zh-Hans.json +++ b/modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/zh-Hans.json @@ -36,6 +36,12 @@ "CoverImage": "封面图", "CreateANewPost": "创建一个新帖子", "CreateANewBlog": "创建一个新博客", - "WhatIsNew": "最新消息" - } - } \ No newline at end of file + "WhatIsNew": "最新消息", + "Name": "名称", + "ShortName": "简称", + "CreationTime": "创建时间", + "Description": "描述", + "Blogs": "博客", + "Tags": "标签" + } +} \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js index c370f10a67..0d8e8b3641 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js @@ -21,9 +21,7 @@ [ { text: l('Edit'), - visible: function () { - return true; //TODO: Check permission - }, + visible: abp.auth.isGranted('Blogging.Blog.Update'), action: function (data) { _editModal.open({ blogId: data.record.id @@ -32,9 +30,7 @@ }, { text: l('Delete'), - visible: function () { - return true; //TODO: Check permission - }, + visible: abp.auth.isGranted('Blogging.Blog.Delete'), confirmMessage: function (data) { return l('BlogDeletionWarningMessage') }, action: function (data) { volo.blogging.blogs diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs index 03214d1ee3..88133e0e84 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs @@ -55,6 +55,11 @@ namespace Volo.Blogging.Pages.Blog public IHtmlContent RenderMarkdownToHtml(string content) { + if(content.IsNullOrWhiteSpace()) + { + return new HtmlString(""); + } + byte[] bytes = Encoding.Default.GetBytes(content); var utf8Content = Encoding.UTF8.GetString(bytes); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Index.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Index.cshtml index fadaaab269..97f06b6dc0 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Index.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Index.cshtml @@ -1,12 +1,12 @@ @page @using Volo.Blogging.Pages.Blog -@*inherits BloggingPage*@ +@inherits BloggingPage @model IndexModel @{ ViewBag.PageTitle = "Blog"; }

- Blogs + @L["Blogs"]