ASP.Net Core MVC 模型返回的 UserId FK 无效

ASP.Net Core MVC Models returning not valid with UserId FK

提问人:Matthew Arnold 提问时间:11/8/2022 最后编辑:Matthew Arnold 更新时间:11/8/2022 访问量:267

问:

好的,所以我遇到了一些问题,在这一点上完全不知道可能出什么问题。我有一个项目已经使用我从头开始构建的登录/注册系统完成了。这很简单。我决定实施 Microsoft 身份平台,以便可以使用 Goggle、Facebook 等添加第三方授权。我已成功添加 identty 平台,并且登录和注册正常工作。我现在唯一的问题是,我用于向网站添加帖子的表单需要将UserId记录为外键,以便我可以记录每个帖子的创建者。

我已将 UserId 作为外键添加到模型中,但现在每当我尝试添加帖子时,ModelState.IsValid 都会不断返回 false,但没有抛出模型验证错误。当我从控制器中删除 IsValid 检查时,它会将所有内容记录到数据库中,包括 UserId......因此,我不确定为什么当它清楚地正确记录所有数据时会返回 false。

这是我的帖子模型

    public class Post
{
    [Key]
    public int PostId { get; set; }

    [Required(ErrorMessage ="is required")]
    [MinLength(3, ErrorMessage ="must be at least 3 characters")]
    [MaxLength(50, ErrorMessage ="must be 50 characters or less")]
    public string Title { get; set; }

    [Required(ErrorMessage ="is required")]
    [GreaterThan0]
    [Display(Name ="Players on Team")]
    public int PlayersOnTeam { get; set; }

    [Required(ErrorMessage ="is required")]
    [GreaterThan0]
    [Display(Name ="Max Players On Team")]
    public int MaxPlayersOnTeam { get; set; }

    // 0 = PC, 1 = PS4, 2 = Xbox One, 3 = PS5, 4 = PS3, 5 = Xbox 360
    [Required(ErrorMessage ="is required")]
    public string Platform { get; set; }

    [Required(ErrorMessage ="is required")]
    public string Language { get; set; }


    [Required(ErrorMessage ="is required")]
    [Display(Name ="Group Type")]
    public string GroupType { get; set; }

    [Required(ErrorMessage ="is required")]
    [GreaterThan0]
    [Display(Name ="Minimum Level")]
    public int MinLevel { get; set; }


    [Required(ErrorMessage ="is required")]
    [MinLength(10, ErrorMessage ="must be at least 10 characters")]
    [MaxLength(500, ErrorMessage ="must be 500 characters or less")]
    public string? Description { get; set; }

    [Required(ErrorMessage ="is required")]
    public string GameActivity { get; set; }

    public DateTime CreatedAt { get; set; } = DateTime.Now;
    public DateTime UpdatedAt { get; set; } = DateTime.Now;
    

    // foreign keys


    [ForeignKey("UserId")]
    public AdvanceUser Author { get; set; }

    // before I had this set up as which is why the controller uses AdvanceUser.Id, not UserId
    // public string Id { get; set;}
    // public AdvanceUser Author { get; set; }

}

