ASP.Net 针对身份用户存储和检索实体的核心
ASP.Net Core storing and retrieving an entity against identity user
我正在搭建一个网站API,已经实现了注册登录。我有一个名为 Task 的模型,如下所示:
public class User_Task
{
[Key]
public long TaskId { get; set; }
public string What { get; set; }
public string How_often { get; set; }
public string How_important { get; set; }
[ForeignKey("FeatureId")]
public long? FeatureId { get; set; }
public virtual ICollection<Step> Steps { get; set; }
public User_Task()
{
}
}
存储库:
public class User_TaskRepository : IUser_TaskRepository
{
private readonly WebAPIDataContext _context;
public User_TaskRepository(WebAPIDataContext context)
{
_context = context;
}
public IEnumerable<User_Task> GetAll()
{
return _context.User_Tasks.Include(task => task.Steps).ToList();
}
public void Add(User_Task item)
{
_context.User_Tasks.Add(item);
_context.SaveChanges();
}
public User_Task Find(long key)
{
return _context.User_Tasks.Include(task => task.Steps).FirstOrDefault(t => t.TaskId == key);
}
public void Remove(long key)
{
var entity = _context.User_Tasks.First(t => t.TaskId == key);
_context.User_Tasks.Remove(entity);
_context.SaveChanges();
}
public void Update(User_Task item)
{
_context.User_Tasks.Update(item);
_context.SaveChanges();
}
}
public interface IUser_TaskRepository
{
void Add(User_Task item);
IEnumerable<User_Task> GetAll();
User_Task Find(long key);
void Remove(long key);
void Update(User_Task item);
}
它的控制器:
[Route("api/[controller]")]
public class User_TaskController : Controller
{
private readonly IUser_TaskRepository _taskRepository;
//Controller
public User_TaskController(IUser_TaskRepository taskRepository)
{
_taskRepository = taskRepository;
}
//Get methods
[HttpGet]
public IEnumerable<User_Task> GetAll()
{
return _taskRepository.GetAll();
}
[HttpGet("{id}", Name = "GetTask")]
public IActionResult GetById(long id)
{
var item = _taskRepository.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
//Create
[HttpPost]
public IActionResult Create([FromBody] User_Task item)
{
if (item == null)
{
return BadRequest();
}
_taskRepository.Add(item);
return CreatedAtRoute("GetTask", new { id = item.TaskId }, item);
}
//Update
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] User_Task item)
{
if (item == null)
{
return BadRequest();
}
var task = _taskRepository.Find(id);
if (task == null)
{
return NotFound();
}
task.What = item.What;
task.How_often = item.How_often;
task.How_important = item.How_important;
UpdateTaskSteps(item.Steps, task.Steps);
_taskRepository.Update(task);
return new NoContentResult();
}
private void UpdateTaskSteps(ICollection<Step> steps, ICollection<Step> taskSteps)
{
foreach (var step in steps)
{
Step taskStep = taskSteps.FirstOrDefault(x => x.StepId == step.StepId);
if (taskStep != null)
{
// Update
taskStep.What = step.What;
}
else
{
// Create
taskSteps.Add(new Step
{
What = step.What,
TaskId = step.TaskId
});
}
}
}
//Delete
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var task = _taskRepository.Find(id);
if (task == null)
{
return NotFound();
}
_taskRepository.Remove(id);
return new NoContentResult();
}
}
现在我有如下 ApplicationUser 模型:
public class ApplicationUser : IdentityUser
{
// Extended Properties
public string FirstName { get; set; }
public string LastName { get; set; }
public ApplicationUser()
{
}
}
还有另一个利益相关者模型:
public class Stakeholder
{
public int Id { get; set; }
public string IdentityId { get; set; }
public ApplicationUser Identity { get; set; } // navigation property
public Stakeholder()
{
}
}
如何确保每个任务都是针对登录用户(即涉众)创建的?我必须使用利益相关者的外键更新我的任务模型吗?我该怎么做,以及如何更新我的控制器方法,以便我可以发回属于发出请求的 user/Stakeholder 的任务?
更新:startup.cs
public class Startup
{
private const string SecretKey = "iNivDmHLpUA223sqsfhqGbMRdRj1PVkH"; // todo: get this from somewhere secure
private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<WebAPIDataContext>(options =>
{
options.UseMySql(Configuration.GetConnectionString("MysqlConnection"),
b => b.MigrationsAssembly("Vision_backlog_backend"));
});
services.AddSingleton<IJwtFactory, JwtFactory>();
// jwt wire up
// Get options from app settings
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
services.AddScoped<IProfileRepository, ProfileRepository>();
services.AddScoped<IUser_TaskRepository, User_TaskRepository>();
services.AddScoped<IFeatureRepository, FeatureRepository>();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
// api user claim policy
services.AddAuthorization(options =>
{
options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
services.AddIdentity<ApplicationUser, IdentityRole>
(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<WebAPIDataContext>()
.AddDefaultTokenProviders();
services.AddMvc().AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
services.AddAutoMapper();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// global policy - assign here or on each controller
app.UseCors("CorsPolicy");
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = false,
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
app.UseMvc();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
}
据我了解,您希望每个利益相关者都有一份 User_Task 的列表。
我建议您将外键添加到您的 User_Task class 中,它引用了 Stakeholder Id,然后将导航属性添加到您的 User_Task 和 Stakeholder classes.
以下应该有效:
User_Task class:
public class User_Task
{
[Key]
public long TaskId { get; set; }
public string What { get; set; }
public string How_often { get; set; }
public string How_important { get; set; }
[ForeignKey("FeatureId")]
public long? FeatureId { get; set; }
public virtual ICollection<Step> Steps { get; set; }
// EF should detect a reference to another table if your property name follows the {className}{idName} format
// so the ForeignKey attribute isn't really needed
[ForeignKey("StakeholderId")]
[Required]
public int StakeholderId { get; set; }
public Stakeholder Stakeholder { get; set; }
public User_Task()
{
}
}
利益相关者class:
public class Stakeholder
{
public int Id { get; set; }
public string IdentityId { get; set; }
public ApplicationUser Identity { get; set; }
// navigation property for User_Tasks
public ICollection<User_Task> User_Tasks { get; set; }
public Stakeholder()
{
}
}
对于您的 存储库 class,您可以使用一种方法 returns 根据登录用户的 Id 属于特定涉众的所有任务:
public ICollection<User_Task> GetUserTasks(string userId){
Stakeholder currentStakeholder = _context.Stakeholders
.FirstOrDefault(sh => sh.IdentityId == userId);
var userTasks = _context.User_Tasks
.Where(task => task.StakeholderId == currentStakeholder.Id).ToList();
return userTasks;
}
现在要获取登录用户的 Id,您必须使用 UserManager class,如果您已经将其注入到您的 DI 容器中,IdentityServer正确设置。所以你只需要将一个 UserManager 添加到你的控制器的构造函数中。
控制器 class 有一个名为 "User" 的 属性,您可以将其传递给 GetUserId() UserManager 的方法 class:
[Route("api/[controller]")]
public class User_TaskController : Controller
{
private readonly IUser_TaskRepository _taskRepository;
private readonly UserManager<ApplicationUser> _userManager;
//Controller
public User_TaskController(IUser_TaskRepository taskRepository, UserManager<ApplicationUser> userManager)
{
_taskRepository = taskRepository;
_userManager = userManager;
}
// The Authorize header means that this method cannot be accessed if the requester is not authenticated
[Authorize]
[HttpGet("current")]
public IActionResult GetCurrentUserTasks()
{
string currentUserId = _userManager.GetUserId(User);
var userTasks = _taskRepository.GetUserTasks(userId);
return userTasks;
}
}
需要考虑的一些额外事项:
- 您可能希望在 API 方面采用 RESTful 风格。考虑让登录用户通过另一个遵循以下模式的控制器访问自己的任务:/Account/Tasks
- 由于 EF Core 还不支持 Lazy Loading,您不需要在导航属性前添加 "virtual" 关键字
- 您还可以在 DbContext 的 OnModelCreating 方法中设置外键,如下所示:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User_Task>().HasOne(t => t.Stakeholder).WithMany(sh => sh.User_Tasks).HasForeignKey(t => t.StakeholderId);
}
更新
向您的 存储库中的特定用户添加任务 class:
public void Add(string userId, User_Task item)
{
Stakeholder currentStakeholder = _context.Stakeholders
.FirstOrDefault(sh => sh.IdentityId == userId);
item.StakeholderId = currentStakeholder.Id;
_context.User_Tasks.Add(item);
_context.SaveChanges();
}
您还可以通过调用 "Add()" 到利益相关者对象的 User_Tasks ICollection 来将任务添加到利益相关者。
另一件要记住的事情:在处理用于创建实体的输入时,您可能应该使用 DTO。用户不应该设置条目的主键,除非你因为某些用例而想要这样做。
我正在搭建一个网站API,已经实现了注册登录。我有一个名为 Task 的模型,如下所示:
public class User_Task
{
[Key]
public long TaskId { get; set; }
public string What { get; set; }
public string How_often { get; set; }
public string How_important { get; set; }
[ForeignKey("FeatureId")]
public long? FeatureId { get; set; }
public virtual ICollection<Step> Steps { get; set; }
public User_Task()
{
}
}
存储库:
public class User_TaskRepository : IUser_TaskRepository
{
private readonly WebAPIDataContext _context;
public User_TaskRepository(WebAPIDataContext context)
{
_context = context;
}
public IEnumerable<User_Task> GetAll()
{
return _context.User_Tasks.Include(task => task.Steps).ToList();
}
public void Add(User_Task item)
{
_context.User_Tasks.Add(item);
_context.SaveChanges();
}
public User_Task Find(long key)
{
return _context.User_Tasks.Include(task => task.Steps).FirstOrDefault(t => t.TaskId == key);
}
public void Remove(long key)
{
var entity = _context.User_Tasks.First(t => t.TaskId == key);
_context.User_Tasks.Remove(entity);
_context.SaveChanges();
}
public void Update(User_Task item)
{
_context.User_Tasks.Update(item);
_context.SaveChanges();
}
}
public interface IUser_TaskRepository
{
void Add(User_Task item);
IEnumerable<User_Task> GetAll();
User_Task Find(long key);
void Remove(long key);
void Update(User_Task item);
}
它的控制器:
[Route("api/[controller]")]
public class User_TaskController : Controller
{
private readonly IUser_TaskRepository _taskRepository;
//Controller
public User_TaskController(IUser_TaskRepository taskRepository)
{
_taskRepository = taskRepository;
}
//Get methods
[HttpGet]
public IEnumerable<User_Task> GetAll()
{
return _taskRepository.GetAll();
}
[HttpGet("{id}", Name = "GetTask")]
public IActionResult GetById(long id)
{
var item = _taskRepository.Find(id);
if (item == null)
{
return NotFound();
}
return new ObjectResult(item);
}
//Create
[HttpPost]
public IActionResult Create([FromBody] User_Task item)
{
if (item == null)
{
return BadRequest();
}
_taskRepository.Add(item);
return CreatedAtRoute("GetTask", new { id = item.TaskId }, item);
}
//Update
[HttpPut("{id}")]
public IActionResult Update(long id, [FromBody] User_Task item)
{
if (item == null)
{
return BadRequest();
}
var task = _taskRepository.Find(id);
if (task == null)
{
return NotFound();
}
task.What = item.What;
task.How_often = item.How_often;
task.How_important = item.How_important;
UpdateTaskSteps(item.Steps, task.Steps);
_taskRepository.Update(task);
return new NoContentResult();
}
private void UpdateTaskSteps(ICollection<Step> steps, ICollection<Step> taskSteps)
{
foreach (var step in steps)
{
Step taskStep = taskSteps.FirstOrDefault(x => x.StepId == step.StepId);
if (taskStep != null)
{
// Update
taskStep.What = step.What;
}
else
{
// Create
taskSteps.Add(new Step
{
What = step.What,
TaskId = step.TaskId
});
}
}
}
//Delete
[HttpDelete("{id}")]
public IActionResult Delete(long id)
{
var task = _taskRepository.Find(id);
if (task == null)
{
return NotFound();
}
_taskRepository.Remove(id);
return new NoContentResult();
}
}
现在我有如下 ApplicationUser 模型:
public class ApplicationUser : IdentityUser
{
// Extended Properties
public string FirstName { get; set; }
public string LastName { get; set; }
public ApplicationUser()
{
}
}
还有另一个利益相关者模型:
public class Stakeholder
{
public int Id { get; set; }
public string IdentityId { get; set; }
public ApplicationUser Identity { get; set; } // navigation property
public Stakeholder()
{
}
}
如何确保每个任务都是针对登录用户(即涉众)创建的?我必须使用利益相关者的外键更新我的任务模型吗?我该怎么做,以及如何更新我的控制器方法,以便我可以发回属于发出请求的 user/Stakeholder 的任务?
更新:startup.cs
public class Startup
{
private const string SecretKey = "iNivDmHLpUA223sqsfhqGbMRdRj1PVkH"; // todo: get this from somewhere secure
private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<WebAPIDataContext>(options =>
{
options.UseMySql(Configuration.GetConnectionString("MysqlConnection"),
b => b.MigrationsAssembly("Vision_backlog_backend"));
});
services.AddSingleton<IJwtFactory, JwtFactory>();
// jwt wire up
// Get options from app settings
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
services.AddScoped<IProfileRepository, ProfileRepository>();
services.AddScoped<IUser_TaskRepository, User_TaskRepository>();
services.AddScoped<IFeatureRepository, FeatureRepository>();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
// api user claim policy
services.AddAuthorization(options =>
{
options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
services.AddIdentity<ApplicationUser, IdentityRole>
(o =>
{
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<WebAPIDataContext>()
.AddDefaultTokenProviders();
services.AddMvc().AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
services.AddAutoMapper();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// global policy - assign here or on each controller
app.UseCors("CorsPolicy");
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = false,
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
app.UseMvc();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
}
据我了解,您希望每个利益相关者都有一份 User_Task 的列表。 我建议您将外键添加到您的 User_Task class 中,它引用了 Stakeholder Id,然后将导航属性添加到您的 User_Task 和 Stakeholder classes.
以下应该有效:
User_Task class:
public class User_Task
{
[Key]
public long TaskId { get; set; }
public string What { get; set; }
public string How_often { get; set; }
public string How_important { get; set; }
[ForeignKey("FeatureId")]
public long? FeatureId { get; set; }
public virtual ICollection<Step> Steps { get; set; }
// EF should detect a reference to another table if your property name follows the {className}{idName} format
// so the ForeignKey attribute isn't really needed
[ForeignKey("StakeholderId")]
[Required]
public int StakeholderId { get; set; }
public Stakeholder Stakeholder { get; set; }
public User_Task()
{
}
}
利益相关者class:
public class Stakeholder
{
public int Id { get; set; }
public string IdentityId { get; set; }
public ApplicationUser Identity { get; set; }
// navigation property for User_Tasks
public ICollection<User_Task> User_Tasks { get; set; }
public Stakeholder()
{
}
}
对于您的 存储库 class,您可以使用一种方法 returns 根据登录用户的 Id 属于特定涉众的所有任务:
public ICollection<User_Task> GetUserTasks(string userId){
Stakeholder currentStakeholder = _context.Stakeholders
.FirstOrDefault(sh => sh.IdentityId == userId);
var userTasks = _context.User_Tasks
.Where(task => task.StakeholderId == currentStakeholder.Id).ToList();
return userTasks;
}
现在要获取登录用户的 Id,您必须使用 UserManager class,如果您已经将其注入到您的 DI 容器中,IdentityServer正确设置。所以你只需要将一个 UserManager 添加到你的控制器的构造函数中。 控制器 class 有一个名为 "User" 的 属性,您可以将其传递给 GetUserId() UserManager 的方法 class:
[Route("api/[controller]")]
public class User_TaskController : Controller
{
private readonly IUser_TaskRepository _taskRepository;
private readonly UserManager<ApplicationUser> _userManager;
//Controller
public User_TaskController(IUser_TaskRepository taskRepository, UserManager<ApplicationUser> userManager)
{
_taskRepository = taskRepository;
_userManager = userManager;
}
// The Authorize header means that this method cannot be accessed if the requester is not authenticated
[Authorize]
[HttpGet("current")]
public IActionResult GetCurrentUserTasks()
{
string currentUserId = _userManager.GetUserId(User);
var userTasks = _taskRepository.GetUserTasks(userId);
return userTasks;
}
}
需要考虑的一些额外事项:
- 您可能希望在 API 方面采用 RESTful 风格。考虑让登录用户通过另一个遵循以下模式的控制器访问自己的任务:/Account/Tasks
- 由于 EF Core 还不支持 Lazy Loading,您不需要在导航属性前添加 "virtual" 关键字
- 您还可以在 DbContext 的 OnModelCreating 方法中设置外键,如下所示:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User_Task>().HasOne(t => t.Stakeholder).WithMany(sh => sh.User_Tasks).HasForeignKey(t => t.StakeholderId);
}
更新
向您的 存储库中的特定用户添加任务 class:
public void Add(string userId, User_Task item)
{
Stakeholder currentStakeholder = _context.Stakeholders
.FirstOrDefault(sh => sh.IdentityId == userId);
item.StakeholderId = currentStakeholder.Id;
_context.User_Tasks.Add(item);
_context.SaveChanges();
}
您还可以通过调用 "Add()" 到利益相关者对象的 User_Tasks ICollection 来将任务添加到利益相关者。 另一件要记住的事情:在处理用于创建实体的输入时,您可能应该使用 DTO。用户不应该设置条目的主键,除非你因为某些用例而想要这样做。