This commit is contained in:
hello 2024-07-11 16:48:22 +08:00
commit 3b631e495f
53 changed files with 488 additions and 84 deletions

View File

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.Mvc; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MultiTenancySample.DatabaseIsolationService.Entities; using MultiTenancySample.DatabaseIsolationService.Entities;
using MultiTenancySample.DatabaseIsolationService.EntityFrameworks; using MultiTenancySample.DatabaseIsolationService.EntityFrameworks;
@ -23,5 +26,14 @@ namespace MultiTenancySample.DatabaseIsolationService.Controllers
return Ok(products); return Ok(products);
} }
[HttpPost]
public async Task<IActionResult> CreateProduct(Product product)
{
await _dbContext.AddAsync(product);
await _dbContext.SaveChangesAsync();
return Ok(product);
}
} }
} }

View File

@ -1,4 +1,7 @@
namespace MultiTenancySample.DatabaseIsolationService.Entities // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace MultiTenancySample.DatabaseIsolationService.Entities
{ {
public class Product public class Product
{ {

View File

@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore;
using MultiTenancySample.DatabaseIsolationService.Entities; using MultiTenancySample.DatabaseIsolationService.Entities;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;

View File

@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore;
using MultiTenancySample.DatabaseIsolationService.Entities; using MultiTenancySample.DatabaseIsolationService.Entities;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;

View File

@ -1,3 +1,6 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using MultiTenancySample.DatabaseIsolationService.EntityFrameworks; using MultiTenancySample.DatabaseIsolationService.EntityFrameworks;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;

View File

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.Mvc; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MultiTenancySample.FieldIsolationService.Entities; using MultiTenancySample.FieldIsolationService.Entities;
using MultiTenancySample.FieldIsolationService.EntityFrameworks; using MultiTenancySample.FieldIsolationService.EntityFrameworks;
@ -23,5 +26,15 @@ namespace MultiTenancySample.FieldIsolationService.Controllers
return Ok(products); return Ok(products);
} }
[HttpPost]
public async Task<IActionResult> CreateProduct(Product product)
{
await _dbContext.AddAsync(product);
await _dbContext.SaveChangesAsync();
return Ok(product);
}
} }
} }

View File