这是我用于创建新帖子的 Post Controller 功能。我已经尝试了以下两个示例,最终都做了同样的事情。请注意,我知道它在此代码中使用了 AdvanceUser.Id 字段而不是 AdvanceUser.UserId(我已经来回更改了它,所以这不是问题。我使用UserId字段,但仍然无法正常工作

 [HttpPost("/lfg/posts/create")]
public IActionResult Create(Post newPost)
{
    if (!loggedIn)
    {
        return RedirectToAction("Index", "User");
    }

    if (uid != null)
    {
        newPost.Id = uid;
    }

    if (ModelState.IsValid == false)
    {
        return New();
    }
    var currentUser = _context.Users.FirstOrDefault(u => u.Id == User.FindFirstValue(ClaimTypes.NameIdentifier));
    newPost.Author = currentUser;

    _context.Posts.Add(newPost);
    _context.SaveChanges();

    return Dashboard();
}

[HttpPost("/lfg/posts/create")]
public async Task<IActionResult> Create(Post newPost)
{
    var user = await _userManager.FindByIdAsync(uid);

    if (!loggedIn)
    {
        return RedirectToAction("Index", "User");
    }

    // if (uid != null)
    // {
    //     newPost.Id = uid;
    // }

    
    newPost.Author = user;

    if (ModelState.IsValid == false)
    {
        return New();
    }


    _context.Posts.Add(newPost);
    await _context.SaveChangesAsync();

    return Dashboard();
}

它们位于类的顶部,用于身份验证和获取当前用户的用户 ID

  private string? uid
{
    get
    {
        var userId = this.User.FindFirstValue(ClaimTypes.NameIdentifier);
        return userId;
    }
}

private bool loggedIn
{
    get
    {
        return User.Identity.IsAuthenticated;
    }
}

这是我添加新帖子的观点

    @model Post


<div class="container w-75 mx-auto">
    <h2 class="text-center">New Post</h2>

    <form class="shadow p-3 rounded" asp-controller="Post" asp-action="Create" method="POST">
        <div class="mb-3">
            <label asp-for="Title"></label>
            <span asp-validation-for="Title" class="text-danger font-weight-bold"></span>
            <input class="form-control" asp-for="Title">
        </div>

        <div class="mb-3">
            <label asp-for="GameActivity"></label>
            <span asp-validation-for="GameActivity" class="text-danger font-weight-bold"></span>
            <select class="form-control" asp-for="GameActivity">
                @{
                    foreach (GameActivity activity in ViewBag.allActivities)
                    {
                        <option value="@activity.Name">@activity.Name</option>
                    }
                }
            </select>
        </div>

        @* <div class="mb-3">
            <label asp-for="GameActivity"></label>
            <span asp-validation-for="GameActivity" class="text-danger font-weight-bold"></span>
            <select class="form-control" asp-for="GameActivity">
                <option value="Raid">Raid</option>
                <option value="Public Event">Public Event</option>
            </select>
        </div> *@

        <div class="mb-3">
            <label asp-for="PlayersOnTeam"></label>
            <span asp-validation-for="PlayersOnTeam" class="text-danger font-weight-bold"></span>
            <input class="form-control" asp-for="PlayersOnTeam">
        </div>

        <div class="mb-3">
            <label asp-for="MaxPlayersOnTeam"></label>
            <span asp-validation-for="MaxPlayersOnTeam" class="text-danger font-weight-bold"></span>
            <input class="form-control" asp-for="MaxPlayersOnTeam">
        </div>

        <div class="mb-3">
            <label asp-for="Platform"></label>
            <span asp-validation-for="Platform" class="text-danger font-weight-bold"></span>
            <select class="form-control" asp-for="Platform">
                <option value="PC">PC</option>
                <option value="PS4">PS4</option>
                <option value="Xbox">Xbox One</option>
                <option value="PS5">PS5</option>
                <option value="PS3">PS3</option>
            </select>
        </div>

        <div class="mb-3">
            <label asp-for="Language"></label>
            <span asp-validation-for="Language" class="text-danger font-weight-bold"></span>
            <input class="form-control" asp-for="Language">
        </div>

        <div class="mb-3">
            <label asp-for="GroupType"></label>
            <span asp-validation-for="GroupType" class="text-danger font-weight-bold"></span>
            <select class="form-control" asp-for="GroupType">
                <option value="LFG">LFG</option>
                <option value="LFM">LFM</option>
            </select>
        </div>

        <div class="mb-3">
            <label asp-for="MinLevel"></label>
            <span asp-validation-for="MinLevel" class="text-danger font-weight-bold"></span>
            <input class="form-control" asp-for="MinLevel">
        </div>

        <div class="mb-3">
            <label asp-for="Description"></label>
            <span asp-validation-for="Description" class="text-danger font-weight-bold"></span>
            <textarea class="form-control" asp-for="Description"></textarea>
        </div>

        <button class="btn btn-success">Submit</button>
    </form>

</div>

最后,这是我的Program.cs类。以防万一需要

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using LFGHub.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Authentication.Certificate;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

// builder.Services.AddAuthentication(
//         CertificateAuthenticationDefaults.AuthenticationScheme)
//     .AddCertificate();

// builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
//     .AddEntityFrameworkStores<ApplicationDbContext>();
// builder.Services.AddControllersWithViews();

builder.Services.AddIdentity<AdvanceUser, IdentityRole>(options => {
    options.Password.RequireDigit = true;
    options.Password.RequireUppercase = true;
}).AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders().AddDefaultUI().AddRoles<IdentityRole>();

builder.Services.AddRazorPages();

builder.Services.AddScoped<RoleManager<IdentityRole>>();
builder.Services.AddScoped<UserManager<AdvanceUser>>();

var app = builder.Build();

var serviceProvider = app.Services.GetService<IServiceProvider>();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();
// app.UseAuthentication();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();

app.Run();

编辑: 下面是 AdvanceUser 模型。我不确定什么可能是不正确的。

using Microsoft.AspNetCore.Identity;
using LFGHub.Models;

public class AdvanceUser : IdentityUser
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DisplayName { get; set; }
    public string MobileNumber { get; set; }

    public string DestinyUsername { get; set; }

    // public virtual ICollection<Post>? Posts { get; set; }

    public List<Post> Posts { get; set; } = new List<Post>();
}

