本篇同步發文在個人Blog: 一袋.NET要扛幾樓?打造容器化的ASP.NET Core網站!系列文章 - (27) 建立購物車系統 - 10
在WebMvc專案的appSettings.json增加購物車服務的Api連結:
"CartUrl": "http://localhost:1028",
修改WebMvc專案的Startup.cs,主要是增加在OIDC的購物車Scope、註冊購物車相關服務:
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json.Serialization;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using WebMvc.Infrastructure;
using WebMvc.Models;
using WebMvc.Services;
namespace WebMvc
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IHttpClient, CustomHttpClient>();
services.AddTransient<ICatalogService, CatalogService>();
services.AddTransient<IAuthService<ApplicationUser>, AuthService>();
services.AddTransient<ICartService, CartService>();
services.AddControllersWithViews().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
var callBackUrl = Configuration.GetValue<string>("CallBackUrl");
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = identityUrl;
options.SignedOutRedirectUri = callBackUrl;
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.RequireHttpsMetadata = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.Scope.Add("basket");
options.NonceCookie.SameSite = SameSiteMode.Lax;
options.CorrelationCookie.SameSite = SameSiteMode.Lax;
options.BackchannelHttpHandler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Catalog}/{action=Index}/{id?}");
});
}
}
}
===
在WebMvc專案新增ViewComponents資料夾,並新增Cart.cs和CartList.cs 2個類別,都實作Microsoft.AspNetCore.Mvc.ViewComponent,之後Views的cshtml可以直接把它們當function呼叫並產生子View:
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using WebMvc.Models;
using WebMvc.Services;
using WebMvc.ViewModels;
namespace WebMvc.ViewComponents
{
public class Cart : ViewComponent
{
private readonly ICartService _cartService;
public Cart(ICartService cartService)
{
_cartService = cartService;
}
public async Task<IViewComponentResult> InvokeAsync(ApplicationUser user)
{
var vm = new CartComponentViewModel();
try
{
var cart = await _cartService.GetCartAsync(user);
vm.ItemsInCart = cart.Items.Count;
vm.TotalCost = cart.Total();
return View(vm);
}
catch (Exception)
{
ViewBag.IsCartInoperative = true;
}
return View(vm);
}
}
}
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
using WebMvc.Models;
using WebMvc.Services;
namespace WebMvc.ViewComponents
{
public class CartList : ViewComponent
{
private readonly ICartService _cartService;
public CartList(ICartService cartService)
{
_cartService = cartService;
}
public async Task<IViewComponentResult> InvokeAsync(ApplicationUser user)
{
var vm = new Models.CartModels.Cart();
try
{
vm = await _cartService.GetCartAsync(user);
return View(vm);
}
catch (Exception)
{
ViewBag.IsCartInoperative = true;
TempData["CartInoperativeMsg"] = "Cart Service is inoperative, please retry later.";
}
return View(vm);
}
}
}
在WebMvc專案的ViewModels新增CartComponentViewModel.cs,是Cart ViewComponent要回傳的View:
namespace WebMvc.ViewModels
{
public class CartComponentViewModel
{
public int ItemsInCart { get; set; }
public decimal TotalCost { get; set; }
public string Disabled => (ItemsInCart == 0) ? "is-disabled" : "";
}
}