@ -1,4 +1,7 @@
namespace MultiTenancySample.FieldIsolationService.Entities // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace MultiTenancySample.FieldIsolationService.Entities
{ {
public interface IMultiTenant public interface IMultiTenant
{ {

View File

@ -1,4 +1,7 @@
using System.Text.Json.Serialization; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using System.Text.Json.Serialization;
namespace MultiTenancySample.FieldIsolationService.Entities namespace MultiTenancySample.FieldIsolationService.Entities
{ {

View File

@ -1,16 +1,19 @@
using Microsoft.EntityFrameworkCore; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore;
using MultiTenancySample.FieldIsolationService.Entities; using MultiTenancySample.FieldIsolationService.Entities;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;
namespace MultiTenancySample.FieldIsolationService.EntityFrameworks namespace MultiTenancySample.FieldIsolationService.EntityFrameworks
{ {
public class DataSeeding(IDbContextFactory<FieldIsolationServiceDbContext> dbContext, ICurrentTenant currentTenant) public class DataSeeding(IDbContextFactory<FieldIsolationServiceDbContext> dbContextFactory, ICurrentTenant currentTenant)
{ {
public async Task SeedDataAsync() public async Task SeedDataAsync()
{ {
currentTenant.SetTenant("Tenant1"); currentTenant.SetTenant("Tenant1");
FieldIsolationServiceDbContext dbContext1 = await dbContext.CreateDbContextAsync(); FieldIsolationServiceDbContext dbContext1 = await dbContextFactory.CreateDbContextAsync();
if (await dbContext1.Database.EnsureCreatedAsync()) if (await dbContext1.Database.EnsureCreatedAsync())
{ {
@ -25,7 +28,7 @@ namespace MultiTenancySample.FieldIsolationService.EntityFrameworks
currentTenant.SetTenant("Tenant2"); currentTenant.SetTenant("Tenant2");
FieldIsolationServiceDbContext dbContext2 = await dbContext.CreateDbContextAsync(); FieldIsolationServiceDbContext dbContext2 = await dbContextFactory.CreateDbContextAsync();
if (await dbContext2.Set<Product>().IgnoreQueryFilters().CountAsync() < 6) if (await dbContext2.Set<Product>().IgnoreQueryFilters().CountAsync() < 6)
{ {

View File

@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query;
using System.Linq.Expressions; using System.Linq.Expressions;

View File

@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using MultiTenancySample.FieldIsolationService.Entities; using MultiTenancySample.FieldIsolationService.Entities;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;

View File

@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using MultiTenancySample.FieldIsolationService.Entities; using MultiTenancySample.FieldIsolationService.Entities;

View File

@ -1,3 +1,6 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MultiTenancySample.FieldIsolationService.EntityFrameworks; using MultiTenancySample.FieldIsolationService.EntityFrameworks;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;

View File

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.Mvc; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MultiTenancySample.SchemaIsolationService.Entities; using MultiTenancySample.SchemaIsolationService.Entities;
using MultiTenancySample.SchemaIsolationService.EntityFrameworks; using MultiTenancySample.SchemaIsolationService.EntityFrameworks;

View File

@ -1,4 +1,7 @@
namespace MultiTenancySample.SchemaIsolationService.Entities // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace MultiTenancySample.SchemaIsolationService.Entities
{ {
public class Product public class Product
{ {

View File

@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore;
using MultiTenancySample.SchemaIsolationService.Entities; using MultiTenancySample.SchemaIsolationService.Entities;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;

View File

@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore;
using MultiTenancySample.SchemaIsolationService.Entities; using MultiTenancySample.SchemaIsolationService.Entities;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;

View File

@ -1,3 +1,6 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MultiTenancySample.SchemaIsolationService.EntityFrameworks; using MultiTenancySample.SchemaIsolationService.EntityFrameworks;
using MultiTenancySample.ServiceDefaults; using MultiTenancySample.ServiceDefaults;

View File

@ -1,4 +1,7 @@
namespace MultiTenancySample.ServiceDefaults // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace MultiTenancySample.ServiceDefaults
{ {
public class CurrentTenant : ICurrentTenant public class CurrentTenant : ICurrentTenant
{ {

View File

@ -1,4 +1,7 @@
namespace MultiTenancySample.ServiceDefaults // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace MultiTenancySample.ServiceDefaults
{ {
public interface ICurrentTenant public interface ICurrentTenant
{ {

View File

@ -1,4 +1,7 @@
namespace MultiTenancySample.ServiceDefaults // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace MultiTenancySample.ServiceDefaults
{ {
public interface ITenantIdProvider public interface ITenantIdProvider
{ {

View File

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.Builder; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace MultiTenancySample.ServiceDefaults namespace MultiTenancySample.ServiceDefaults

View File

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.Http; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.AspNetCore.Http;
namespace MultiTenancySample.ServiceDefaults namespace MultiTenancySample.ServiceDefaults
{ {
@ -10,6 +13,11 @@ namespace MultiTenancySample.ServiceDefaults
const string tenantKey = "tenant"; const string tenantKey = "tenant";
if (httpContext.User.FindAll(tenantKey).Any())
{
return httpContext.User.FindFirst(tenantKey)?.Value;
}
if (httpContext.Request.Headers.TryGetValue(tenantKey, out var headerValues)) if (httpContext.Request.Headers.TryGetValue(tenantKey, out var headerValues))
{ {
return headerValues.First(); return headerValues.First();

View File

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.Http; // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Microsoft.AspNetCore.Http;
namespace MultiTenancySample.ServiceDefaults namespace MultiTenancySample.ServiceDefaults
{ {

View File

@ -1,6 +0,0 @@
{
"sdk": {
"version": "8.0.204",
"rollForward": "latestFeature"
}
}

View File

@ -21,7 +21,8 @@ public class OpenApiConfigureOptions(IConfiguredServiceEndPointResolver serviceR
try try
{ {
HttpResponseMessage response = httpClient.GetAsync(uriBuilder.Uri).GetAwaiter().GetResult(); HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri) { Version = new Version(2, 0) };
HttpResponseMessage response = httpClient.SendAsync(request).GetAwaiter().GetResult();
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
urlDescriptors.Add(new UrlDescriptor urlDescriptors.Add(new UrlDescriptor

View File

@ -9,6 +9,6 @@
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" /> <ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery.Yarp" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.ServiceDiscovery.Yarp" Version="8.0.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -15,6 +15,6 @@
<ProjectReference Include="..\HelloShop.WebApp\HelloShop.WebApp.csproj" /> <ProjectReference Include="..\HelloShop.WebApp\HelloShop.WebApp.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.0.1" /> <PackageReference Include="Aspire.Hosting.AppHost" Version="8.0.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,19 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using AutoMapper;
using HelloShop.BasketService.Entities;
using HelloShop.BasketService.Protos;
namespace HelloShop.BasketService.AutoMapper
{
public class BasketsMapConfiguration : Profile
{
public BasketsMapConfiguration()
{
CreateMap<CustomerBasket, CustomerBasketResponse>();
CreateMap<BasketItem, BasketListItem>().ReverseMap();
CreateMap<UpdateBasketRequest, CustomerBasket>();
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.BasketService.Entities
{
public class BasketItem
{
public int ProductId { get; set; }
public int Quantity { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.BasketService.Entities
{
public class CustomerBasket
{
public int BuyerId { get; set; }
public List<BasketItem> Items { get; set; } = [];
}
}

View File

@ -1,16 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> <IncludeHttpRuleProtos>true</IncludeHttpRuleProtos>
<ItemGroup> </PropertyGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" /> <ItemGroup>
</ItemGroup> <None Remove="Protos\basket.proto" />
<ItemGroup> </ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.63.0" /> <ItemGroup>
</ItemGroup> <Protobuf Include="Protos\basket.proto" GrpcServices="Server" />
<ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" /> </ItemGroup>
</ItemGroup> <ItemGroup>
<PackageReference Include="Calzolari.Grpc.AspNetCore.Validation" Version="8.1.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.63.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Grpc.Swagger" Version="0.8.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,22 +1,61 @@
// Copyright (c) HelloShop Corporation. All rights reserved. // Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information. // See the license file in the project root for more information.
using Calzolari.Grpc.AspNetCore.Validation;
using HelloShop.BasketService.Repositories;
using HelloShop.BasketService.Services; using HelloShop.BasketService.Services;
using HelloShop.ServiceDefaults.Extensions; using HelloShop.ServiceDefaults.Extensions;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); builder.AddServiceDefaults();
// Add services to the container. // Add extensions services to the container.
builder.Services.AddGrpc();
const string issuerSigningKey = HelloShop.ServiceDefaults.Constants.IdentityConstants.IssuerSigningKey;
builder.Services.AddAuthentication().AddJwtBearer(options =>
{
options.TokenValidationParameters.ValidateIssuer = false;
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(issuerSigningKey));
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSingleton<IBasketRepository, DistributedCacheBasketRepository>();
builder.Services.AddGrpc(options => options.EnableMessageValidation()).AddJsonTranscoding();
builder.Services.AddGrpcSwagger();
builder.Services.AddOpenApi();
builder.Services.AddDataSeedingProviders();
builder.Services.AddCustomLocalization();
builder.Services.AddModelMapper().AddModelValidator();
builder.Services.AddLocalization().AddPermissionDefinitions();
builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization();
builder.Services.AddGrpcValidation();
builder.Services.AddCors();
// End addd extensions services to the container.
var app = builder.Build(); var app = builder.Build();
app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.MapDefaultEndpoints(); app.MapDefaultEndpoints();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
app.MapGrpcService<GreeterService>(); app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.").WithTags("Welcome");
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
// Configure extensions request pipeline.
app.UseAuthentication().UseAuthorization();
app.MapGrpcService<GreeterService>();
app.MapGrpcService<CustomerBasketService>();
app.UseDataSeedingProviders();
app.UseCustomLocalization();
app.UseOpenApi();
app.MapGroup("api/Permissions").MapPermissionDefinitions("Permissions");
// End configure extensions request pipeline.
app.Run(); app.Run();

View File

@ -4,7 +4,8 @@
"http": { "http": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:8004", "applicationUrl": "http://localhost:8004",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
@ -13,7 +14,8 @@
"https": { "https": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:8104;http://localhost:8004", "applicationUrl": "https://localhost:8104;http://localhost:8004",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"

View File

@ -0,0 +1,42 @@
syntax = "proto3";
import "google/protobuf/empty.proto";
import "google/api/annotations.proto";
option csharp_namespace = "HelloShop.BasketService.Protos";
package basket;
service Basket {
rpc GetBasket(google.protobuf.Empty) returns (CustomerBasketResponse) {
option (google.api.http) = {
get: "/api/basket"
};
}
rpc UpdateBasket(UpdateBasketRequest) returns (CustomerBasketResponse) {
option (google.api.http) = {
put: "/api/basket"
body: "*"
};
}
rpc DeleteBasket(google.protobuf.Empty) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/api/basket"
};
}
}
message CustomerBasketResponse {
repeated BasketListItem items = 1;
}
message BasketListItem {
int32 product_id = 1;
int32 quantity = 2;
}
message UpdateBasketRequest {
repeated BasketListItem items = 1;
}

View File

@ -1,13 +1,20 @@
syntax = "proto3"; syntax = "proto3";
option csharp_namespace = "HelloShop.BasketService"; option csharp_namespace = "HelloShop.BasketService.Protos";
import "google/api/annotations.proto";
package greet; package greet;
// The greeting service definition. // The greeting service definition.
service Greeter { service Greeter {
// Sends a greeting // Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply); rpc SayHello (HelloRequest) returns (HelloReply){
option (google.api.http) = {
post: "/api/greet"
body: "*"
};
}
} }
// The request message containing the user's name. // The request message containing the user's name.

View File

@ -0,0 +1,28 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.BasketService.Entities;
using Microsoft.Extensions.Caching.Distributed;
namespace HelloShop.BasketService.Repositories
{
public class DistributedCacheBasketRepository(IDistributedCache cache) : IBasketRepository
{
private const string BasketKeyPrefix = "basket";
private static string GetBasketKey(int customerId) => $"{BasketKeyPrefix}:{customerId}";
public async Task DeleteBasketAsync(int customerId, CancellationToken token = default) => await cache.RemoveAsync(GetBasketKey(customerId), token);
public async Task<CustomerBasket?> GetBasketAsync(int customerId, CancellationToken token = default) => await cache.GetObjectAsync<CustomerBasket>(GetBasketKey(customerId), token);
public async Task<CustomerBasket?> UpdateBasketAsync(CustomerBasket basket, CancellationToken token = default)
{
DistributedCacheEntryOptions options = new() { SlidingExpiration = TimeSpan.MaxValue };
await cache.SetObjectAsync(GetBasketKey(basket.BuyerId), basket, options, token);
return await GetBasketAsync(basket.BuyerId, token);
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.BasketService.Entities;
namespace HelloShop.BasketService.Repositories
{
public interface IBasketRepository
{
Task<CustomerBasket?> GetBasketAsync(int customerId, CancellationToken token = default);
Task<CustomerBasket?> UpdateBasketAsync(CustomerBasket basket, CancellationToken token = default);
Task DeleteBasketAsync(int customerId, CancellationToken token = default);
}
}

View File

@ -0,0 +1,78 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using AutoMapper;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using HelloShop.BasketService.Entities;
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.BasketService.Protos;
using HelloShop.BasketService.Repositories;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
namespace HelloShop.BasketService.Services
{
[Authorize]
public class CustomerBasketService(IBasketRepository repository, ILogger<CustomerBasketService> logger, IMapper mapper) : Basket.BasketBase
{
public override async Task<CustomerBasketResponse> GetBasket(Empty request, ServerCallContext context)
{
string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (!int.TryParse(nameIdentifier, out int userId))
{
throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated."));
}
logger.LogDebug("Begin GetBasketById call from method {Method} for basket id {Id}", context.Method, userId);
CustomerBasket? basket = await repository.GetBasketAsync(userId, context.CancellationToken);
if (basket is not null)
{
mapper.Map<CustomerBasketResponse>(basket);
}
return new();
}
public override async Task<CustomerBasketResponse> UpdateBasket(UpdateBasketRequest request, ServerCallContext context)
{
string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (!int.TryParse(nameIdentifier, out int userId))
{
throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated."));
}
logger.LogDebug("Begin UpdateBasket call from method {Method} for basket id {Id}", context.Method, userId);
CustomerBasket customerBasket = mapper.Map<CustomerBasket>(request, opts => opts.AfterMap((src, dest) => dest.BuyerId = userId));
CustomerBasket? result = await repository.UpdateBasketAsync(customerBasket, context.CancellationToken);
result = result ?? throw new RpcException(new Status(StatusCode.NotFound, $"Basket with buyer id {userId} does not exist"));
return mapper.Map<CustomerBasketResponse>(result);
}
public override async Task<Empty> DeleteBasket(Empty request, ServerCallContext context)
{
string? nameIdentifier = context.GetHttpContext().User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (!int.TryParse(nameIdentifier, out int userId))
{
throw new RpcException(new Status(StatusCode.Unauthenticated, "The caller is not authenticated."));
}
await repository.DeleteBasketAsync(userId, context.CancellationToken);
return new();
}
}
}

View File

@ -2,17 +2,12 @@
// See the license file in the project root for more information. // See the license file in the project root for more information.
using Grpc.Core; using Grpc.Core;
using HelloShop.BasketService.Protos;
namespace HelloShop.BasketService.Services; namespace HelloShop.BasketService.Services;
public class GreeterService : Greeter.GreeterBase public class GreeterService : Greeter.GreeterBase
{ {
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{ {
return Task.FromResult(new HelloReply return Task.FromResult(new HelloReply

View File

@ -0,0 +1,18 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using FluentValidation;
using FluentValidation.Validators;
using HelloShop.BasketService.Protos;
namespace HelloShop.BasketService.Validations.Baskets
{
public class BasketListItemValidator : AbstractValidator<BasketListItem>
{
public BasketListItemValidator()
{
RuleFor(x => x.ProductId).GreaterThan(0);
RuleFor(x => x.Quantity).GreaterThan(0);
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using FluentValidation;
using HelloShop.BasketService.Protos;
namespace HelloShop.BasketService.Validations.Baskets
{
public class UpdateBasketRequestValidator : AbstractValidator<UpdateBasketRequest>
{
public UpdateBasketRequestValidator()
{
RuleFor(x => x.Items).NotNull();
RuleForEach(x => x.Items).SetValidator(new BasketListItemValidator());
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using FluentValidation;
using HelloShop.BasketService.Protos;
namespace HelloShop.BasketService.Validations.Greeters
{
public class HelloRequestValidator : AbstractValidator<HelloRequest>
{
public HelloRequestValidator()
{
RuleFor(x => x.Name).NotNull().NotEmpty().Length(3, 20);
}
}
}

View File

@ -5,5 +5,10 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
},
"AllowedHosts": "*" "AllowedHosts": "*"
} }

View File

@ -58,9 +58,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.61" /> <PackageReference Include="Microsoft.Maui.Controls" Version="8.0.70" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.61" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.70" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.61" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.70" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -8,9 +8,9 @@
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" /> <ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@ -8,8 +8,8 @@
<ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" /> <ProjectReference Include="..\HelloShop.ServiceDefaults\HelloShop.ServiceDefaults.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@ -22,7 +22,7 @@ namespace HelloShop.ServiceDefaults.Extensions
services.Configure<SwaggerUIOptions>(options => services.Configure<SwaggerUIOptions>(options =>
{ {
options.DocumentTitle = Assembly.GetExecutingAssembly().GetName().Name; options.DocumentTitle = Assembly.GetEntryAssembly()?.GetName().Name;
options.InjectStylesheet("/ServiceDefaults/Resources/OpenApi/Custom.css"); options.InjectStylesheet("/ServiceDefaults/Resources/OpenApi/Custom.css");
}); });

View File

@ -7,8 +7,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.6.0" /> <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.7.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.2" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />

View File

@ -9,14 +9,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="8.0.1" /> <PackageReference Include="Aspire.Hosting.Testing" Version="8.0.2" />
<PackageReference Include="coverlet.collector" Version="6.0.2"> <PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.8.1" /> <PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1"> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@ -21,7 +21,6 @@ namespace HelloShop.ProductService.FunctionalTests
// Assert // Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
} }
[Fact] [Fact]

View File

@ -14,11 +14,11 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.8.1" /> <PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1"> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@ -14,11 +14,11 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Moq" Version="4.20.70" /> <PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.8.1" /> <PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1"> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>