在 Blazor 中使用 Web Api 创建复杂对象
Creating a complex object using Web Api in Blazor
我正在尝试在我的网站 api 中使用 Post 方法创建一个复杂对象。但是我很难做到这一点,因为当我创建一个 Board 对象时,我要求它有一个 Board.Company.Name 将它与公司相关联。但是,当我 select 一个已经存在的公司名称并处理有效提交时,将使用我选择的 Board.Company.Name 创建一个新公司。然后我显示我创建的董事会,看起来实际上没有公司与之关联。下面我包含了相关代码。这是我使用 C# 和 Blazor 的第一个项目,所以如果我遗漏了任何重要的内容,请告诉我,我会添加它。
公司模式
public class Company
{
[Key]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
public DateTime Founded { get; set; }
}
棋盘模型
public class Board
{
[Key]
public Guid Id { get; set; }
[ValidateComplexType]
public Company Company { get; set; } = new();
[Required]
public string Name { get; set; }
public string Description { get; set; }
public List<Ticket> Tickets { get; set; }
public Board()
{
}
}
Api POST 方法
[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
_context.Boards.Add(board);
await _context.SaveChangesAsync();
return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}
Create_Board
@page "/create_board"
@inject NavigationManager Navigation
@inject HttpClient Http
<div>
<button class="btn btn-outline-secondary oi oi-arrow-left" @onclick="GoToHome"></button>
<h3 class="text-center">Create a board</h3>
</div>
<hr />
<EditForm Model="Board" OnValidSubmit="@HandleValidSubmit">
<ObjectGraphDataAnnotationsValidator />
<div class="form-group row">
<label for="Company" class="col-sm-2 col-form-label">Company</label>
<div class="col-sm-10">
<InputSelect id="Company" class="form-control" @bind-Value="Board.Company.Name">
<option value="" disabled selected>Company</option>
@foreach (var company in Companies)
{
<option>@company.Value.Name</option>
}
</InputSelect>
<ValidationMessage For="@(() => Board.Company.Name)" />
</div>
</div>
<div class="form-group row">
<label for="Name" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<InputText id="Name" class="form-control" placeholder="Name" @bind-Value="Board.Name" />
<ValidationMessage For="@(() => Board.Name)" />
</div>
</div>
<div class="form-group row">
<label for="Description" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-10">
<InputText id="Description" class="form-control" placeholder="Description" @bind-Value="Board.Description" />
<ValidationMessage For="@(() => Board.Description)" />
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
@code {
private void GoToHome()
{
Navigation.NavigateTo("/");
}
private Board Board { get; set; } = new Board();
private Dictionary<Guid, Company> Companies = new Dictionary<Guid, Company>();
protected override async Task OnInitializedAsync()
{
try
{
Companies = await Http.GetFromJsonAsync<Dictionary<Guid, Company>>("api/Companies");
}
catch (Exception)
{
Console.WriteLine("Exception occurred for GET companies");
}
}
private async void HandleValidSubmit()
{
try
{
var response = await Http.PostAsJsonAsync("/api/Boards", Board);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var board = JsonConvert.DeserializeObject<Board>(content);
Navigation.NavigateTo($"/read_board/{board.Id}");
}
catch (Exception)
{
Console.WriteLine("Exception occurred for POST board");
}
}
}
我认为设置名称不会给您带来任何乐趣;即使上下文存在的时间足够长(它不应该存在;上下文应该只在 API 的请求存在时存在)看到你使用它之前下载的公司名称它会看到一个 Guid.Empty(默认值)和(大概你已经告诉 EF 它是数据库生成的)这将使上下文认为公司是名称为 X
的新公司
相反,我认为我应该让实体遵循典型的“让 CompanyId 成为董事会成员并将其设置在那里”的路线,而不是在新的相关实体上设置名称:
<InputSelect id="Company" class="form-control" @bind-Value="Board.CompanyId">
<option value="" disabled selected>Company</option>
@foreach (var company in Companies)
{
<option value="@company.Key">@company.Value.Name</option>
}
</InputSelect>
这应该保存,EF 将看到公司 ID 并连接相关公司。
如果您不同意这种做法(将 CompanyId 实体添加到 Board),您可以采用:
- 在保存之前通过 ID 下载该公司,并将其指定为公司 - 然后您将使用更改跟踪器之前看到的公司实例,它将知道如何连接到现有公司而不是创建一个新的,例如
<InputSelect id="Company" class="form-control" @bind-Value="Board.Company.Id">
<option value="" disabled selected>Company</option>
@foreach (var company in Companies)
{
<option value="@company.Key">@company.Value.Name</option>
}
</InputSelect>
[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
board.Company = _context.Companies.Find(board.Company.Id); // download existing co with that ID
_context.Boards.Add(board);
await _context.SaveChangesAsync();
return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}
或
- 看看欺骗更改 tracker/context 以为它已经看到你用 Id X 创建的新公司。我个人不是粉丝,但是:
[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
_context.Boards.Add(board);
_context.Entries(board.Company).State = EntityState.Unchanged; //don't try to save the Company
await _context.SaveChangesAsync();
return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}
我正在尝试在我的网站 api 中使用 Post 方法创建一个复杂对象。但是我很难做到这一点,因为当我创建一个 Board 对象时,我要求它有一个 Board.Company.Name 将它与公司相关联。但是,当我 select 一个已经存在的公司名称并处理有效提交时,将使用我选择的 Board.Company.Name 创建一个新公司。然后我显示我创建的董事会,看起来实际上没有公司与之关联。下面我包含了相关代码。这是我使用 C# 和 Blazor 的第一个项目,所以如果我遗漏了任何重要的内容,请告诉我,我会添加它。
公司模式
public class Company
{
[Key]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
public DateTime Founded { get; set; }
}
棋盘模型
public class Board
{
[Key]
public Guid Id { get; set; }
[ValidateComplexType]
public Company Company { get; set; } = new();
[Required]
public string Name { get; set; }
public string Description { get; set; }
public List<Ticket> Tickets { get; set; }
public Board()
{
}
}
Api POST 方法
[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
_context.Boards.Add(board);
await _context.SaveChangesAsync();
return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}
Create_Board
@page "/create_board"
@inject NavigationManager Navigation
@inject HttpClient Http
<div>
<button class="btn btn-outline-secondary oi oi-arrow-left" @onclick="GoToHome"></button>
<h3 class="text-center">Create a board</h3>
</div>
<hr />
<EditForm Model="Board" OnValidSubmit="@HandleValidSubmit">
<ObjectGraphDataAnnotationsValidator />
<div class="form-group row">
<label for="Company" class="col-sm-2 col-form-label">Company</label>
<div class="col-sm-10">
<InputSelect id="Company" class="form-control" @bind-Value="Board.Company.Name">
<option value="" disabled selected>Company</option>
@foreach (var company in Companies)
{
<option>@company.Value.Name</option>
}
</InputSelect>
<ValidationMessage For="@(() => Board.Company.Name)" />
</div>
</div>
<div class="form-group row">
<label for="Name" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<InputText id="Name" class="form-control" placeholder="Name" @bind-Value="Board.Name" />
<ValidationMessage For="@(() => Board.Name)" />
</div>
</div>
<div class="form-group row">
<label for="Description" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-10">
<InputText id="Description" class="form-control" placeholder="Description" @bind-Value="Board.Description" />
<ValidationMessage For="@(() => Board.Description)" />
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</EditForm>
@code {
private void GoToHome()
{
Navigation.NavigateTo("/");
}
private Board Board { get; set; } = new Board();
private Dictionary<Guid, Company> Companies = new Dictionary<Guid, Company>();
protected override async Task OnInitializedAsync()
{
try
{
Companies = await Http.GetFromJsonAsync<Dictionary<Guid, Company>>("api/Companies");
}
catch (Exception)
{
Console.WriteLine("Exception occurred for GET companies");
}
}
private async void HandleValidSubmit()
{
try
{
var response = await Http.PostAsJsonAsync("/api/Boards", Board);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var board = JsonConvert.DeserializeObject<Board>(content);
Navigation.NavigateTo($"/read_board/{board.Id}");
}
catch (Exception)
{
Console.WriteLine("Exception occurred for POST board");
}
}
}
我认为设置名称不会给您带来任何乐趣;即使上下文存在的时间足够长(它不应该存在;上下文应该只在 API 的请求存在时存在)看到你使用它之前下载的公司名称它会看到一个 Guid.Empty(默认值)和(大概你已经告诉 EF 它是数据库生成的)这将使上下文认为公司是名称为 X
的新公司相反,我认为我应该让实体遵循典型的“让 CompanyId 成为董事会成员并将其设置在那里”的路线,而不是在新的相关实体上设置名称:
<InputSelect id="Company" class="form-control" @bind-Value="Board.CompanyId">
<option value="" disabled selected>Company</option>
@foreach (var company in Companies)
{
<option value="@company.Key">@company.Value.Name</option>
}
</InputSelect>
这应该保存,EF 将看到公司 ID 并连接相关公司。
如果您不同意这种做法(将 CompanyId 实体添加到 Board),您可以采用:
- 在保存之前通过 ID 下载该公司,并将其指定为公司 - 然后您将使用更改跟踪器之前看到的公司实例,它将知道如何连接到现有公司而不是创建一个新的,例如
<InputSelect id="Company" class="form-control" @bind-Value="Board.Company.Id">
<option value="" disabled selected>Company</option>
@foreach (var company in Companies)
{
<option value="@company.Key">@company.Value.Name</option>
}
</InputSelect>
[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
board.Company = _context.Companies.Find(board.Company.Id); // download existing co with that ID
_context.Boards.Add(board);
await _context.SaveChangesAsync();
return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}
或
- 看看欺骗更改 tracker/context 以为它已经看到你用 Id X 创建的新公司。我个人不是粉丝,但是:
[HttpPost]
public async Task<ActionResult<Board>> PostBoard(Board board)
{
_context.Boards.Add(board);
_context.Entries(board.Company).State = EntityState.Unchanged; //don't try to save the Company
await _context.SaveChangesAsync();
return CreatedAtAction("GetBoard", new { id = board.Id }, board);
}