Preparing the Database

docker run --name postgres -e POSTGRES_PASSWORD=postgres -e TZ=Asia/Shanghai -d -p 5432:5432 postgres
docker run --name pgadmin -e PGADMIN_DEFAULT_EMAIL=test@test.com -e PGADMIN_DEFAULT_PASSWORD=test -e TZ=Asia/Shanghai -d -p 5050:80 dpage/pgadmin4

Initializing the Dapr Runtime

dapr init -s --from-dir C:\dapr

Extending the Wait Time for RabbitMQ Startup

public static IResourceBuilder<IDaprSidecarResource> WithReference(this IResourceBuilder<IDaprSidecarResource> builder, IResourceBuilder<IResourceWithConnectionString> resourceBuilder, int waitInSeconds = 10)

Updating MediatR Dependency Injection

Previously, behaviors were registered with the options.AddBehavior method; now they are registered with the options.AddOpenBehavior method.


builder.Services.AddMediatR(options =>
{
    options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
    options.AddOpenBehavior(typeof(LoggingBehavior<,>));
    options.AddOpenBehavior(typeof(ValidatorBehavior<,>));
    options.AddOpenBehavior(typeof(TransactionBehavior<,>));
});

Microservices Using Distributed Events Need the HttpContext Injected

builder.Services.AddHttpContextAccessor();

Dropping a Database in Azure Data Studio

-- Terminate all sessions connected to the target database
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = 'my_database'
  AND pid <> pg_backend_pid();

-- Drop the database
DROP DATABASE my_database;

Automatically Creating the Database Before IdentityService Demo Data Generation

await serviceProvider.GetRequiredService<IdentityServiceDbContext>().Database.EnsureCreatedAsync(cancellationToken);

Consistently Use DateTimeOffset Instead of DateTime

The advantage of using UTC time is that it can be converted between time zones without losing time information, which helps programs handle time better and benefits the internationalization and localization of cross-time-zone applications.

var utcNow = DateTimeOffset.UtcNow;

When displaying time, you can use the ToLocalTime method to convert it to local time.

var localTime = utcNow.LocalDateTime;

The time type in PostgreSQL databases is timestamp with time zone, which converts time to UTC for storage.

All times in a PostgreSQL database are UTC, but they are converted to local time according to the time zone when queried. This process is transparent and automatic.

Remove the previously configured, inelegant time conversion settings.

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);

Prefer TimeProvider for Getting the Current Time

Use the TimeProvider abstract class whenever possible, because it makes it easier to mock time in tests.

var now = TimeProvider.System.Now;
var utcNow = TimeProvider.System.UtcNow;

which is equivalent to

var now = DateTimeOffset.Now;
var utcNow = DateTimeOffset.UtcNow;
public class MyTimeProvider: TimeProvider
{
    public override DateTimeOffset Now => new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero);
    public override DateTimeOffset UtcNow => new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero);
}
services.AddSingleton<TimeProvider, MyTimeProvider>();
public class MyService
{
    private readonly TimeProvider _timeProvider;

    public MyService(TimeProvider timeProvider)
    {
        _timeProvider = timeProvider;
    }

    public void DoSomething()
    {
        var now = _timeProvider.Now;
        var utcNow = _timeProvider.UtcNow;
    }
}

Configuring OrderingService Authentication

  const string issuerSigningKey = ServiceDefaults.Constants.IdentityConstantsIssuerSigningKey;

  builder.Services.AddAuthentication().AddJwtBearer(options =>
  {
      options.TokenValidationParameters.ValidateIssuer = false;
      options.TokenValidationParameters.ValidateAudience = false;
      options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(issuerSigningKey));
  });

Configuring OrderingService Object Mapping

    public class OrdersMapConfiguration : Profile
    {
        public OrdersMapConfiguration()
        {
            CreateMap<CreateOrderRequest, CreateOrderCommand>().ForMember(dest => dest.OrderItems, opt => opt.MapFrom(src => src.Items));
            CreateMap<BasketItem, CreateOrderCommand.CreateOrderCommandItem>().ForMember(dest => dest.Units, opt => opt.MapFrom(src => src.Quantity));
            CreateMap<CreateOrderCommand.CreateOrderCommandItem, OrderItem>();
            CreateMap<CreateOrderCommand, Address>();
        }
    }

Configuring the Field Lengths of the Product Entity in ProductService

public class ProductEntityTypeConfiguration : IEntityTypeConfiguration<Product>
{
    public void Configure(EntityTypeBuilder<Product> builder)
    {
        builder.Property(x => x.Name).HasMaxLength(64);
    }
}