我正在尝试创建一个应用程序,就像卖家管理库存和订单的系统一样。我不想对我的 API 进行数百次调用,因此我想在用户登录后存储用户,并且仅在需要时再次调用 API。我尝试了所有不同的“解决方案”,但没有任何效果。我不想使用 Identity,因为我的 API 不使用它,而且它对于我正在做的事情来说太复杂了。
起初,我将用户存储在单例中
UserService
作为静态属性,但后来我意识到这会导致在应用程序的所有实例中使用相同的 User 属性,因此我尝试添加会话和身份验证,但我只是不这样做了解这一切是如何运作的,但我什至不知道从哪里开始学习。
这是我现在的
UserService
:
using Market.Data.Models;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Caching.Memory;
using System.Net;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Diagnostics;
namespace Market.Services
{
public class UserService : IUserService
{
private readonly IHttpClientFactory factory;
private readonly IHttpContextAccessor httpContextAccessor;
private readonly HttpClient client;
private User? User;
public UserService(IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor)
{
factory = httpClientFactory;
client = factory.CreateClient();
client.BaseAddress = new Uri("https://farmers-market.sommee.com/api/");
this.httpContextAccessor = httpContextAccessor;
User = GetUser();
}
private async Task SaveUserToContext(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.UserData, JsonSerializer.Serialize(user)),
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
httpContextAccessor.HttpContext.User = new ClaimsPrincipal(claimsIdentity);
}
public async Task<User> Login(string email, string password)
{
var url = $"https://farmers-market.somee.com/api/users/login?email={email}&password={password}";
var response = await client.GetAsync(url);
var result = new User();
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
Console.WriteLine(stringResponse);
result = JsonSerializer.Deserialize<User>(stringResponse,
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
User = result;
await SaveUserToContext(result!);
}
else
{
throw new HttpRequestException(response.ReasonPhrase);
}
if (result == null)
{
throw new Exception("Error with login");
}
return result;
}
public async Task<HttpStatusCode> Register(User user)
{
var url = $"https://farmers-market.somee.com/api/users/add";
var jsonParsed = JsonSerializer.Serialize<User>(user, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
HttpContent content = new StringContent(jsonParsed.ToString(), Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
return response.StatusCode;
}
public Task RemoveOrderAsync(int orderId)
{
if (User == null)
{
throw new Exception("User is not authenticated");
}
User.SoldOrders.Remove(User.SoldOrders.Single(x => x.Id == orderId));
return Task.CompletedTask;
}
public void AddApprovedOrder(int id)
{
User!.SoldOrders.Single(x => x.Id == id).IsApproved = true;
}
public void AddDeliveredOrder(int id)
{
User!.SoldOrders.Single(x => x.Id == id).IsDelivered = true;
}
public User? GetUser()
{
var saved = httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.UserData);
if (saved == null)
return null;
User = JsonSerializer.Deserialize<User>(saved);
return User;
}
}
}
在这种方法中,我将用户保存到
HttpContext
,但在另一个控制器中使用该服务后,保存的 UserData
消失了。
这是我的
program.cs
:
using Market.Services;
using Market.Services.Firebase;
using Market.Services.Inventory;
using Market.Services.Offers;
using Market.Services.Orders;
using Market.Services.Reviews;
using Microsoft.AspNetCore.Authentication.Cookies;
using Market.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Market.Data.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddHttpClient();
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30); // Adjust timeout as needed
options.Cookie.HttpOnly = true;
options.Cookie.Name = "SessionCookie_" + Guid.NewGuid().ToString();
options.Cookie.IsEssential = true;
});
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
// Set a unique cookie name for this instance of the app
options.Cookie.Name = "AuthCookie_" + Guid.NewGuid().ToString(); // Or use another unique value
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IInventoryService, InventoryService>();
builder.Services.AddScoped<IOfferService, OfferService>();
builder.Services.AddScoped<IFirebaseServive, FirebaseService>();
builder.Services.AddScoped<IOrdersService, OrdersService>();
builder.Services.AddScoped<IReviewsService, ReviewsService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
我对所有解决方案或不同方法持开放态度。
在我看来,session和cookie不是存储股票、订单的好方法,并且已经使用了内存中的session或cookie。
首先,内存中的会话将在设置时间和应用程序重新启动后过期,这意味着如果用户正在使用您的应用程序,并且应用程序突然重新启动或发生某些情况,则用户拥有的所有数据都将丢失。会造成很大的使用问题。如果用户想要查询他们使用过的股票、订单,但发生了一些事情导致应用程序无法存储会话中的数据,这会使您的应用程序不可靠。
此外,你还需要考虑你的数据始终是最新的,在我看来,股票和订单变化非常频繁,这意味着会话中的数据是错误的数据,你如何保持所有数据是最新的和正确的一样吗?
总而言之,我仍然不建议你选择session来存储这两个数据。使用API来获取数据是最好的方法,并且在缓存中你可以存储每个用户一些未更改的数据。
在这种方法中,我将用户保存到 HttpContext,但在另一个控制器中使用该服务后,保存的 UserData 消失了。
这与你的 SaveUserToContext 方法有关,SaveUserToContext 方法是将声明添加到 httpcontext 中,但是声明是从 cookie 中读取的,如果你只是在 httpcontext 中添加一些声明,它不会将数据保留到下一个请求中,因为它不会将它们添加到 cookie 中。
如果您想向 cookie 添加新的声明,您需要重新调用
httpContextAccessor.HttpContext.SignInAsync
方法为客户端用户重置 cookie。
代码如下:
private async Task SaveUserToContext(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.UserData, JsonSerializer.Serialize(user)),
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(30), // Set a custom expiry if needed
};
await httpContextAccessor.HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
结果:
我的测试示例:
private async Task SaveUserToContext( )
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.UserData, "JsonSerializer.Serialize(user)"),
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(30), // Set a custom expiry if needed
};
await _contextAccessor.HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}