You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core MVC中Post模型关联UserId外键时ModelState.IsValid返回false

问题描述

原本项目使用自行搭建的登录/注册系统,后来为接入谷歌、Facebook等第三方授权,集成了Microsoft Identity Platform,登录注册功能正常。但在添加网站帖子时,需要将UserId作为外键记录创建者信息,却出现异常:每次提交帖子时ModelState.IsValid始终返回false,但看不到任何模型验证错误;移除控制器中的IsValid校验后,所有数据(包括UserId)都能正常存入数据库。

Post模型

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控制器创建方法

[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();
}

控制器中的身份认证属性

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

DbContext

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; }
}

内容的提问来源于stack exchange,提问作者Matthew Arnold

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 19:45:43