mirror of https://github.com/abpframework/abp.git
61 changed files with 825 additions and 194 deletions
@ -0,0 +1,30 @@ |
|||
# New Containers feature with NET 8.0 |
|||
|
|||
This article will show you the new feature of containers with NET 8.0. |
|||
|
|||
## Non-root user |
|||
|
|||
The `Non-root user` feature on net 8 is a security measure that allows users to have limited access to the system without having full administrative privileges. Hosting containers as `non-root` aligns with the principle of least privilege. |
|||
It’s free security provided by the operating system. If you run your app as root, your app process can do anything in the container, like modify files, install packages, or run arbitrary executables. |
|||
That’s a concern if your app is ever attacked. If you run your app as non-root, your app process cannot do much, greatly limiting what a bad actor could accomplish. |
|||
|
|||
## Default ASP.NET Core port changed from 80 to 8080 |
|||
|
|||
In .NET 8, there has been a change in the default port used by ASP.NET Core applications. Previously, the default port assigned to ASP.NET Core applications was `80`. However, starting from .NET 8, the default port has been changed to `8080`. |
|||
This change was made to avoid conflicts with other applications and services that commonly use port 80, such as web servers like IIS or Apache. By using port 8080 as the default, there is less potential for clashes and easier deployment of ASP.NET Core applications alongside other services. |
|||
|
|||
It's important to note that this change only affects the default port used when an ASP.NET Core application is run without explicitly specifying a port. |
|||
|
|||
If you want your application to continue using port 80, you can still specify it during the application launch or configure it in the application settings. |
|||
|
|||
* Recommended: Explicitly set the `ASPNETCORE_HTTP_PORTS`, `ASPNETCORE_HTTPS_PORTS``, and `ASPNETCORE_URLS` environment variables to the desired port. Example: `docker run --rm -it -p 9999:80 -e ASPNETCORE_HTTP_PORTS=80 <my-app>`` |
|||
* Update existing commands and configuration that rely on the expected default port of port 80 to reference port 8080 instead. Example: `docker run --rm -it -p 9999:8080 <my-app>`` |
|||
|
|||
> The `dockerfile` of ABP templates has been updated to use port `80`. |
|||
|
|||
## References |
|||
|
|||
- [Secure your .NET cloud apps with rootless Linux Containers](https://devblogs.microsoft.com/dotnet/securing-containers-with-rootless/) |
|||
- [Containers breaking changes](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#containers) |
|||
- [ASP.NET Core apps use port 8080 by default](https://learn.microsoft.com/en-us/dotnet/core/compatibility/8.0#containers) |
|||
- [Docker images for ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-8.0) |
|||
@ -0,0 +1,53 @@ |
|||
# New Raw SQL queries for unmapped types feature with EF Core 8.0 |
|||
|
|||
## Introduction |
|||
|
|||
I would love to talk about the new feature in EF Core 8.0, specifically the `raw SQL queries for unmapped types`. |
|||
This feature was recently introduced by Microsoft and is aimed at providing more flexibility and customization in database queries. |
|||
|
|||
## What is the raw SQL queries for the unmapped types feature? |
|||
|
|||
To give you a better understanding, let's look at a sample repository method with the ABP framework. |
|||
Here is an example of a raw SQL query using the new feature: |
|||
|
|||
````csharp |
|||
public interface IAuthorRepository : IRepository<Author, Guid> |
|||
{ |
|||
Task<List<AuthorIdWithNames>> GetAllAuthorNamesAsync(); |
|||
} |
|||
|
|||
public class AuthorIdWithNames |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
} |
|||
|
|||
public class EfCoreAuthorRepository : EfCoreRepository<BookStoreDbContext, Author, Guid>, IAuthorRepository |
|||
{ |
|||
public EfCoreAuthorRepository(IDbContextProvider<BookStoreDbContext> dbContextProvider) |
|||
: base(dbContextProvider) |
|||
{ |
|||
} |
|||
|
|||
public virtual async Task<List<AuthorIdWithNames>> GetAllAuthorNamesAsync() |
|||
{ |
|||
return await (await GetDbContextAsync()).Database.SqlQuery<AuthorIdWithNames>(@$"SELECT Id, Name FROM Authors").ToListAsync(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In this code, we can see that we are using the `SqlQuery` method to execute a raw SQL query on a custom type, `AuthorIdWithNames` in this case. This allows us to retrieve data that may not be mapped to any of our entity classes in the context. |
|||
|
|||
## In summary |
|||
|
|||
This feature can be particularly useful in scenarios where we need to access data from tables or views that are not directly mapped to our entities. It also provides an alternative to using stored procedures for querying data. |
|||
|
|||
However, it's important to note that using raw SQL queries can increase the risk of SQL injection attacks. So, it's recommended to use parameterized queries to prevent this. Additionally, this feature may not work with certain database providers, so it's important to check for compatibility before implementing it. |
|||
|
|||
In conclusion, the raw SQL queries for unmapped types feature in EF Core 8.0 is a great addition for developers looking for more flexibility in database queries. It allows us to work with data that may not be directly mapped to our entities and can be a useful tool in certain scenarios. Just remember to use parameterized queries and check for compatibility before implementing it. |
|||
|
|||
## References |
|||
|
|||
- [Raw SQL queries for unmapped types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#raw-sql-queries-for-unmapped-types) |
|||
- [SQL Queries](https://learn.microsoft.com/en-us/ef/core/querying/sql-queries#querying-scalar-(non-entity)-types) |
|||
@ -0,0 +1,189 @@ |
|||
# Using Complex Types as Value Objects with Entity Framework Core 8.0 |
|||
|
|||
Entity Framework Core 8.0 is being shipped in a week as a part of .NET 8.0. In this article, I will introduce the new **[Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types)** feature of EF Core 8 and show some examples of how you can use it in your projects built with ABP Framework. |
|||
|
|||
## What is a Value Object? |
|||
|
|||
A [Value Object](https://docs.abp.io/en/abp/latest/Value-Objects) is a simple object that has no conceptual identity (Id). Instead, a Value Object is identified by its properties. |
|||
|
|||
A Value Object is typically owned by a parent [Entity](https://docs.abp.io/en/abp/latest/Entities) object. Using [Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) with EF Core 8 is the best way to create Value Objects that are stored as a part of your main entity. |
|||
|
|||
## Creating an ABP Project |
|||
|
|||
> **WARNING**: ABP Framework has not a .NET 8.0 compatible version yet. It is planned to be released on November 15, 2023. I created this project with ABP 7.4.1 (based on .NET 7.0), then manually changed the `TargetFramework` (in the `csproj`) to `net8.0` and added the `Microsoft.EntityFrameworkCore.SqlServer` package with version `8.0.0-rc.2.23480.1`. After that, the project is being compiled, but some parts of this article may not work as expected until ABP Framework 8.0-RC.1 is released. |
|||
> |
|||
> **I will update the article once ABP Framework 8.0-RC.1 is released.** |
|||
|
|||
I will show code examples, so I am creating a new ABP project using the following [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) command: |
|||
|
|||
````bash |
|||
abp new ComplexTypeDemo -t app-nolayers |
|||
```` |
|||
|
|||
> I prefer a non-layered project to keep the things simple. If you are new to ABP Framework, follow the [Getting Started](https://docs.abp.io/en/abp/latest/Getting-Started-Overall) tutorial to learn how to create a new project from scratch. |
|||
|
|||
Once I created the solution, I am running the following command to execute the database migrations in order to create the initial database: |
|||
|
|||
````bash |
|||
dotnet run --project ComplexTypeDemo --migrate-database |
|||
```` |
|||
|
|||
> We could also execute the `migrate-database.ps1` (that is coming as a part of the solution) as a shortcut. |
|||
|
|||
## Creating a Complex Type |
|||
|
|||
Assume that we have a `Customer` [entity](https://docs.abp.io/en/abp/latest/Entities) as shown below: |
|||
|
|||
````csharp |
|||
public class Customer : BasicAggregateRoot<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
public Address HomeAddress { get; set; } |
|||
public Address BusinessAddress { get; set; } |
|||
} |
|||
```` |
|||
|
|||
Here, we have two address properties, one for home and the other one for business. Since an address is a multi-values property, we can define it as a separate object: |
|||
|
|||
````csharp |
|||
public class Address |
|||
{ |
|||
public string City { get; set; } |
|||
public string Line1 { get; set; } |
|||
public string? Line2 { get; set; } |
|||
public string PostCode { get; set; } |
|||
} |
|||
```` |
|||
|
|||
`Address` is a typical complex object. It is actually a part of the `Customer` object, but we wanted to collect its parts into a dedicated class to make it a domain concept and easily manage its properties together. |
|||
|
|||
## Configure EF Core Mappings |
|||
|
|||
We should set the `Address` class as a Complex Type in our EF Core mapping configuration. There are two ways of it. |
|||
|
|||
As the first way, we can add the `ComplexType` attribute on top of the `Address` class: |
|||
|
|||
````csharp |
|||
[ComplexType] // Added this line |
|||
public class Address |
|||
{ |
|||
... |
|||
} |
|||
```` |
|||
|
|||
As an alternative, we can configure the mapping using the fluent mapping API. You can write the following code into the `OnModelCreating` method of your `DbContext` class: |
|||
|
|||
````csharp |
|||
builder.Entity<Customer>(b => |
|||
{ |
|||
b.ToTable("Customers"); |
|||
b.ComplexProperty(x => x.HomeAddress); // Mapping a Complex Type |
|||
b.ComplexProperty(x => x.BusinessAddress); // Mapping another Complex Type |
|||
//... configure other properties |
|||
}); |
|||
|
|||
```` |
|||
|
|||
You can further configure the properties of the `Address` class: |
|||
|
|||
````csharp |
|||
b.ComplexProperty(x => x.HomeAddress, a => |
|||
{ |
|||
a.Property(x => x.City).HasMaxLength(50).IsRequired(); |
|||
}); |
|||
```` |
|||
|
|||
Once you configure the mappings, you can use the [EF Core command-line tool](https://learn.microsoft.com/en-us/ef/core/cli/dotnet) to create a database migration: |
|||
|
|||
````bash |
|||
dotnet ef migrations add "Added_Customer_And_Address" |
|||
```` |
|||
|
|||
And update the database: |
|||
|
|||
````bash |
|||
dotnet ef database update |
|||
```` |
|||
|
|||
If you check the fields of the `Customers` table in your dayabase, you will see the following fields: |
|||
|
|||
* `Id` |
|||
* `Name` |
|||
* `HomeAddress_City` |
|||
* `HomeAddress_Line1` |
|||
* `HomeAddress_Line2` |
|||
* `HomeAddress_PostCode` |
|||
* `BusinessAddress_City` |
|||
* `BusinessAddress_Line1` |
|||
* `BusinessAddress_Line2` |
|||
* `BusinessAddress_PostCode` |
|||
|
|||
As you see, EF Core stores the `Address` properties as a part of your main entity. |
|||
|
|||
## Querying Objects |
|||
|
|||
You can query entities from database using the properties of a complex type as same as you query by the properties of the main entity. |
|||
|
|||
**Example: Find customers by `BusinessAddress.PostCode`:** |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IRepository<Customer, Guid> _customerRepository; |
|||
|
|||
public MyService(IRepository<Customer, Guid> customerRepository) |
|||
{ |
|||
_customerRepository = customerRepository; |
|||
} |
|||
|
|||
public async Task DemoAsync() |
|||
{ |
|||
var customers = await _customerRepository.GetListAsync( |
|||
c => c.BusinessAddress.PostCode == "12345" |
|||
); |
|||
|
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
## Mutable vs Immutable |
|||
|
|||
Entity Framework Core Complex Types can work with mutable and immutable types. In the `Address` example above, I've shown a simple mutable class - that means you can change an individual property of an `Address` object after creating it (or after querying from database). However, designing Value Objects as immutable is a highly common approach. |
|||
|
|||
For example, you can use C#'s `struct` type to define an immutable `Address` type: |
|||
|
|||
````csharp |
|||
public readonly struct Address(string line1, string? line2, string city, string postCode) |
|||
{ |
|||
public string City { get; } = city; |
|||
public string Line1 { get; } = line1; |
|||
public string? Line2 { get; } = line2; |
|||
public string PostCode { get; } = postCode; |
|||
} |
|||
```` |
|||
|
|||
See the [Microsoft's documentation](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#mutability) for more examples and different usages. |
|||
|
|||
## Final Notes |
|||
|
|||
There are more details about using Complex Types in your applications. I want to mention some of them here: |
|||
|
|||
* You can have nested complex types. For example, `Address` may have one or more `PhoneNumber` objects as its properties. Everything will work seamlessly. |
|||
* A single instance of a Complex Type can be set to multiple properties (of the same or different entities). In that case, changing the Complex object's properties will affect all of the properties. However, try to avoid that since it may create unnecessary complexities in your code that is hard to understand. |
|||
* You can manipulate mutable complex object properties just as another property in your entity. EF Core change tracking system will track them as you expect. |
|||
* Currently, EF Core doesn't support to have a collection of complex objects in an entity. It works only for properties. |
|||
|
|||
For more details and examples, see the Microsoft's document in the *References* section. |
|||
|
|||
## Source Code |
|||
|
|||
You can find the sample project here: |
|||
|
|||
https://github.com/hikalkan/samples/tree/master/EfCoreComplexTypeDemo |
|||
|
|||
## References |
|||
|
|||
* [Value objects using Complex Types](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#value-objects-using-complex-types) in [What's new with EF Core 8.0](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew) document by Microsoft. |
|||
* [ABP Entity Framework Core integration document](https://docs.abp.io/en/abp/latest/Entity-Framework-Core) |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
# CMS Kit |
|||
|
|||
## Updating Client Proxies |
|||
|
|||
This project have 3 types of client proxies. Before updating client proxies, make sure `Volo.CmsKit.Web.Unified` project is running. |
|||
Then you can update Client proxies in 3 different projects. Execute the following commands in the directory of the each project. |
|||
|
|||
- CMS Kit Public (**Volo.CmsKit.Public.HttpApi.Client**) |
|||
```bash |
|||
abp generate-proxy -t csharp -url https://localhost:44349 -m cms-kit --without-contracts |
|||
``` |
|||
|
|||
- CMS Kit Common (**Volo.CmsKit.Common.HttpApi.Client**) |
|||
|
|||
```bash |
|||
abp generate-proxy -t csharp -url https://localhost:44349 -m cms-kit-common --without-contracts |
|||
``` |
|||
|
|||
- CMS Kit Admin (**Volo.CmsKit.Admin.HttpApi.Client**) |
|||
|
|||
```bash |
|||
abp generate-proxy -t csharp -url https://localhost:44349 -m cms-kit-admin --without-contracts |
|||
``` |
|||
|
|||
@ -0,0 +1,28 @@ |
|||
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Client.ClientProxying; |
|||
using Volo.Abp.Http.Modeling; |
|||
using Volo.CmsKit.Blogs; |
|||
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace Volo.CmsKit.Blogs; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IBlogFeatureAppService), typeof(BlogFeatureClientProxy))] |
|||
public partial class BlogFeatureClientProxy : ClientProxyBase<IBlogFeatureAppService>, IBlogFeatureAppService |
|||
{ |
|||
public virtual async Task<BlogFeatureDto> GetOrDefaultAsync(Guid blogId, string featureName) |
|||
{ |
|||
return await RequestAsync<BlogFeatureDto>(nameof(GetOrDefaultAsync), new ClientProxyRequestTypeValue |
|||
{ |
|||
{ typeof(Guid), blogId }, |
|||
{ typeof(string), featureName } |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
// This file is part of BlogFeatureClientProxy, you can customize it here
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace Volo.CmsKit.Blogs; |
|||
|
|||
public partial class BlogFeatureClientProxy |
|||
{ |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
// This file is automatically generated by ABP framework to use MVC Controllers from CSharp
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Content; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Client.ClientProxying; |
|||
using Volo.Abp.Http.Modeling; |
|||
using Volo.CmsKit.MediaDescriptors; |
|||
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace Volo.CmsKit.MediaDescriptors; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(IMediaDescriptorAppService), typeof(MediaDescriptorClientProxy))] |
|||
public partial class MediaDescriptorClientProxy : ClientProxyBase<IMediaDescriptorAppService>, IMediaDescriptorAppService |
|||
{ |
|||
public virtual async Task<RemoteStreamContent> DownloadAsync(Guid id) |
|||
{ |
|||
return await RequestAsync<RemoteStreamContent>(nameof(DownloadAsync), new ClientProxyRequestTypeValue |
|||
{ |
|||
{ typeof(Guid), id } |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
// This file is part of MediaDescriptorClientProxy, you can customize it here
|
|||
// ReSharper disable once CheckNamespace
|
|||
namespace Volo.CmsKit.MediaDescriptors; |
|||
|
|||
public partial class MediaDescriptorClientProxy |
|||
{ |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Volo.CmsKit.Web; |
|||
|
|||
public static class CmsBlogsWebConsts |
|||
{ |
|||
public static string BlogsRoutePrefix { get; set; } = "blogs"; |
|||
} |
|||
Loading…
Reference in new issue