【问题标题】:ASP.Net Core storing and retrieving an entity against identity userASP.Net Core 存储和检索针对身份用户的实体
【发布时间】:2017-05-30 23:15:00
【问题描述】:

我正在构建一个 Web 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()
    {

    }

}

如何确保每个任务都是针对登录用户(即利益相关者)创建的?我将不得不使用利益相关者的外键更新我的任务模型?我该怎么做?如何更新我的控制器方法,以便我可以发回属于发出请求的用户/利益相关者的任务?

更新: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");
            });

        }
    }

【问题讨论】:

    标签: asp.net-core entity-framework-core asp.net-core-webapi asp.net-core-identity


    【解决方案1】:

    据我了解,您希望每个利益相关者都有一个 User_Task 列表。 我建议您在 User_Task 类中添加一个引用 Stakeholder Id 的外键,然后将导航属性添加到您的 User_Task 和 Stakeholder 类中。

    以下应该有效:

    User_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; }
    
        // 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()
        {
    
        }
    }
    

     

    利益相关者类:

    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()
        {
    
        }
    
    }
    

     

    对于您的存储库类,您可以有一个方法,根据登录用户的 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 类,如果您设置正确,它应该由 IdentityServer 注入到您的 DI 容器中。因此,您只需将 UserManager 添加到控制器的构造函数中。 Controller 类有一个名为 "User" 的属性,您可以将其传递给 UserManager 类的 GetUserId() 方法:

    [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 尚不支持延迟加载,因此无需在导航属性前添加“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);
    
            }
    

    更新

    在您的存储库类中向特定用户添加任务:

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

    【讨论】:

    • 感谢奥马尔提供了如此详细的答案!无论谁提到这个问题,它都会对我有很大帮助。我只需要更改 return userTasks;在控制器中返回新的 ObjectResult(userTasks);否则它会给我铸造错误。此外,将 OnModelCreating() 方法放入 DBContext 会使我的 AccountController 无法访问,引发 5oo 内部服务器错误,并且我无法创建新用户,所以我没有添加它。您能否也像 GetCurrentUserTasks() 一样指出如何实现一种方法来创建具有特定 userId 的任务?
    • 您可以通过将特定 userId 添加到 Task 对象并保存更改来创建具有特定 userId 的任务。我将编辑我之前的答案以添加一个示例并更改 GetCurrentUserTasks() 方法的返回类型。我还想知道为什么在添加 OnModelCreating() 覆盖时出现 500 错误。有这方面的详细信息吗?
    • 您在更新中编写的代码是任务存储库创建方法的一部分,对吗?
    • 是的,它是为了让您大致了解它的工作原理。我编辑了代码 sn-p 以显示存储库类中的 Add(User_Task item) 方法的外观。在您的控制器中,您将获得登录用户的 ID,然后将其与 Task 对象一起传递给存储库的 Add 方法
    • 非常感谢奥马尔!这真的很有帮助:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-25
    • 1970-01-01
    • 2017-04-03
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 2021-06-23
    相关资源
    最近更新 更多