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。用户不应该设置条目的主键,除非你因为某些用例而想要这样做。