这是我的数据库上下文

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using LFGHub.Models;

namespace LFGHub.Data;

public class ApplicationDbContext : IdentityDbContext<AdvanceUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

        builder.Entity<Post>()
            .HasOne(c => c.Author)
            .WithMany(x => x.Posts);
    }
    //public DbSet<Game> Games { get; set; }
    public DbSet<GameActivity> GameActivities { get; set; }
    public DbSet<Post> Posts { get; set; }
    // public DbSet<GroupMember> GroupMembers { get; set; }
    public DbSet<NewsPost> NewsPosts { get; set; }
}
C# MySQL ASP.NET-CORE 验证

评论

1赞 terrencep 11/8/2022
我认为您必须深入研究 AdvanceUser 对象才能查看在 ModelState.IsValid 之前缺少或绑定不正确的内容。ModelState 对它仅根据 Post 类进行验证的数据库一无所知。
1赞 topsail 11/8/2022
您使用的是带有外键属性的 Author 对象,这看起来很奇怪。通常,外键是基元,或者以其他方式在 DBContext 中设置。你确定你做对了吗?
1赞 pcalkins 11/8/2022
你有“[ForeignKey(”UserId“)]”,但我没有看到任何设置它的东西......它不是 Post 模型或 page 类中的属性。不过,这听起来并没有给您带来任何问题。也许是这个位:私有字符串?UID ...这似乎不应该是可为 null 的或字符串。
1赞 pcalkins 11/8/2022
您已经将该部分作为页面模型的一部分,但似乎您应该在方法中查找用户的 ID,然后在添加之前将其设置为 Post。
1赞 topsail 11/8/2022
是的,这也很好奇......不是 AdvanceUser 的属性。据我所知,它也不是基类(IdentityUser)中定义的属性。UserId

答:

1赞 Matthew Arnold 11/8/2022 #1

我弄清楚了问题出在哪里。我必须更改 Post 模型 foregin 键属性,如下所示:

[ForeignKey("User")]
public string UserId { get; set; }
public AdvanceUser? User { get; set; }

在我的 AdvanceUser 类中,我将 ICollection 从虚拟 ICollection 更改为普通的 ICollection

public ICollection<Post> Posts { get; set; }

最后,我将数据库上下文更改为

public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
    : base(options)
{
}

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    // Customize the ASP.NET Identity model and override the defaults if needed.
    // For example, you can rename the ASP.NET Identity table names and more.
    // Add your customizations after calling base.OnModelCreating(builder);

    // Commented this out too
    // builder.Entity<Post>()
    //     .HasOne(c => c.Author)
    //     .WithMany(x => x.Posts);
}
//public DbSet<Game> Games { get; set; }
public DbSet<GameActivity> GameActivities { get; set; }
public DbSet<Post> Posts { get; set; }
// public DbSet<GroupMember> GroupMembers { get; set; }
public DbSet<NewsPost> NewsPosts { get; set; }

我注意到,当我查看数据库中的表时,它正在创建两个 userId 列。一个称为 UserId,另一个称为 AuthorId。所以我做了这些改变,它现在起作用了!感谢所有帮助的人!