Browse Source

Merge branch 'dev' into Extra-filters-12512

pull/14876/head
malik masis 4 years ago
parent
commit
7f145070a8
  1. 71
      docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md
  2. 29
      docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md
  3. 88
      docs/en/Dependency-Injection.md
  4. 8
      docs/en/Migration-Guides/Abp-7_0.md
  5. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
  6. 20
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs
  7. 3
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs
  8. 40
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs
  9. 34
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorChangeApplicationIdGuidStep.cs
  10. 50
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorPortChangeForSeparatedAuthServersStep.cs
  11. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Maui/MauiChangePortStep.cs
  12. 25
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs
  13. 1
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs
  14. 25
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs
  15. 40
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs
  16. 6
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs
  17. 14
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs
  18. 4
      framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs

71
docs/en/Community-Articles/2022-11-28-ef-bulk-operations/POST.md

@ -0,0 +1,71 @@
# Bulk Operations with Entity Framework Core 7.0
Entity Framework tracks all the entity changes and applies those changes to the database one by one when the `SaveChanges()` method is called. There was no way to execute bulk operations in Entity Framework Core without a dependency.
As you know the [Entity Framework Extensions](https://entityframework-extensions.net/bulk-savechanges) library was doing it but it was not free.
There was no other solution until now. [Bulk Operations](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) are now available in Entity Framework Core with .NET 7.
With .NET 7, there are two new methods such as `ExecuteUpdate` and `ExecuteDelete` available to execute bulk operations. It's a similar usage with the Entity Framework Core Extensions library if you're familiar with it.
You can visit the microsoft example [here](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#executeupdate-and-executedelete-bulk-updates) about how to use it.
It can be easily used with the DbContext.
```csharp
await context.Tags.Where(t => t.Text.Contains(".NET")).ExecuteDeleteAsync();
```
## Using with ABP Framework
ABP Framework provides an abstraction over database operations and implements generic repository pattern. So, DbContext can't be accessed outside of [repositories](https://docs.abp.io/en/abp/latest/Repositories).
You can use the `ExecuteUpdate` and `ExecuteDelete` methods inside a repository.
```csharp
public class BookEntityFrameworkCoreRepository : EfCoreRepository<BookStoreDbContext, Book, Guid>, IBookRepository
{
public BookEntityFrameworkCoreRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public async Task UpdateListingAsync()
{
var dbSet = await GetDbSetAsync();
await dbSet
.Where(x => x.IsListed && x.PublishedOn.Year <= 2022)
.ExecuteUpdateAsync(s => s.SetProperty(x => x.IsListed, x => false));
}
public async Task DeleteOldBooksAsync()
{
var dbSet = await GetDbSetAsync();
await dbSet
.Where(x => x.PublishedOn.Year <= 2000)
.ExecuteDeleteAsync();
}
}
```
There is no need to take an action for bulk inserting. You can use the `InsertManyAsync` method of the repository instead of creating a new method for it if you don't have custom logic. It'll use a new bulk inserting feature automatically since it's available in EF Core 7.0.
```csharp
public class MyDomainService : DomainService
{
protected IRepository<Book, Guid> BookRepository { get; }
public MyDomainService(IRepository<Book, Guid> bookRepository)
{
BookRepository = bookRepository;
}
public async Task CreateBooksAsync(List<Book> books)
{
// It'll use bulk inserting automatically.
await BookRepository.InsertManyAsync(books);
}
}
```
> If you use `ExecuteDeleteAsync` or `ExecuteUpdateAsync`, then ABP's soft delete and auditing features can not work. Because these ABP features work with EF Core's change tracking system and these new methods doesn't work with the change tracking system. So, use them carefully.

29
docs/en/Community-Articles/2022-11-29-EF-Core-Guarded-Types/POST.md

@ -1,6 +1,6 @@
# Value generation for DDD guarded types with Entity Framework Core 7.0 # Value generation for DDD guarded types with Entity Framework Core 7.0
In domain-driven design (DDD), *guarded keys* can improve the type safety of key properties. This is achieved by wrapping the key type in another type which is specific to the use of the key. In this article, I will explain the cases why you may need to use use guarded types and discuss the advantages and limitations when implementing to an ABP application. In domain-driven design (DDD), *guarded keys* can improve the type safety of key properties. This is achieved by wrapping the key type in another type which is specific to the use of the key. In this article, I will explain the cases why you may need to use guarded types and discuss the advantages and limitations when implementing to an ABP application.
> You can find the source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo). > You can find the source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo).
@ -30,14 +30,16 @@ public class ProductAppService : MyProductStoreAppService, IProductAppService
} }
```` ````
While the sample demonstrates a very simple mistake, whilst the business logic get more complex, it is easier to come across mistakes similar to this one. The next section offers using guarded types to prevent these kind of problems as a solution to the problem. While the sample demonstrates a very simple mistake, it is easier to come across similar mistakes when the business logic gets more complex especially when you are using methods with **multiple foreign key arguments**. The next section offers using guarded types to prevent these kind of problems as a solution to the problem.
## The Solution ## The Solution
With EFCore7, key properties can be guarded with type safety. To use that, update your aggreagate root or entity unique identifier with a complex object. Strongly-typed IDs (*guarded keys*) is a DDD approach to address this problem. One of the main problems with .NET users was handling the persisting these objects. With EFCore7, key properties can be guarded with type safety seamlessly.
To use guarded keys, update your aggregate root or entity unique identifier with a complex type to overcome *primitive obsession*:
````csharp ````csharp
public struct CategoryId public readonly struct CategoryId
{ {
public CategoryId(Guid value) => Value = value; public CategoryId(Guid value) => Value = value;
public Guid Value { get; } public Guid Value { get; }
@ -50,8 +52,6 @@ public readonly struct ProductId
} }
```` ````
> It is worth considering as with many [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design) (DDD) concepts, this improved type safety comes with additional code complexity.
You can now use these keys for your aggregate roots or entities: You can now use these keys for your aggregate roots or entities:
```csharp ```csharp
@ -84,7 +84,9 @@ public class Category : AggregateRoot<CategoryId>
} }
``` ```
`ProductId` and `CategoryId` guarded key types shown in the sample use `Guid` key values, which means Guid values will be used in the mapped database tables. This is achieved by defining [value converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions) for the types. Override the `ConfigureConventions` method of your DbContext to use the value converters: `ProductId` and `CategoryId` guarded key types shown in the sample use `Guid` key values, which means Guid values will be used in the mapped database tables. This is achieved by defining [value converters](https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions) for the types.
Override the `ConfigureConventions` method of your DbContext to use the value converters:
````csharp ````csharp
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
@ -133,15 +135,15 @@ public class ProductStoreDataSeedContributor : IDataSeedContributor, ITransientD
} }
``` ```
You can also use `integer` as guarded type for your key properties and use [Sequence-based key generation for SQL Server](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#sequence-based-key-generation-for-sql-server). You can also use `integer` as guarded type for your key properties and use [Sequence-based key generation for SQL Server](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#sequence-based-key-generation-for-sql-server) for value generation.
## Discussions ## Discussions
In this section, I will discuss the use cases of guarded types and limitations when implementing to an ABP application. In this section, I will discuss the use cases of guarded types and limitations when implementing to an ABP application.
### Do I need guarded type key properties even if I follow DDD principles? ### Do I need to use guarded types?
While using strongly-typed key properties reduce the chance of unnoticed errors, admittedly it increases the code complexity by adding new types to your solution. If you are already following the best practices of [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design), you are aware that **updates** and **creations** of an aggregate are done **over** the aggregate root itself as a whole unit. And entity state changes of an aggregate root can be done using the [domain services](https://docs.abp.io/en/abp/latest/Domain-Services). Domain services should already validate the entity. If you are already following the best practices of [Domain-Driven Design](https://docs.abp.io/en/abp/latest/Domain-Driven-Design), you are aware that **updates** and **creations** of an aggregate are done **over** the aggregate root itself as a whole unit. And entity state changes of an aggregate root should be done using the [domain services](https://docs.abp.io/en/abp/latest/Domain-Services). Domain services should already validate the entity.
**Example: Using domain service to update product:** **Example: Using domain service to update product:**
@ -166,7 +168,7 @@ public class ProductManager : DomainService
} }
```` ````
In this sample, domain service validates that both **product** and the **category** entities, passed by the application layer, are valid objects since they are not key properties. However, manual assignment is already in place and more complex the domain logic, higher to miss out mistakes. In this sample, domain service validates that both **product** and the **category** entities, passed by the application layer, are valid objects since they are not key properties. However, manual assignment is already in place and more complex the domain logic, higher to miss out mistakes. At the end, it will depend on your tolerance level for developer errors comparing to the time you want to spend time on additional code base for guarded types.
### Limitations ### Limitations
@ -192,12 +194,13 @@ var newProduct = await _productRepository.InsertAsync(
In this article, I tried to explain DDD guarded types for key properties and value generation for these properties using Entity Framework 7.0 and ABP. In this article, I tried to explain DDD guarded types for key properties and value generation for these properties using Entity Framework 7.0 and ABP.
As in many DDD concepts and patterns, guarded types provides improved safety for your code base at the expense of additional code complexity. If you have a large team working on a large scale solution containing complex business logics where key assignments are abundant, I personally suggest using guarded types. Using strongly-typed key properties reduce the chance of unnoticed errors. Admittedly it increases the code complexity by adding new types to your solution with extra coding, especially if you are using classes as keys. Guarded types provide improved safety for your code base at the expense of additional code complexity as in many DDD concepts and patterns.
If you have a large team working on a large scale solution containing complex business logics where key assignments are abundant or if you are using methods with multiple foreign key arguments, I personally suggest using guarded types.
## The Source Code ## The Source Code
* You can find the full source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo). * You can find the full source code of the example application [here](https://github.com/abpframework/abp-samples/tree/master/EfCoreGuardedTypeDemo).
* You can see [this pull request](https://github.com/abpframework/abp-samples/pull/207/files) for the changes I've done after creating the application.
## See Also ## See Also

88
docs/en/Dependency-Injection.md

@ -2,7 +2,7 @@
ABP's Dependency Injection system is developed based on Microsoft's [dependency injection extension](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) library (Microsoft.Extensions.DependencyInjection nuget package). So, it's documentation is valid in ABP too. ABP's Dependency Injection system is developed based on Microsoft's [dependency injection extension](https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96) library (Microsoft.Extensions.DependencyInjection nuget package). So, it's documentation is valid in ABP too.
> While ABP has no core dependency to any 3rd-party DI provider, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. Startup templates come with Autofac installed. See [Autofac integration](Autofac-Integration.md) document for more information. > While ABP has no core dependency to any 3rd-party DI provider. However, it's required to use a provider that supports dynamic proxying and some other advanced features to make some ABP features properly work. Startup templates come with [Autofac](https://autofac.org/) installed. See [Autofac integration](Autofac-Integration.md) document for more information.
## Modularity ## Modularity
@ -20,24 +20,24 @@ public class BlogModule : AbpModule
## Conventional Registration ## Conventional Registration
ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` by overriding the `PreConfigureServices` method. ABP introduces conventional service registration. You need not do anything to register a service by convention. It's automatically done. If you want to disable it, you can set `SkipAutoServiceRegistration` to `true` in the constructor of your module class. Example:
````C# ````C#
public class BlogModule : AbpModule public class BlogModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public BlogModule()
{ {
SkipAutoServiceRegistration = true; SkipAutoServiceRegistration = true;
} }
} }
```` ````
Once you skip auto registration, you should manually register your services. In that case, ``AddAssemblyOf`` extension method can help you to register all your services by convention. Example: Once you skip the auto registration, you should manually register your services. In that case, ``AddAssemblyOf`` extension method can help you to register all your services by convention. Example:
````c# ````c#
public class BlogModule : AbpModule public class BlogModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public BlogModule()
{ {
SkipAutoServiceRegistration = true; SkipAutoServiceRegistration = true;
} }
@ -105,9 +105,7 @@ Example:
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] [Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
public class TaxCalculator public class TaxCalculator
{ {
} }
```` ````
``Dependency`` attribute has a higher priority than other dependency interfaces if it defines the ``Lifetime`` property. ``Dependency`` attribute has a higher priority than other dependency interfaces if it defines the ``Lifetime`` property.
@ -120,7 +118,6 @@ public class TaxCalculator
[ExposeServices(typeof(ITaxCalculator))] [ExposeServices(typeof(ITaxCalculator))]
public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency public class TaxCalculator: ICalculator, ITaxCalculator, ICanCalculate, ITransientDependency
{ {
} }
```` ````
@ -179,7 +176,11 @@ public class MyModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
//Replacing the IConnectionStringResolver service //Replacing the IConnectionStringResolver service
context.Services.Replace(ServiceDescriptor.Transient<IConnectionStringResolver, MyConnectionStringResolver>()); context.Services.Replace(
ServiceDescriptor.Transient<
IConnectionStringResolver,
MyConnectionStringResolver
>());
} }
} }
```` ````
@ -202,7 +203,7 @@ public class TaxAppService : ApplicationService
_taxCalculator = taxCalculator; _taxCalculator = taxCalculator;
} }
public void DoSomething() public async Task DoSomethingAsync()
{ {
//...use _taxCalculator... //...use _taxCalculator...
} }
@ -227,7 +228,7 @@ public class MyService : ITransientDependency
Logger = NullLogger<MyService>.Instance; Logger = NullLogger<MyService>.Instance;
} }
public void DoSomething() public async Task DoSomethingAsync()
{ {
//...use Logger to write logs... //...use Logger to write logs...
} }
@ -244,17 +245,21 @@ One restriction of property injection is that you cannot use the dependency in y
Property injection is also useful when you want to design a base class that has some common services injected by default. If you're going to use constructor injection, all derived classes should also inject depended services into their own constructors which makes development harder. However, be very careful using property injection for non-optional services as it makes it harder to clearly see the requirements of a class. Property injection is also useful when you want to design a base class that has some common services injected by default. If you're going to use constructor injection, all derived classes should also inject depended services into their own constructors which makes development harder. However, be very careful using property injection for non-optional services as it makes it harder to clearly see the requirements of a class.
#### DisablePropertyInjectionAttribute #### DisablePropertyInjection Attribute
You can use `[DisablePropertyInjection]` attribute on class or properties to disable property injection for the whole class or some specific properties. You can use `[DisablePropertyInjection]` attribute on classes or their properties to disable property injection for the whole class or some specific properties.
````C# ````C#
// Disabling for all properties of the MyService class
[DisablePropertyInjection] [DisablePropertyInjection]
public class MyService : ITransientDependency public class MyService : ITransientDependency
{ {
public ILogger<MyService> Logger { get; set; }
public ITaxCalculator TaxCalculator { get; set; } public ITaxCalculator TaxCalculator { get; set; }
} }
// Disabling only for the TaxCalculator property
public class MyService : ITransientDependency public class MyService : ITransientDependency
{ {
public ILogger<MyService> Logger { get; set; } public ILogger<MyService> Logger { get; set; }
@ -262,17 +267,21 @@ public class MyService : ITransientDependency
[DisablePropertyInjection] [DisablePropertyInjection]
public ITaxCalculator TaxCalculator { get; set; } public ITaxCalculator TaxCalculator { get; set; }
} }
```` ````
### Resolve Service from IServiceProvider ### Resolve Service from IServiceProvider
You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject IServiceProvider into your class and use ``GetService`` method as shown below: You may want to resolve a service directly from ``IServiceProvider``. In that case, you can inject `IServiceProvider` into your class and use the ``GetService`` or the `GetRequiredService` method as shown below:
````C# ````C#
public class MyService : ITransientDependency public class MyService : ITransientDependency
{ {
public ILogger<MyService> Logger { get; set; } private readonly ITaxCalculator _taxCalculator;
public MyService(IServiceProvider serviceProvider)
{
_taxCalculator = serviceProvider.GetRequiredService<ITaxCalculator>();
}
} }
```` ````
@ -374,15 +383,15 @@ IEnumerable<IExternalLogger> services = _serviceProvider.GetServices<IExternalLo
### Releasing/Disposing Services ### Releasing/Disposing Services
If you used a constructor or property injection, you don't need to be concerned about releasing the service's resources. However, if you have resolved a service from ``IServiceProvider``, you might, in some cases, need to take care about releasing the service resources. If you used a constructor or property injection, you don't need to be concerned about releasing the service's resources. However, if you have resolved a service from ``IServiceProvider``, in some cases, you might need to take care about releasing the service resources.
ASP.NET Core releases all services at the end of a current HTTP request, even if you directly resolved from ``IServiceProvider`` (assuming you injected IServiceProvider). But, there are several cases where you may want to release/dispose manually resolved services: ASP.NET Core releases all services at the end of a current HTTP request, even if you directly resolved from ``IServiceProvider`` (assuming you injected `IServiceProvider`). But, there are several cases where you may want to release/dispose manually resolved services:
* Your code is executed outside of AspNet Core request and the executer hasn't handled the service scope. * Your code is executed outside of ASP.NET Core request and the executer hasn't handled the service scope.
* You only have a reference to the root service provider. * You only have a reference to the root service provider.
* You may want to immediately release & dispose services (for example, you may creating too many services with big memory usage and don't want to overuse memory). * You may want to immediately release & dispose services (for example, you may creating too many services with big memory usages and don't want to overuse the memory).
In any case, you can use such a 'using' code block to safely and immediately release services: In any case, you can create a service scope block to safely and immediately release services:
````C# ````C#
using (var scope = _serviceProvider.CreateScope()) using (var scope = _serviceProvider.CreateScope())
@ -392,7 +401,42 @@ using (var scope = _serviceProvider.CreateScope())
} }
```` ````
Both services are released when the created scope is disposed (at the end of the using block). Both services are released when the created scope is disposed (at the end of the `using` block).
### Cached Service Providers
ABP provides two special services to optimize resolving services from `IServiceProvider`. `ICachedServiceProvider` and `ITransientCachedServiceProvider` both inherits from the `IServiceProvider` interface and internally caches the resolved services, so you get the same service instance even if you resolve a service multiple times.
The main difference is the `ICachedServiceProvider` is itself registered as scoped, while the `ITransientCachedServiceProvider` is registered as transient to the dependency injection system.
The following example injects the `ICachedServiceProvider` service and resolves a service in the `DoSomethingAsync` method:
````csharp
public class MyService : ITransientDependency
{
private readonly ICachedServiceProvider _serviceProvider;
public MyService(ICachedServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task DoSomethingAsync()
{
var taxCalculator = _serviceProvider.GetRequiredService<ITaxCalculator>();
// TODO: Use the taxCalculator
}
}
````
With such a usage, you don't need to deal with creating service scopes and disposing the resolved services (as explained in the *Releasing/Disposing Services* section above). Because all the services resolved from the `ICachedServiceProvider` will be released once the service scope of the `MyService` instance is disposed. Also, you don't need to care about memory leaks (because of creating too many `ITaxCalculator` instances if we call `DoSomethingAsync` too many times), because only one `ITaxCalculator` instance is created, and it is reused.
Since `ICachedServiceProvider` and `ITransientCachedServiceProvider` extends the standard `IServiceProvider` interface, you can use all the extension method of the `IServiceProvider` interface on them. In addition, they provides some other methods to provide a default value or a factory method for the services that are not found (that means not registered to the dependency injection system). Notice that the default value (or the value returned from your factory method) is also cached and reused.
Use `ICachedServiceProvider` (instead of `ITransientCachedServiceProvider`) unless you need to create the service cache per usage. `ITransientCachedServiceProvider` guarantees that the created service instances are not shared with any other service, even they are in the same service scope. The services resolved from `ICachedServiceProvider` are shared with other services in the same service scope (in the same HTTP Request, for example), so it can be thought as more optimized.
> ABP Framework also provides the `IAbpLazyServiceProvider` service. It does exists for backward compatibility and works exactly same with the `ITransientCachedServiceProvider` service. So, use the `ITransientCachedServiceProvider` since the `IAbpLazyServiceProvider` might be removed in future ABP versions.
## Advanced Features ## Advanced Features

8
docs/en/Migration-Guides/Abp-7_0.md

@ -101,3 +101,11 @@ See https://github.com/abpframework/abp/pull/13644 for more info.
We've already done this for our themes. We've already done this for our themes.
See https://github.com/abpframework/abp/pull/13845 for more info. See https://github.com/abpframework/abp/pull/13845 for more info.
## Replaced `BlogPostPublicDto` with `BlogPostCommonDto`
- In the CMS Kit Module, `BlogPostPublicDto` has been moved to `Volo.CmsKit.Common.Application.Contracts` from `Volo.CmsKit.Public.Application.Contracts` and renamed to `BlogPostCommonDto`.
- See the [PR#13499](https://github.com/abpframework/abp/pull/13499) for more information.
> You can ignore this if you don't use CMS Kit Module.

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs

@ -111,7 +111,7 @@ public class NewCommand : ProjectCreationCommandBase, IConsoleCommand, ITransien
var skipBundling = commandLineArgs.Options.ContainsKey(Options.SkipBundling.Long) || commandLineArgs.Options.ContainsKey(Options.SkipBundling.Short); var skipBundling = commandLineArgs.Options.ContainsKey(Options.SkipBundling.Long) || commandLineArgs.Options.ContainsKey(Options.SkipBundling.Short);
if (!skipBundling) if (!skipBundling)
{ {
await RunBundleForBlazorWasmTemplateAsync(projectArgs); await RunBundleForBlazorWasmOrMauiBlazorTemplateAsync(projectArgs);
} }
await ConfigurePwaSupportForAngular(projectArgs); await ConfigurePwaSupportForAngular(projectArgs);

20
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ProjectCreationCommandBase.cs

@ -118,7 +118,7 @@ public abstract class ProjectCreationCommandBase
Logger.LogInformation("DBMS: " + databaseManagementSystem); Logger.LogInformation("DBMS: " + databaseManagementSystem);
} }
var uiFramework = GetUiFramework(commandLineArgs); var uiFramework = GetUiFramework(commandLineArgs, template);
if (uiFramework != UiFramework.NotSpecified) if (uiFramework != UiFramework.NotSpecified)
{ {
Logger.LogInformation("UI Framework: " + uiFramework); Logger.LogInformation("UI Framework: " + uiFramework);
@ -413,22 +413,24 @@ public abstract class ProjectCreationCommandBase
} }
} }
protected async Task RunBundleForBlazorWasmTemplateAsync(ProjectBuildArgs projectArgs) protected async Task RunBundleForBlazorWasmOrMauiBlazorTemplateAsync(ProjectBuildArgs projectArgs)
{ {
if (AppTemplateBase.IsAppTemplate(projectArgs.TemplateName) && projectArgs.UiFramework == UiFramework.Blazor) if (AppTemplateBase.IsAppTemplate(projectArgs.TemplateName) && projectArgs.UiFramework is UiFramework.Blazor or UiFramework.MauiBlazor)
{ {
Logger.LogInformation("Generating bundles for Blazor Wasm..."); var isWebassembly = projectArgs.UiFramework == UiFramework.Blazor;
var message = isWebassembly ? "Generating bundles for Blazor Wasm" : "Generating bundles for MAUI Blazor";
Logger.LogInformation($"{message}...");
await EventBus.PublishAsync(new ProjectCreationProgressEvent await EventBus.PublishAsync(new ProjectCreationProgressEvent
{ {
Message = "Generating bundles for Blazor Wasm" Message = message
}, false); }, false);
var directory = Path.GetDirectoryName( var directory = Path.GetDirectoryName(
Directory.GetFiles(projectArgs.OutputFolder, "*.Blazor.csproj", SearchOption.AllDirectories).First() Directory.GetFiles(projectArgs.OutputFolder, isWebassembly? "*.Blazor.csproj" :"*.MauiBlazor.csproj", SearchOption.AllDirectories).First()
); );
await _bundlingService.BundleAsync(directory, true); await _bundlingService.BundleAsync(directory, true, projectType: isWebassembly ? BundlingConsts.WebAssembly : BundlingConsts.MauiBlazor);
} }
} }
@ -531,7 +533,7 @@ public abstract class ProjectCreationCommandBase
} }
} }
protected virtual UiFramework GetUiFramework(CommandLineArgs commandLineArgs) protected virtual UiFramework GetUiFramework(CommandLineArgs commandLineArgs, string template = "app")
{ {
if (commandLineArgs.Options.ContainsKey("no-ui")) if (commandLineArgs.Options.ContainsKey("no-ui"))
{ {
@ -554,6 +556,8 @@ public abstract class ProjectCreationCommandBase
return UiFramework.Blazor; return UiFramework.Blazor;
case "blazor-server": case "blazor-server":
return UiFramework.BlazorServer; return UiFramework.BlazorServer;
case "maui-blazor" when template == AppProTemplate.TemplateName:
return UiFramework.MauiBlazor;
default: default:
throw new CliUsageException(ExceptionMessageHelper.GetInvalidOptionExceptionMessage("UI Framework")); throw new CliUsageException(ExceptionMessageHelper.GetInvalidOptionExceptionMessage("UI Framework"));
} }

3
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/UiFramework.cs

@ -7,5 +7,6 @@ public enum UiFramework
Mvc = 2, Mvc = 2,
Angular = 3, Angular = 3,
Blazor = 4, Blazor = 4,
BlazorServer = 5 BlazorServer = 5,
MauiBlazor = 6
} }

40
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/AppTemplateBase.cs

@ -132,7 +132,11 @@ public abstract class AppTemplateBase : TemplateInfo
case UiFramework.BlazorServer: case UiFramework.BlazorServer:
ConfigureWithBlazorServerUi(context, steps); ConfigureWithBlazorServerUi(context, steps);
break; break;
case UiFramework.MauiBlazor:
ConfigureWithMauiBlazorUi(context, steps);
break;
case UiFramework.Mvc: case UiFramework.Mvc:
case UiFramework.NotSpecified: case UiFramework.NotSpecified:
ConfigureWithMvcUi(context, steps); ConfigureWithMvcUi(context, steps);
@ -510,6 +514,37 @@ public abstract class AppTemplateBase : TemplateInfo
} }
} }
protected void ConfigureWithMauiBlazorUi(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
context.Symbols.Add("ui:maui-blazor");
steps.Add(new MauiBlazorChangeApplicationIdGuidStep());
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Host"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "/aspnet-core/test/MyCompanyName.MyProjectName.Web.Tests"));
if (context.BuildArgs.ExtraProperties.ContainsKey("separate-identity-server") ||
context.BuildArgs.ExtraProperties.ContainsKey("separate-auth-server"))
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds"));
steps.Add(new MauiBlazorPortChangeForSeparatedAuthServersStep());
steps.Add(new AppTemplateChangeDbMigratorPortSettingsStep("44300"));
if (context.BuildArgs.MobileApp == MobileApp.ReactNative)
{
steps.Add(new ReactEnvironmentFilePortChangeForSeparatedAuthServersStep());
}
}
else
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.HttpApi.Host"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.IdentityServer"));
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.AuthServer"));
steps.Add(new TemplateProjectRenameStep("MyCompanyName.MyProjectName.HttpApi.HostWithIds", "MyCompanyName.MyProjectName.HttpApi.Host"));
steps.Add(new AppTemplateChangeConsoleTestClientPortSettingsStep("44305"));
}
}
protected void RemoveUnnecessaryPorts(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps) protected void RemoveUnnecessaryPorts(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{ {
steps.Add(new RemoveUnnecessaryPortsStep()); steps.Add(new RemoveUnnecessaryPortsStep());
@ -599,7 +634,8 @@ public abstract class AppTemplateBase : TemplateInfo
{ {
if ((context.BuildArgs.UiFramework == UiFramework.Mvc if ((context.BuildArgs.UiFramework == UiFramework.Mvc
|| context.BuildArgs.UiFramework == UiFramework.Blazor || context.BuildArgs.UiFramework == UiFramework.Blazor
|| context.BuildArgs.UiFramework == UiFramework.BlazorServer) && || context.BuildArgs.UiFramework == UiFramework.BlazorServer
|| context.BuildArgs.UiFramework == UiFramework.MauiBlazor) &&
context.BuildArgs.MobileApp == MobileApp.None) context.BuildArgs.MobileApp == MobileApp.None)
{ {
steps.Add(new MoveFolderStep("/aspnet-core/", "/")); steps.Add(new MoveFolderStep("/aspnet-core/", "/"));

34
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorChangeApplicationIdGuidStep.cs

@ -0,0 +1,34 @@
using System;
using System.Linq;
using System.Xml;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.Utils;
namespace Volo.Abp.Cli.ProjectBuilding.Templates.App;
public class MauiBlazorChangeApplicationIdGuidStep: ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
var projectFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MyCompanyName.MyProjectName.MauiBlazor.csproj"));
if (projectFile == null)
{
return;
}
using (var stream = StreamHelper.GenerateStreamFromString(projectFile.Content))
{
var doc = new XmlDocument { PreserveWhitespace = true };
doc.Load(stream);
var node = doc.SelectSingleNode("/Project/PropertyGroup/ApplicationIdGuid");
if (node != null)
{
node.InnerText = Guid.NewGuid().ToString();
}
projectFile.SetContent(doc.OuterXml);
}
}
}

50
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/App/MauiBlazorPortChangeForSeparatedAuthServersStep.cs

@ -0,0 +1,50 @@
using System;
using System.Linq;
using Volo.Abp.Cli.ProjectBuilding.Building;
namespace Volo.Abp.Cli.ProjectBuilding.Templates.App;
public class MauiBlazorPortChangeForSeparatedAuthServersStep: ProjectBuildPipelineStep
{
public override void Execute(ProjectBuildContext context)
{
var appsettingsFile = context.Files.FirstOrDefault(x =>
!x.IsDirectory &&
x.Name.EndsWith("aspnet-core/src/MyCompanyName.MyProjectName.MauiBlazor/appsettings.json",
StringComparison.InvariantCultureIgnoreCase)
);
if(appsettingsFile == null)
{
return;
}
appsettingsFile.NormalizeLineEndings();
var lines = appsettingsFile.GetLines();
for (var i = 1; i < lines.Length; i++)
{
var line = lines[i];
var previousLine = lines[i-1];
if (line.Contains("Authority") && line.Contains("localhost"))
{
line = line.Replace("44305", "44301");
}
else if (previousLine.Contains("AbpAccountPublic") && line.Contains("BaseUrl") && line.Contains("localhost"))
{
line = line.Replace("44305", "44301");
}
else if (previousLine.Contains("Default") && line.Contains("BaseUrl") && line.Contains("localhost"))
{
line = line.Replace("44305", "44300");
}
lines[i] = line;
}
appsettingsFile.SetLines(lines);
}
}

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/Maui/MauiChangePortStep.cs

@ -53,6 +53,7 @@ public class MauiChangePortStep : ProjectBuildPipelineStep
{ {
case UiFramework.Angular: case UiFramework.Angular:
case UiFramework.Blazor: case UiFramework.Blazor:
case UiFramework.MauiBlazor:
authServerPort = "44305"; authServerPort = "44305";
apiHostPort = "44305"; apiHostPort = "44305";
break; break;

25
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/AbpLazyServiceProvider.cs

@ -1,15 +1,23 @@
using System; using System;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;
/// <summary>
/// This class is equivalent of the <see cref="TransientCachedServiceProvider"/>.
/// Use <see cref="TransientCachedServiceProvider"/> instead of this class, for new projects.
/// </summary>
[ExposeServices(typeof(IAbpLazyServiceProvider))] [ExposeServices(typeof(IAbpLazyServiceProvider))]
public class AbpLazyServiceProvider : CachedServiceProviderBase, IAbpLazyServiceProvider, ITransientDependency public class AbpLazyServiceProvider :
CachedServiceProviderBase,
IAbpLazyServiceProvider,
ITransientDependency
{ {
public AbpLazyServiceProvider(IServiceProvider serviceProvider) public AbpLazyServiceProvider(IServiceProvider serviceProvider)
: base(serviceProvider) : base(serviceProvider)
{ {
} }
public virtual T LazyGetRequiredService<T>() public virtual T LazyGetRequiredService<T>()
{ {
return (T)LazyGetRequiredService(typeof(T)); return (T)LazyGetRequiredService(typeof(T));
@ -17,7 +25,7 @@ public class AbpLazyServiceProvider : CachedServiceProviderBase, IAbpLazyService
public virtual object LazyGetRequiredService(Type serviceType) public virtual object LazyGetRequiredService(Type serviceType)
{ {
return GetService(serviceType); return this.GetRequiredService(serviceType);
} }
public virtual T LazyGetService<T>() public virtual T LazyGetService<T>()
@ -32,24 +40,21 @@ public class AbpLazyServiceProvider : CachedServiceProviderBase, IAbpLazyService
public virtual T LazyGetService<T>(T defaultValue) public virtual T LazyGetService<T>(T defaultValue)
{ {
return (T)LazyGetService(typeof(T), defaultValue); return GetService(defaultValue);
} }
public virtual object LazyGetService(Type serviceType, object defaultValue) public virtual object LazyGetService(Type serviceType, object defaultValue)
{ {
return LazyGetService(serviceType) ?? defaultValue; return GetService(serviceType, defaultValue);
} }
public virtual T LazyGetService<T>(Func<IServiceProvider, object> factory) public virtual T LazyGetService<T>(Func<IServiceProvider, object> factory)
{ {
return (T)LazyGetService(typeof(T), factory); return GetService<T>(factory);
} }
public virtual object LazyGetService(Type serviceType, Func<IServiceProvider, object> factory) public virtual object LazyGetService(Type serviceType, Func<IServiceProvider, object> factory)
{ {
return CachedServices.GetOrAdd( return GetService(serviceType, factory);
serviceType,
_ => new Lazy<object>(() => factory(ServiceProvider))
).Value;
} }
} }

1
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProvider.cs

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;

25
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/CachedServiceProviderBase.cs

@ -3,7 +3,7 @@ using System.Collections.Concurrent;
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;
public abstract class CachedServiceProviderBase public abstract class CachedServiceProviderBase : ICachedServiceProviderBase
{ {
protected IServiceProvider ServiceProvider { get; } protected IServiceProvider ServiceProvider { get; }
protected ConcurrentDictionary<Type, Lazy<object>> CachedServices { get; } protected ConcurrentDictionary<Type, Lazy<object>> CachedServices { get; }
@ -22,4 +22,27 @@ public abstract class CachedServiceProviderBase
_ => new Lazy<object>(() => ServiceProvider.GetService(serviceType)) _ => new Lazy<object>(() => ServiceProvider.GetService(serviceType))
).Value; ).Value;
} }
public T GetService<T>(T defaultValue)
{
return (T)GetService(typeof(T), defaultValue);
}
public object GetService(Type serviceType, object defaultValue)
{
return GetService(serviceType) ?? defaultValue;
}
public T GetService<T>(Func<IServiceProvider, object> factory)
{
return (T)GetService(typeof(T), factory);
}
public object GetService(Type serviceType, Func<IServiceProvider, object> factory)
{
return CachedServices.GetOrAdd(
serviceType,
_ => new Lazy<object>(() => factory(ServiceProvider))
).Value;
}
} }

40
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/IAbpLazyServiceProvider.cs

@ -2,21 +2,57 @@
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;
public interface IAbpLazyServiceProvider /// <summary>
/// This service is equivalent of the <see cref="ITransientCachedServiceProvider"/>.
/// Use <see cref="ITransientCachedServiceProvider"/> instead of this interface, for new projects.
/// </summary>
public interface IAbpLazyServiceProvider : ICachedServiceProviderBase
{ {
/// <summary>
/// This method is equivalent of the GetRequiredService method.
/// It does exists for backward compatibility.
/// </summary>
T LazyGetRequiredService<T>(); T LazyGetRequiredService<T>();
/// <summary>
/// This method is equivalent of the GetRequiredService method.
/// It does exists for backward compatibility.
/// </summary>
object LazyGetRequiredService(Type serviceType); object LazyGetRequiredService(Type serviceType);
/// <summary>
/// This method is equivalent of the GetService method.
/// It does exists for backward compatibility.
/// </summary>
T LazyGetService<T>(); T LazyGetService<T>();
/// <summary>
/// This method is equivalent of the GetService method.
/// It does exists for backward compatibility.
/// </summary>
object LazyGetService(Type serviceType); object LazyGetService(Type serviceType);
/// <summary>
/// This method is equivalent of the <see cref="ICachedServiceProviderBase.GetService{T}(T)"/> method.
/// It does exists for backward compatibility.
/// </summary>
T LazyGetService<T>(T defaultValue); T LazyGetService<T>(T defaultValue);
/// <summary>
/// This method is equivalent of the <see cref="ICachedServiceProviderBase.GetService(Type, object)"/> method.
/// It does exists for backward compatibility.
/// </summary>
object LazyGetService(Type serviceType, object defaultValue); object LazyGetService(Type serviceType, object defaultValue);
/// <summary>
/// This method is equivalent of the <see cref="ICachedServiceProviderBase.GetService(Type, Func{IServiceProvider, object})"/> method.
/// It does exists for backward compatibility.
/// </summary>
object LazyGetService(Type serviceType, Func<IServiceProvider, object> factory); object LazyGetService(Type serviceType, Func<IServiceProvider, object> factory);
/// <summary>
/// This method is equivalent of the <see cref="ICachedServiceProviderBase.GetService{T}(Func{IServiceProvider, object})"/> method.
/// It does exists for backward compatibility.
/// </summary>
T LazyGetService<T>(Func<IServiceProvider, object> factory); T LazyGetService<T>(Func<IServiceProvider, object> factory);
} }

6
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProvider.cs

@ -1,6 +1,4 @@
using System; namespace Volo.Abp.DependencyInjection;
namespace Volo.Abp.DependencyInjection;
/// <summary> /// <summary>
/// Provides services by caching the resolved services. /// Provides services by caching the resolved services.
@ -8,7 +6,7 @@ namespace Volo.Abp.DependencyInjection;
/// This service's lifetime is scoped and it should be used /// This service's lifetime is scoped and it should be used
/// for a limited scope. /// for a limited scope.
/// </summary> /// </summary>
public interface ICachedServiceProvider : IServiceProvider public interface ICachedServiceProvider : ICachedServiceProviderBase
{ {
} }

14
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ICachedServiceProviderBase.cs

@ -0,0 +1,14 @@
using System;
namespace Volo.Abp.DependencyInjection;
public interface ICachedServiceProviderBase : IServiceProvider
{
T GetService<T>(T defaultValue);
object GetService(Type serviceType, object defaultValue);
T GetService<T>(Func<IServiceProvider, object> factory);
object GetService(Type serviceType, Func<IServiceProvider, object> factory);
}

4
framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ITransientCachedServiceProvider.cs

@ -1,5 +1,3 @@
using System;
namespace Volo.Abp.DependencyInjection; namespace Volo.Abp.DependencyInjection;
/// <summary> /// <summary>
@ -8,7 +6,7 @@ namespace Volo.Abp.DependencyInjection;
/// This service's lifetime is transient. /// This service's lifetime is transient.
/// <see cref="ICachedServiceProvider"/> for the one with scoped lifetime. /// <see cref="ICachedServiceProvider"/> for the one with scoped lifetime.
/// </summary> /// </summary>
public interface ITransientCachedServiceProvider : IServiceProvider public interface ITransientCachedServiceProvider : ICachedServiceProviderBase
{ {
} }
Loading…
Cancel
Save