通过 Web Api 发送项目列表的更好方法
Better way to send a list of items trough WebApi
我有一个 Web API 配置为发送一个 POST 到 SQL 服务器。
我还有一个服务器应用程序 (SignalR),它向 POST API.
发送一个字符串列表
问题:Post每次只接收一个项目,所以我在循环中多次发送请求,每次迭代都会发送一个新项目。
它有效,但我相信有一种优化的方法可以做到这一点,如果在迭代中出现问题,正确的做法是取消交易,但使用这种循环方法,这是不可能的。
我正在接受有关如何更好地处理此问题的提示。
WebApi:
VisitaItemControl.cs
public class VisitaItemControl
{
public string ItemID { get; set; }
public string VisitaID { get; set; }
}
VisitaItemControlController.cs
[Route("api/[controller]")]
[ApiController]
public class VisitaItemControlController : ControllerBase
{
private readonly IConfiguration _configuration;
public VisitaItemControlController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost]
public JsonResult Post(VisitaItemControl visitaItemControl)
{
string query = @"INSERT INTO VisitaItemControl (
ItemID,
VisitaID)
VALUES (
@ItemID,
@VisitaID
)";
DataTable dt = new DataTable();
string sqlDataSource = _configuration.GetConnectionString("connectionstring");
SqlDataReader sqlDataReader;
using (SqlConnection sqlConnection = new SqlConnection(sqlDataSource))
{
sqlConnection.Open();
using (SqlCommand cmd = new SqlCommand(query, sqlConnection))
{
cmd.Parameters.AddWithValue(@"ItemID", visitaItemControl.ItemID);
cmd.Parameters.AddWithValue(@"VisitaID", visitaItemControl.VisitaID);
sqlDataReader = cmd.ExecuteReader();
dt.Load(sqlDataReader);
sqlDataReader.Close();
sqlConnection.Close();
}
}
return new JsonResult("Saved!");
}
}
SignalR 应用程序:
foreach (var item in addedItems)
{
var postObject = new VisitaItemControl()
{
ItemID = item.ItemID,
VisitaID = empObj.VisitaID,
};
var request2 = new HttpRequestMessage(HttpMethod.Post, config["API_POST"]);
request2.Content = new StringContent(JsonSerializer.Serialize(postObject), null, "application/json");
var client2 = ClientFactory.CreateClient();
var response2 = await client.SendAsync(request2);
using var responseStream2 = await response2.Content.ReadAsStreamAsync();
string res2 = await JsonSerializer.DeserializeAsync<string>(responseStream2);
}
await JS.InvokeVoidAsync("alert", "Saved!");
await refreshList();
uriHelper.NavigateTo("/", forceLoad: true);
}
以下是您尝试做的事情的结构化方法的基础知识。
我已经使用 Entity Framework 来管理数据库和 InMemory Implemnentation 用于演示目的。我已经在 Blazor Server 项目中实现了所有内容,因此我们可以测试和管理 UI 中的数据,并使用 Postman 与 API.
进行交互
项目包:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.4" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
</ItemGroup>
我的 InMemory 数据库上下文:
public class InMemoryDbContext : DbContext
{
public DbSet<VisitaItemControl>? VisitaItemControl { get; set; }
public InMemoryDbContext(DbContextOptions<InMemoryDbContext> options) : base(options) { }
}
我们将使用 DBContextFactory 服务来管理将使用它作为源 DbContext 的数据库连接。
我的数据代理接口 - 这通常会实现所有 CRUD 进程。我们使用接口将应用程序与数据存储分离。
public interface IDataBroker
{
public ValueTask<bool> AddItems<TRecord>(IEnumerable<TRecord> items) where TRecord : class;
public ValueTask<IEnumerable<TRecord>> GetItems<TRecord>(int count) where TRecord : class;
}
还有我的服务器实现 - 注意我注入了 DbContextFactory 来管理我的数据库连接。
public class ServerDataBroker : IDataBroker
{
private readonly IDbContextFactory<InMemoryDbContext> database;
public ServerDataBroker(IDbContextFactory<InMemoryDbContext> db)
=> this.database = db;
public async ValueTask<bool> AddItems<TRecord>(IEnumerable<TRecord> items) where TRecord : class
{
var result = false;
using var dbContext = database.CreateDbContext();
foreach (var item in items)
dbContext.Add(item);
var rowsAdded = await dbContext.SaveChangesAsync();
if (rowsAdded == items.Count())
result = true;
// Do something if not all rows are added
return result;
}
public async ValueTask<IEnumerable<TRecord>> GetItems<TRecord>(int count) where TRecord : class
{
using var dbContext = database.CreateDbContext();
return await dbContext.Set<TRecord>()
.Take(count)
.ToListAsync();
}
}
对于 UI,我构建了一个非常简单的视图服务来保存和管理数据:
public class VisitaItemControlService
{
private IDataBroker _broker;
public event EventHandler? ListUpdated;
public IEnumerable<VisitaItemControl> Records { get; protected set; } = new List<VisitaItemControl>();
public VisitaItemControlService(IDataBroker dataBroker)
=> _broker = dataBroker;
public async ValueTask<bool> AddItems(IEnumerable<VisitaItemControl> items)
{
var result = await _broker.AddItems<VisitaItemControl>(items);
if (result)
{
await this.GetItems(1000);
this.ListUpdated?.Invoke(this, EventArgs.Empty);
}
return result;
}
public async ValueTask GetItems(int count)
=> this.Records = await _broker.GetItems<VisitaItemControl>(count);
}
这是我用来测试系统的索引页面。
@page "/"
@inject VisitaItemControlService service;
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<div>
<button class="btn btn-primary" @onclick=AddItems>Add Some Items</button>
</div>
@if (loaded)
{
@foreach (var item in this.service.Records)
{
<div class="p-2">
<span>
Item : @item.ItemID
</span>
<span>
Visita : @item.VisitaID
</span>
</div>
}
}
@code {
private bool loaded = false;
protected async override Task OnInitializedAsync()
{
await this.service.GetItems(1000);
this.service.ListUpdated += this.OnListUpdated;
this.loaded = true;
}
private async Task AddItems()
{
var addList = new List<VisitaItemControl> {
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "AA" },
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "BB" },
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "CC" }
};
await this.service.AddItems(addList);
}
private void OnListUpdated(object? sender, EventArgs e)
=> this.InvokeAsync(StateHasChanged);
}
注意使用事件通知 UI 列表已更改并触发 re-render。
这是我的 API 控制器:
[ApiController]
public class VisitaItemControlController : ControllerBase
{
private IDataBroker _dataBroker;
public VisitaItemControlController(IDataBroker dataBroker)
=> _dataBroker = dataBroker;
[Route("/api/[controller]/list")]
[HttpGet]
public async Task<IActionResult> GetRecordsAsync()
{
var list = await _dataBroker.GetItems<VisitaItemControl>(1000);
return Ok(list);
}
[Route("/api/[controller]/addlist")]
[HttpPost]
public async Task<bool> AddRecordsAsync([FromBody] IEnumerable<VisitaItemControl> records)
=> await _dataBroker.AddItems(records);
}
最后Program
配置所有服务和中间件。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddDbContextFactory<InMemoryDbContext>(options => options.UseInMemoryDatabase("TestDb"));
builder.Services.AddSingleton<IDataBroker, ServerDataBroker>();
builder.Services.AddScoped<VisitaItemControlService>();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/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.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
一些 postman 屏幕截图:
以及项目结构:
我有一个 Web API 配置为发送一个 POST 到 SQL 服务器。 我还有一个服务器应用程序 (SignalR),它向 POST API.
发送一个字符串列表问题:Post每次只接收一个项目,所以我在循环中多次发送请求,每次迭代都会发送一个新项目。
它有效,但我相信有一种优化的方法可以做到这一点,如果在迭代中出现问题,正确的做法是取消交易,但使用这种循环方法,这是不可能的。 我正在接受有关如何更好地处理此问题的提示。
WebApi:
VisitaItemControl.cs
public class VisitaItemControl
{
public string ItemID { get; set; }
public string VisitaID { get; set; }
}
VisitaItemControlController.cs
[Route("api/[controller]")]
[ApiController]
public class VisitaItemControlController : ControllerBase
{
private readonly IConfiguration _configuration;
public VisitaItemControlController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost]
public JsonResult Post(VisitaItemControl visitaItemControl)
{
string query = @"INSERT INTO VisitaItemControl (
ItemID,
VisitaID)
VALUES (
@ItemID,
@VisitaID
)";
DataTable dt = new DataTable();
string sqlDataSource = _configuration.GetConnectionString("connectionstring");
SqlDataReader sqlDataReader;
using (SqlConnection sqlConnection = new SqlConnection(sqlDataSource))
{
sqlConnection.Open();
using (SqlCommand cmd = new SqlCommand(query, sqlConnection))
{
cmd.Parameters.AddWithValue(@"ItemID", visitaItemControl.ItemID);
cmd.Parameters.AddWithValue(@"VisitaID", visitaItemControl.VisitaID);
sqlDataReader = cmd.ExecuteReader();
dt.Load(sqlDataReader);
sqlDataReader.Close();
sqlConnection.Close();
}
}
return new JsonResult("Saved!");
}
}
SignalR 应用程序:
foreach (var item in addedItems)
{
var postObject = new VisitaItemControl()
{
ItemID = item.ItemID,
VisitaID = empObj.VisitaID,
};
var request2 = new HttpRequestMessage(HttpMethod.Post, config["API_POST"]);
request2.Content = new StringContent(JsonSerializer.Serialize(postObject), null, "application/json");
var client2 = ClientFactory.CreateClient();
var response2 = await client.SendAsync(request2);
using var responseStream2 = await response2.Content.ReadAsStreamAsync();
string res2 = await JsonSerializer.DeserializeAsync<string>(responseStream2);
}
await JS.InvokeVoidAsync("alert", "Saved!");
await refreshList();
uriHelper.NavigateTo("/", forceLoad: true);
}
以下是您尝试做的事情的结构化方法的基础知识。
我已经使用 Entity Framework 来管理数据库和 InMemory Implemnentation 用于演示目的。我已经在 Blazor Server 项目中实现了所有内容,因此我们可以测试和管理 UI 中的数据,并使用 Postman 与 API.
进行交互项目包:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.4" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
</ItemGroup>
我的 InMemory 数据库上下文:
public class InMemoryDbContext : DbContext
{
public DbSet<VisitaItemControl>? VisitaItemControl { get; set; }
public InMemoryDbContext(DbContextOptions<InMemoryDbContext> options) : base(options) { }
}
我们将使用 DBContextFactory 服务来管理将使用它作为源 DbContext 的数据库连接。
我的数据代理接口 - 这通常会实现所有 CRUD 进程。我们使用接口将应用程序与数据存储分离。
public interface IDataBroker
{
public ValueTask<bool> AddItems<TRecord>(IEnumerable<TRecord> items) where TRecord : class;
public ValueTask<IEnumerable<TRecord>> GetItems<TRecord>(int count) where TRecord : class;
}
还有我的服务器实现 - 注意我注入了 DbContextFactory 来管理我的数据库连接。
public class ServerDataBroker : IDataBroker
{
private readonly IDbContextFactory<InMemoryDbContext> database;
public ServerDataBroker(IDbContextFactory<InMemoryDbContext> db)
=> this.database = db;
public async ValueTask<bool> AddItems<TRecord>(IEnumerable<TRecord> items) where TRecord : class
{
var result = false;
using var dbContext = database.CreateDbContext();
foreach (var item in items)
dbContext.Add(item);
var rowsAdded = await dbContext.SaveChangesAsync();
if (rowsAdded == items.Count())
result = true;
// Do something if not all rows are added
return result;
}
public async ValueTask<IEnumerable<TRecord>> GetItems<TRecord>(int count) where TRecord : class
{
using var dbContext = database.CreateDbContext();
return await dbContext.Set<TRecord>()
.Take(count)
.ToListAsync();
}
}
对于 UI,我构建了一个非常简单的视图服务来保存和管理数据:
public class VisitaItemControlService
{
private IDataBroker _broker;
public event EventHandler? ListUpdated;
public IEnumerable<VisitaItemControl> Records { get; protected set; } = new List<VisitaItemControl>();
public VisitaItemControlService(IDataBroker dataBroker)
=> _broker = dataBroker;
public async ValueTask<bool> AddItems(IEnumerable<VisitaItemControl> items)
{
var result = await _broker.AddItems<VisitaItemControl>(items);
if (result)
{
await this.GetItems(1000);
this.ListUpdated?.Invoke(this, EventArgs.Empty);
}
return result;
}
public async ValueTask GetItems(int count)
=> this.Records = await _broker.GetItems<VisitaItemControl>(count);
}
这是我用来测试系统的索引页面。
@page "/"
@inject VisitaItemControlService service;
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<div>
<button class="btn btn-primary" @onclick=AddItems>Add Some Items</button>
</div>
@if (loaded)
{
@foreach (var item in this.service.Records)
{
<div class="p-2">
<span>
Item : @item.ItemID
</span>
<span>
Visita : @item.VisitaID
</span>
</div>
}
}
@code {
private bool loaded = false;
protected async override Task OnInitializedAsync()
{
await this.service.GetItems(1000);
this.service.ListUpdated += this.OnListUpdated;
this.loaded = true;
}
private async Task AddItems()
{
var addList = new List<VisitaItemControl> {
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "AA" },
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "BB" },
new VisitaItemControl { ItemID = Guid.NewGuid().ToString(), VisitaID = "CC" }
};
await this.service.AddItems(addList);
}
private void OnListUpdated(object? sender, EventArgs e)
=> this.InvokeAsync(StateHasChanged);
}
注意使用事件通知 UI 列表已更改并触发 re-render。
这是我的 API 控制器:
[ApiController]
public class VisitaItemControlController : ControllerBase
{
private IDataBroker _dataBroker;
public VisitaItemControlController(IDataBroker dataBroker)
=> _dataBroker = dataBroker;
[Route("/api/[controller]/list")]
[HttpGet]
public async Task<IActionResult> GetRecordsAsync()
{
var list = await _dataBroker.GetItems<VisitaItemControl>(1000);
return Ok(list);
}
[Route("/api/[controller]/addlist")]
[HttpPost]
public async Task<bool> AddRecordsAsync([FromBody] IEnumerable<VisitaItemControl> records)
=> await _dataBroker.AddItems(records);
}
最后Program
配置所有服务和中间件。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddDbContextFactory<InMemoryDbContext>(options => options.UseInMemoryDatabase("TestDb"));
builder.Services.AddSingleton<IDataBroker, ServerDataBroker>();
builder.Services.AddScoped<VisitaItemControlService>();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/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.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
一些 postman 屏幕截图:
以及项目结构: