ASP.NET Core应用程序14:创建表单应用程序

创建ASP.NET Core MVC应用程序(2)-利用MySQL Connector NET连接到MySQL 创建ASP.NET Core MVC应用程序(2)-利用MySQL Connector NET连接到MySQL用惯.NET的研发人员都习惯性地使用SQLServer作为数据库。然而.NET Core都玩开源了,那么本文我就采用MySQL数据库。安装MySQL首先从官网下载MySQL安装包。在Mac下会安装到/usr/local/mysql/bin/mysql文件目录下。Mac下安装完成之后可以在系统 阅读详情

  前几章集中讨论了处理 HTML 表单一个方面的单个特性,有时很难看到它们如何组合在一起执行常见的任务。本章将介绍创建控制器、视图和 Razor Pages 的过程,这些页面支持具有创建,读取、更新和删除(CRUD)功能的应用程序。本章不介绍新的功能,目标是演示如何将标签助手模型绑定和模型验证等功能与 Entity Framework Core 结合使用。

1 准备工作

  本章继续使用上章项目。
  为准备本章,请把 Controllers 文件夹中 HomeController.cs 文件的内容替换为如下代码。

[AutoValidateAntiforgeryToken]
public class HomeController : Controller
{
    private DataContext context;
    private IEnumerable<Category> Categories => context.Categories;
    private IEnumerable<Supplier> Suppliers => context.Suppliers;
    public HomeController(DataContext data)
    {
        context = data;
    }
    public IActionResult Index()
    {
        return View(context.Products.
            Include(p => p.Category).Include(p => p.Supplier));
    }
}

  添加 Views/Home 文件夹中 Index.cshtm。

@model IEnumerable<Product>
@{
    Layout = "_SimpleLayout";
}

<h4 class="bg-primary text-white text-center p-2">Products</h4>
<table class="table table-sm table-bordered table-striped">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Price</th>
            <th>Category</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (Product p in Model)
        {
            <tr>
                <td>@p.ProductId</td>
                <td>@p.Name</td>
                <td>@p.Price</td>
                <td>@p.Category.Name</td>
                <td class="text-center">
                    <a asp-action="Details" asp-route-id="@p.ProductId"
                       class="btn btn-sm btn-info">Details</a>
                    <a asp-action="Edit" asp-route-id="@p.ProductId"
                       class="btn btn-sm btn-warning">Edit</a>
                    <a asp-action="Delete" asp-route-id="@p.ProductId"
                       class="btn btn-sm btn-danger">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>
<a asp-action="Create" class="btn btn-primary">Create</a>

  接下来,更新 Product 类,以更改验证约束,删除模型级检査,并禁用用远程验证。

//[PhraseAndPrice(Phrase = "Small", Price = "100")]
public class Product
{
    public long ProductId { get; set; }
    [Required]
    [Display(Name = "Name")]
    public string Name { get; set; }
    [Column(TypeName = "decimal(8, 2)")]
    [Required(ErrorMessage = "Please enter a price")]
    [Range(1, 999999, ErrorMessage = "Please enter a positive price")]
    public decimal Price { get; set; }
    [PrimaryKey(ContextType = typeof(DataContext),
        DataType = typeof(Category))]
    //[Remote("CategoryKey", "Validation", 
    //     ErrorMessage = "Enter an existing key")]
    public long CategoryId { get; set; }
    public Category Category { get; set; }
    [PrimaryKey(ContextType = typeof(DataContext),
        DataType = typeof(Category))]
    //[Remote("SupplierKey", "Validation", 
    //    ErrorMessage = "Enter an existing key")]
    public long SupplierId { get; set; }
    public Supplier Supplier { get; set; }
}

  最后,在 Startup 类中禁用全局过滤器。

//services.Configure<MvcOptions>(opts =>
//{
//    opts.Filters.Add<HttpsOnlyAttribute>();
//    opts.Filters.Add(new MessageAttribute("This is the globally-scoped filter"));
//});

  使用浏览器请求 http://ocalhost:5000/controllers,这将显示一个产品列表,有-些锚定元素样式化为按钮,但直到添加了创建、编辑和删除对象的功能之后,这些元素才会有效。

2 创建 MVC 表单应用程序

  在接下来的部分中,将展示如何使用 MVC 控制器和视图执行核心数据操作。本章的后面使用 Razor 页面(Razor Pages)创建相同的功能。

2.1 准备视图模型和视图

  下面定义一个用于多个操作的表单,通过它的视图模型类进行配置。要创建视图模型类,向Models 文件夹添加一个名为 ProductViewModel.cs 的类文件。

public class ProductViewModel
{
    public Product Product { get; set; }
    public string Action { get; set; } = "Create";
    public bool ReadOnly { get; set; } = false;
    public string Theme { get; set; } = "primary";
    public bool ShowAction { get; set; } = true;
    public IEnumerable<Category> Categories { get; set; }
        = Enumerable.Empty<Category>();
    public IEnumerable<Supplier> Suppliers { get; set; }
        = Enumerable.Empty<Supplier>();
}

  这个类将允许控制器向其视图传递数据和显示设置。Product 属性提供要显示的数据。Categories 和 Suppliers 属性提供在需要时对 Category 和 Supplier 对象的访问。
  其他属性配置给用户呈现内容的方式:Action 属性给当前任务指定操作方法的名称,ReadOnly 属性指定用户是否可以编辑数据,Theme 属性指定内容的引导主题,ShowAction 属性用于控制提交表单的按钮可
见性。
  要创建允许用户与应用程序数据交互的视图,给 Views/Home 文件夹添加 ProductEditor.cshtmnl 的 Razor 视图。

@model ProductViewModel
@{
    Layout = "_SimpleLayout";
}

<partial name="_Validation" />

<h5 class="bg-@Model.Theme text-white text-center p-2">@Model.Action</h5>

<form asp-action="@Model.Action" method="post">
    <div class="form-group">
        <label asp-for="Product.ProductId"></label>
        <input class="form-control" asp-for="Product.ProductId" readonly />
    </div>
    <div class="form-group">
        <label asp-for="Product.Name"></label>
        <div>
            <span asp-validation-for="Product.Name" class="text-danger"></span>
        </div>
        <input class="form-control" asp-for="Product.Name"
               readonly="@Model.ReadOnly" />
    </div>
    <div class="form-group">
        <label asp-for="Product.Price"></label>
        <div>
            <span asp-validation-for="Product.Price" class="text-danger"></span>
        </div>
        <input class="form-control" asp-for="Product.Price"
               readonly="@Model.ReadOnly" />
    </div>
    <div class="form-group">
        <label asp-for="Product.CategoryId">Category</label>
        <div>
            <span asp-validation-for="Product.CategoryId" class="text-danger"></span>
        </div>
        <select asp-for="Product.CategoryId" class="form-control"
                disabled="@Model.ReadOnly"
                asp-items="@(new SelectList(Model.Categories,
                    "CategoryId", "Name"))">
            <option value="" disabled selected>Choose a Category</option>
        </select>
    </div>
    <div class="form-group">
        <label asp-for="Product.SupplierId">Supplier</label>
        <div>
            <span asp-validation-for="Product.SupplierId" class="text-danger"></span>
        </div>
        <select asp-for="Product.SupplierId" class="form-control"
                disabled="@Model.ReadOnly"
                asp-items="@(new SelectList(Model.Suppliers,
                    "SupplierId", "Name"))">
            <option value="" disabled selected>Choose a Supplier</option>
        </select>
    </div>
    @if (Model.ShowAction)
    {
        <button class="btn btn-@Model.Theme" type="submit">@Model.Action</button>
    }
    <a class="btn btn-secondary" asp-action="Index">Back</a>
</form>

  这个视图可能看起来很复杂,但它只结合了前面章节介绍的特性,一旦看到它的实际应用,它将变得更加清晰。这个视图的模型是一个 ProductViewModel 对象,它既提供了显示给用户的数据,也提供了有关数据应该如何显示的一些信息。
  对于 Product 类定义的每个属性,视图包含一组元素:描述属性的 label 元素、允许对值进行编辑的 imput 或 select 元素,以及显示验证消息的 span 元素。每个元素都配置了 asp-for 特性,确保标签助手为每个属性转换元素。这里有定义视图结构的 div 元素,所有元素都是用于样式化表单的引导 CSS 类的成员。

2.2 读取数据

  最简单的操作是从数据库中读取数据并将其呈现给用户。在大多数应用程序中,这将允许用户看到列表视图中没有的额外细节。应用程序执行的每个任务都需要一组不同的 ProductViewModel 属性。为了管理这些组合,给 Models 文件夹添加一个名为 ViewModelFactory.cs 的类文件。

public static class ViewModelFactory
{
    public static ProductViewModel Details(Product p)
    {
        return new ProductViewModel
        {
            Product = p,
            Action = "Details",
            ReadOnly = true,
            Theme = "info",
            ShowAction = false,
            Categories = p == null ? Enumerable.Empty<Category>()
                : new List<Category> { p.Category },
            Suppliers = p == null ? Enumerable.Empty<Supplier>()
                : new List<Supplier> { p.Supplier },
        };
    }
}

  Details 方法生成一个为査看对象而配置的 ProducfViewModel 对象。当用户査看详细信息 Category 和 Supplier 信息时是只读的,这意味着只需要提供当前的 Category 和 Supplier 信息。
  接下来,向使用 VewModelFactoryDetais 方法的主控制器添加一个操作方法,创建 ProductViewModel对象,并使用 ProductEditor 视图将其显示给用户。

public async Task<IActionResult> Details(long id)
{
    Product p = await context.Products
        .Include(p => p.Category)
        .Include(p => p.Supplier)
        .FirstOrDefaultAsync(p => p.ProductId == id);
    ProductViewModel model = ViewModelFactory.Details(p);
    return View("ProductEditor", model);
}

  操作方法使用 id 参数(从路由数据中绑定模型)来查询数据库,并将 product 对象传递给ViewModelFactory.Details 方法。大多数操作都需要 Catcgory 和 Supplier 数据,因此添加了提供对数据的直接访问的属性。
  要测试 Details 特性,请求 http:/localhost:5000/controllers。单击其中一个Deaiis 按钮,将看到使用 ProductEditor 视图以只读形式显示选择的对象。

2.3 创建数据

  创建数据依赖于模型绑定从请求中获取表单数据,并依赖验证来确保数据可以存储在数据库中。第一步是添加一个工厂方法,创建用于创建数据的视图模型对象。在 Models 文件夹的 ViewModelFactory.cs 文件中添加创建方法。

public static ProductViewModel Create(Product product,
    IEnumerable<Category> categories, IEnumerable<Supplier> suppliers)
{
    return new ProductViewModel
    {
        Product = product,
        Categories = categories,
        Suppliers = suppliers
    };
}

  为 ProductViewModel 属性所使用的默认值是为创建数据而设置的,因此 Create 方法只设置 Product、Categories 和 Suppliers 属性。在 Home 控制器中创建数据操作方法。

public IActionResult Create()
{
    return View("ProductEditor",
        ViewModelFactory.Create(new Product(), Categories, Suppliers));
}
[HttpPost]
public async Task<IActionResult> Create([FromForm] Product product)
{
    if (ModelState.IsValid)
    {
        product.ProductId = default;
        product.Category = default;
        product.Supplier = default;
        context.Products.Add(product);
        await context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View("ProductEditor",
        ViewModelFactory.Create(product, Categories, Suppliers));
}

  有两个 Create 方法,它们通过 HttpPost 属性和方法参数进行区分。HTTP GET 请求将由第一个方法处理,该方法选择 ProductEditor 视图并为其提供一个 ProductViewModel 对象。当用户提交表单时,它将被第二种方法接收,该方法依赖模型绑定来接收数据,并通过模型验证来确保数据有效。
  如果数据通过验证,那么通过重置三个属性来准备存储在数据库中的对象。Entity Framework Core 配置数据库,以便在存储新数据时由数据库服务器分配主键。如果试图存储一个对象并提供一个不为零的 Productld 值,将抛出一个异常。
  重置了 Category 和 Supplier 属性,以防止存储对象时,EF Core 试图处理相关数据。注意,当验证失败时,使用参数调用 View 方法。
  之所以这样做,是因为视图期望的视图模型对象与前面使用模型绑定从请求中提取的数据类型不同。相反,这里创建了一个新的视图模型对象,该对象包含模型绑定数据,并将其传递给 View 方法。
  重启 ASP.NET Core,请求 http://hocalhost:5000/controllers,然后单击 Create。填写表单并单击 Create 按钮提交数据。新对象存储在数据库中,并在浏览器重定向到 Index 操作时显示出来。

2.4 编辑数据

  向视图模型工厂添加一个新方法,该方法将配置向用户显示数据的方式,Models 文件夹的 ViewModelFactory.cs 文件中添加编辑方法。
向在 Controllers 文件夹的 HomeController.cs 添加操作方法,向用户显示 Product 对象的当前属性,并接收用户所做的更改。

public async Task<IActionResult> Edit(long id)
{
    Product p = await context.Products.FindAsync(id);
    ProductViewModel model = ViewModelFactory.Edit(p, Categories, Suppliers);
    return View("ProductEditor", model);
}
[HttpPost]
public async Task<IActionResult> Edit([FromForm] Product product)
{
    if (ModelState.IsValid)
    {
        product.Category = default;
        product.Supplier = default;
        context.Products.Update(product);
        await context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View("ProductEditor",
        ViewModelFactory.Edit(product, Categories, Suppliers));
}

2.5 删除数据

  在 Models 文件夹的 ViewModelFactory.cs 文件中添加删除方法。

public static ProductViewModel Delete(Product p,
    IEnumerable<Category> categories, IEnumerable<Supplier> suppliers)
{
    return new ProductViewModel
    {
        Product = p,
        Action = "Delete",
        ReadOnly = true,
        Theme = "danger",
        Categories = categories,
        Suppliers = suppliers
    };
}

  将删除操作方法添加到 Home 控制器中。

public async Task<IActionResult> Delete(long id)
{
    ProductViewModel model = ViewModelFactory.Delete(
        await context.Products.FindAsync(id), Categories, Suppliers);
    return View("ProductEditor", model);
}
[HttpPost]
public async Task<IActionResult> Delete(Product product)
{
    context.Products.Remove(product);
    await context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}

3 创建 Razor Pages 表单应用程序

   Pages 文件夹中创建 Index.cshtml Razor Pages,并使用浏览器请求 http://localhost:5000/pages。

@page "/pages/{id:long?}"
@model IndexModel
@using Microsoft.AspNetCore.Mvc.RazorPages
@using Microsoft.EntityFrameworkCore

<div class="m-2">
    <h4 class="bg-primary text-white text-center p-2">Products</h4>
    <table class="table table-sm table-bordered table-striped">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Price</th>
                <th>Category</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (Product p in Model.Products)
            {
                <tr>
                    <td>@p.ProductId</td>
                    <td>@p.Name</td>
                    <td>@p.Price</td>
                    <td>@p.Category.Name</td>
                    <td class="text-center">
                        <a asp-page="Details" asp-route-id="@p.ProductId"
                           class="btn btn-sm btn-info">Details</a>
                        <a asp-page="Edit" asp-route-id="@p.ProductId"
                           class="btn btn-sm btn-warning">Edit</a>
                        <a asp-page="Delete" asp-route-id="@p.ProductId"
                           class="btn btn-sm btn-danger">Delete</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <a asp-page="Create" class="btn btn-primary">Create</a>
</div>

@functions
{

    public class IndexModel : PageModel
    {
        private DataContext context;

        public IndexModel(DataContext dbContext)
        {
            context = dbContext;
        }

        public IEnumerable<Product> Products { get; set; }

        public void OnGetAsync(long id = 1)
        {
            Products = context.Products
                .Include(p => p.Category).Include(p => p.Supplier);
        }
    }
}

3.1 创建常用功能

  这里不在示例应用程序所需的每个页面中复制相同的 HTML 表单和支持代码。相反,定义一个部分视图来定义 HTM 表单,再定义一个基类,来定义页面模型类所需的公共代码。对于部分视图,给 pages 文件夹添加一个名为 _ProductEditor.cshtml。

@model ProductViewModel

<partial name="_Validation" />

<h5 class="bg-@Model.Theme text-white text-center p-2">@Model.Action</h5>

<form asp-page="@Model.Action" method="post">
    <div class="form-group">
        <label asp-for="Product.ProductId"></label>
        <input class="form-control" asp-for="Product.ProductId" readonly />
    </div>
    <div class="form-group">
        <label asp-for="Product.Name"></label>
        <div>
            <span asp-validation-for="Product.Name" class="text-danger"></span>
        </div>
        <input class="form-control" asp-for="Product.Name"
               readonly="@Model.ReadOnly" />
    </div>
    <div class="form-group">
        <label asp-for="Product.Price"></label>
        <div>
            <span asp-validation-for="Product.Price" class="text-danger"></span>
        </div>
        <input class="form-control" asp-for="Product.Price"
               readonly="@Model.ReadOnly" />
    </div>
    <div class="form-group">
        <label asp-for="Product.CategoryId">Category</label>
        <div>
            <span asp-validation-for="Product.CategoryId" class="text-danger"></span>
        </div>
        <select asp-for="Product.CategoryId" class="form-control"
                disabled="@Model.ReadOnly" 
                asp-items="@(new SelectList(Model.Categories,"CategoryId", "Name"))">
            @*<option value="-1">Create New Category...</option>*@
            <option value="" disabled selected>Choose a Category</option>
        </select>
    </div>
    @*<partial name="_CategoryEditor" for="Product" />*@
    <div class="form-group">
        <label asp-for="Product.SupplierId">
            Supplier
        </label>

        <div>
            <span asp-validation-for="Product.SupplierId" class="text-danger"></span>
        </div>
        <select asp-for="Product.SupplierId" class="form-control" 
                disabled="@Model.ReadOnly"
                asp-items="@(new SelectList(Model.Suppliers,
                "SupplierId", "Name"))">
            <option value="" disabled selected>Choose a Supplier</option>
        </select>
    </div>

    @if (Model.ShowAction)
    {
        <button class="btn btn-@Model.Theme" type="submit">@Model.Action</button>
    }
    <a class="btn btn-secondary" asp-page="Index">Back</a>
</form>

  部分视图使用 ProductViewModel 类作为它的模型类型,并依赖内置的标签助手为 Product 类定义的属性显示 input 和 select 元素。这与本章前面使用的内容相同,只是将 asp-action 属性替换为 asp-page,以指定 form 和 anchor 元素的目标。要定义页面模型基类,向 Pages 文件夹添加一个名为 EditorPageModel.cs 的类文件。

public class EditorPageModel : PageModel
{
    public EditorPageModel(DataContext dbContext)
    {
        DataContext = dbContext;
    }
    public DataContext DataContext { get; set; }
    public IEnumerable<Category> Categories => DataContext.Categories;
    public IEnumerable<Supplier> Suppliers => DataContext.Suppliers;
    public ProductViewModel ViewModel { get; set; }
}

  向 Pages 文件夹下的 _ViewImports.cshtml 添加名称空间。

@namespace MyWebApp.Pages
@using MyWebApp.Models
@addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *,MyWebApp
@using Microsoft.AspNetCore.Mvc.RazorPages
@using Microsoft.EntityFrameworkCore
@using MyWebApp.Pages
@using System.Text.Json
@using Microsoft.AspNetCore.Http

3.2 为 CRUD 操作定义页面

  有了部分视图和共享基类,处理各个操作的页面就十分简单了。给 Pages 文件夹添加一个名Details.cshtml 的Razor Pages。

@page "/pages/details/{id}"
@model DetailsModel

<div class="m-2">
    <partial name="_ProductEditor" model="@Model.ViewModel" />
</div>

@functions
{
    public class DetailsModel : EditorPageModel
    {
        public DetailsModel(DataContext dbContext) : base(dbContext) { }
        public async Task OnGetAsync(long id)
        {
            Product p = await DataContext.Products.
                Include(p => p.Category).Include(p => p.Supplier)
                .FirstOrDefaultAsync(p => p.ProductId == id);
            ViewModel = ViewModelFactory.Details(p);
        }
    }
}

  给Pages 文件夹添加一个名为 Create.cshtml 的 Razor Pages。

@page "/pages/create"
@model CreateModel

<div class="m-2">
    <partial name="_ProductEditor" model="@Model.ViewModel" />
</div>

@functions 
{
    public class CreateModel : EditorPageModel
    {
        public CreateModel(DataContext dbContext) : base(dbContext) { }

        public void OnGet()
        {
            ViewModel = ViewModelFactory.Create(p, Categories, Suppliers);
        }
        public async Task<IActionResult> OnPostAsync([FromForm] Product product)
        {
            if (ModelState.IsValid)
            {
                product.ProductId = default;
                product.Category = default;
                product.Supplier = default;
                DataContext.Products.Add(product);
                await DataContext.SaveChangesAsync();
                return RedirectToPage(nameof(Index));
            }
            ViewModel = ViewModelFactory.Create(product, Categories, Suppliers);
            return Page();
        }
    }
}

  给 Pages 文件夹添加一个名为 Edit.cshtml 的 RazorPages。

@page "/pages/edit/{id}"
@model EditModel

<div class="m-2">
    <partial name="_ProductEditor" model="@Model.ViewModel" />
</div>

@functions 
{
    public class EditModel : EditorPageModel
    {
        public EditModel(DataContext dbContext) : base(dbContext) { }

        public async Task OnGetAsync(long id)
        {
            Product p = await this.DataContext.Products.FindAsync(id);
            ViewModel = ViewModelFactory.Edit(p, Categories, Suppliers);
        }
        public async Task<IActionResult> OnPostAsync([FromForm] Product product)
        {
            if (ModelState.IsValid)
            {
                product.Category = default;
                product.Supplier = default;
                DataContext.Products.Update(product);
                await DataContext.SaveChangesAsync();
                return RedirectToPage(nameof(Index));
            }
            ViewModel = ViewModelFactory.Edit(product, Categories, Suppliers);
            return Page();
        }
    }
}

  给 Pages 文件夹添加一个名为 Delete.cshtml的 Razor Pages。

@page "/pages/delete/{id}"
@model DeleteModel

<div class="m-2">
    <partial name="_ProductEditor" model="@Model.ViewModel" />
</div>

@functions 
{
    public class DeleteModel : EditorPageModel
    {
        public DeleteModel(DataContext dbContext) : base(dbContext) { }

        public async Task OnGetAsync(long id)
        {
            ViewModel = ViewModelFactory.Delete(
                await DataContext.Products.FindAsync(id), Categories, Suppliers);
        }
        public async Task<IActionResult> OnPostAsync([FromForm] Product product)
        {
            DataContext.Products.Remove(product);
            await DataContext.SaveChangesAsync();
            return RedirectToPage(nameof(Index));
        }
    }
}

  重启 ASP.NET Core 并导航到 http://localhost:5000/pages,将能够单击链接,来查看、创建编辑和删除数据。

4 创建新的相关数据对象

  一些应用程序需要允许用户创建新的相关数据,这样,例如,新的类别可以与该类别中的产品一起创建。有两种方法可以解决这个问题。

4.1 在同一请求中提供相关数据

  第一种方法是要求用户提供以相同形式创建相关数据所需的数据。以用户输入Product 对象值的相同形式来收集 Category 对象信息,这对于简单数据类型是种有用的方法。
  给 Pages 文件夹添加为 _CategoryEditor.cshtml 的 Razor 视图,在各自的部分视图中定义相关数据类型的 HTML 元素。

@model Product
<script type="text/javascript">
    $(document).ready(() => {
        const catGroup = $("#categoryGroup").hide();
        $("select[name='Product.CategoryId']").on("change", (event) =>
            event.target.value === "-1" ? catGroup.show() : catGroup.hide());
    });
</script>

<div class="form-group bg-info p-1" id="categoryGroup">
    <label class="text-white" asp-for="Category.Name">
        New Category Name
    </label>
    <input class="form-control" asp-for="Category.Name" value="" />
</div>

  Category 类型只需要一个属性,用户使用标准 input 元素提供该属性。部分视图中的 script 元素包含隐藏新元素的 jQuery 代码,直到用户选择为 Product.CategoryId 属性设置 -1 值的 option 元素为止。
  在 Pages 文件夹的 _ProductEditor.cshtml 向 编辑 添加了部分视图,还显示 option 元素,该元素显示用于创建新 Category 对象的元素。

<option value="-1">Create New Category...</option>
<partial name="_CategoryEditor" for="Product" />

  需要在多个页面中使用新功能,因此为了避免代码重复,在 Pages 文件夹的 EditorPageModel.cs 文件中添加一个方法来处理页面模型基类中的相关数据。

protected async Task CheckNewCategory(Product product)
{
    if (product.CategoryId == -1 && 
        !string.IsNullOrEmpty(product.Category?.Name))
    {
        DataContext.Categories.Add(product.Category);
        await DataContext.SaveChangesAsync();
        product.CategoryId = product.Category.CategoryId;
        ModelState.Clear();
        TryValidateModel(product);
    }
}

  新代码使用从用户接收到的数据创建一个 Category 对象,并将其存储在数据库中。数据库服务器为新对象分配一个主键,EF Core 使用该主键来更新 Category 对象。这允许更新 Product 对象的 CategopyId 属性,重新验证模型数据;分配给 CategopyId 属性的值将通过验证,应为它对应于新分配的键。要将新功能集成到 Create 页面中,在 Pages 文件夹的 Create.cshtm 文件中添加语句。向 Edit 页面中的处理程序方法添加相同的语句。

public async Task<IActionResult> OnPostAsync([FromForm] Product product)
{
    await CheckNewCategory(product);
    ......
}

  重启并使用浏览器请求 http://localhost:5000/pages/edit/1。单击 Category 元素并从选项列表中选择 Create New Category。在 input 元素中输入新的类别名称,然后单击 Edit 按钮。当处理请求时,将一个新的 Categony 对象存储在数据库中并与 Product 对象相关联。

4.2 创建新数据

  对于具有自己复杂创建过程的相关数据类型,向主表单添加元素可能让用户不知所措;更好的方法是从主表单导航到另一个控制器或页面,让用户创建新对象,然后返回来完成原始任务。下面演示用于创建 Supplier 对象的技术,尽管 Supplier 类型很简单,只需要用户提供两个值。
  要创建允许用户创建 Supplier 对象的表单,给 Pages 文件夹添加名为 SupplierBreakOut.cshtml 的 Razor Pages。

@page "/pages/supplier"
@model SupplierPageModel

<div class="m-2">
    <h5 class="bg-secondary text-white text-center p-2">New Supplier</h5>
    <form asp-page="SupplierBreakOut" method="post">
        <div class="form-group">
            <label asp-for="Supplier.Name"></label>
            <input class="form-control" asp-for="Supplier.Name" />
        </div>
        <div class="form-group">
            <label asp-for="Supplier.City"></label>
            <input class="form-control" asp-for="Supplier.City" />
        </div>
        <button class="btn btn-secondary" type="submit">Create</button>
        <a class="btn btn-outline-secondary"
           asp-page="@Model.ReturnPage" asp-route-id="@Model.ProductId">
            Cancel
        </a>
    </form>
</div>

@functions 
{
    public class SupplierPageModel : PageModel
    {
        private DataContext context;

        public SupplierPageModel(DataContext dbContext)
        {
            context = dbContext;
        }

        [BindProperty]
        public Supplier Supplier { get; set; }

        public string ReturnPage { get; set; }
        public string ProductId { get; set; }

        public void OnGet([FromQuery(Name = "Product")] Product product,
                string returnPage)
        {
            TempData["product"] = Serialize(product);
            TempData["returnAction"] = ReturnPage = returnPage;
            TempData["productId"] = ProductId = product.ProductId.ToString();
        }

        public async Task<IActionResult> OnPostAsync()
        {
            context.Suppliers.Add(Supplier);
            await context.SaveChangesAsync();
            Product product = Deserialize(TempData["product"] as string);
            product.SupplierId = Supplier.SupplierId;
            TempData["product"] = Serialize(product);
            string id = TempData["productId"] as string;
            return RedirectToPage(TempData["returnAction"] as string,
                new { id = id });
        }

        private string Serialize(Product p) => JsonSerializer.Serialize(p);
        private Product Deserialize(string json) =>
            JsonSerializer.Deserialize<Product>(json);
    }
}

  用户使用 GET 请求导航到此页面,该请求包含用户提供的产品的详细信息和用户应该返回到的页面的名称。使用 TempData 功能来存储该数据。
  此页面向用户显示一个表单,其中包含创建新 Supplier 对象所需的 Name 和 City 属性字段。提交表单时,POST 处理程序方法存储一个新的 Supplier 对象,并使用数据库服务器分配的键来更新 product 对象,然后将其再次存储为临时数据。用户重定向到到达的页面。
  向 Pages 文件夹的 _ProductEditor.cshtml 部分视图添加了元素,允许用户导航到新页面。

<label asp-for="Product.SupplierId">
    Supplier
    @if (!Model.ReadOnly) { 
        <input type="hidden" name="returnPage" value="@Model.Action" />
        <button class="btn btn-sm btn-outline-primary ml-3" 
                asp-page="SupplierBreakOut" formmethod="get" formnovalidate>
            Create New Supplier
        </button>
    }
</label>

  新元素添加了一个隐藏的 input 元素(该元素捕获要返回的页面),以及一个 button 元素(该元素使用 GET请求将表单数据提交到 SupplierBreakOut页面,这意味着表单值编码到查询字符串中)。
  在Pages 文件夹的 Create.cshtml 文件中所需的更改,以添加对检索临时数据和使用它填充 Product表单的支持。

public void OnGet() {
    Product p = TempData.ContainsKey("product") 
        ? JsonSerializer.Deserialize<Product>(TempData["product"] as string) 
        : new Product();
    ViewModel = ViewModelFactory.Create(p, Categories, Suppliers);
}

  在 Edit 页面中需要进行类似的更改。

public async Task OnGetAsync(long id) {
    Product p = TempData.ContainsKey("product") 
        ? JsonSerializer.Deserialize<Product>(TempData["product"] as string) 
        : await this.DataContext.Products.FindAsync(id);
    ViewModel = ViewModelFactory.Edit(p, Categories, Suppliers);
}

  其效果是向用户显示 Create New Supplier 按钮,该按钮向浏览器发送到一个表单,该表单可用于创建 Supplier 对象。一旦 Supplier 存储在数据库中,浏览器就发送回原始页面,表单中填充用户输入的数据,并且 Supplier select 元素设置为新创建的对象。

PLC模糊控制模糊PID(梯形图实现+算法分析) 博途PLC的模糊PID控制详细内容请查看下面的博客文章:Matlab仿真+博途PLC模糊PID控制完整SCL源代码参考(带模糊和普通PID切换功能)_博途怎么实现模糊pid_RXXW_Dor的博客-优快云博客模糊PID的其它相关数学基础,理论知识大家可以参看专栏的其它文章,这里不再赘述,本文就双容水箱的模糊PID问题做详细讲解(包括所有源代码和模糊规则设置参数)。 阅读详情

相关推荐

微信PC端Multi文件夹数据库解析:语音消息导出与转换实战指南

本文提供了一份详细的微信PC端语音消息导出与转换实战指南。通过解析Multi文件夹中的MediaMSG数据库,指导用户定位、解密数据库,并利用Python脚本精准提取SILK格式语音,最终转换为通用的WAV或MP3格式,实现个人语音数据的本地备份与处理。

garlic的博客 580

ASP.NET CORE MVC 5 中带有会话的登录表单

创建 ASP.NET Core MVC 5 项目 在 Visual Studio 上,从“开始”中选择“创建新项目” 选择ASP.NET Core Web 应用程序 输入项目名称并选择新项目的位置 选择ASP.NET Core 5.0 版本并选择ASP.NET Core Empty Template。点击创建按钮完成 添加配置 打开Startup.cs文件并添加新配置,如下所示: using Microsoft.AspNetCore.Builder; usin..

allway2的博客 591

深入解析APS开源排程工具OptaPlanner:链式结构与移动优化

本文深入解析了开源APS排程工具OptaPlanner的核心机制,重点阐述了链式结构如何作为排程优化的“骨架”,通过单一`previous`变量隐式定义资源与顺序,从而简化建模。文章详细介绍了基于此结构的移动操作(如`ChainChangeMove`)如何驱动优化引擎探索解空间,并自动触发影子变量更新与分数计算,为构建高效、灵活的生产计划系统提供了实践指导。

keras9composer的博客 296

ASP.NET 在vs中制作web表单

表单作业: (1) 新建一个web窗体 (2) 为了页面好看,可以创建一个表格,将上述插件放到表格里 (3) 在设计视图中,vs最上面一栏工具栏中有工具 表—插入表格,根据要求,插入一个八行三列的表格 (4) 根据图片上的要求,第一行和最后一行只有一格,下面来把他们改成那样的。选中第一行,点击表—修改—合并单元格,第八行重复上述操作 (5) 下面,按照图片要求输入文字、放入插件(第二列的) (6) 如果感到布局不是很好看,可以选中整个表格,然后选择居中 这样居中是整个表居中,如果想让第一行和最后一

weixin_46067990的博客 3851

PyTorch深度学习实战(24)——使用U-Net架构进行图像分割

图像分割 (Image segmentation) 是计算机视觉领域中的一个重要任务,旨在将图像划分为具有语义信息的不同区域或对象。与目标检测任务不同,图像分割要求像素级别的精确定位和分类,将每个像素标记为属于哪个类别或属于哪个区域。在本节中,我们将介绍图像分割的基本概念,并训练一个基于 U-Net 架构的图像分割模型。

盼小辉丶的博客 1万+

数据挖掘实战-基于KMeans算法对超市客户进行聚类分群

本实验基于KMeans算法对超市客户进行了聚类分群。通过对客户购物数据进行聚类,我们成功将客户分为不同的群体。每个群体代表了具有相似购物行为和偏好的客户群体。通过实验结果,我们发现了客户群体之间的明显差异和共性。不同群体的客户在购买频率、购物金额和购买种类等方面有所区别。这为超市提供了重要的市场细分信息,帮助超市经营者更好地了解不同客户群体的需求和喜好,制定有针对性的营销策略和商品推荐,提高客户满意度和忠诚度。此外,实验还揭示了超市客户的购物行为规律,为超市优化商品陈列和促销策略提供了指导。

m0_64336780的博客 2万+

ASP.net学习之表单介绍

表单进行数据采集的

雨忆古风 1279

司法AI系统的架构设计:法律知识图谱如何辅助智能庭审?架构师亲测

简单来说,法律知识图谱是一种采用图数据结构(节点Node + 边Edge/关系Relation)来组织和表达法律领域知识的语义网络。它以。

AI 算法实战派 1088

ASP.NET Core应用程序1:创建示例项目

1 创建项目 创建一个MyWebApp的空项目。也可以用一下命令行创建。 dotnet new globaljson --sdk-version 3.1 --output MyWebApp 已成功创建模板“global.json file”。 dotnet new web --no-https --output MyWebApp --framework netcoreapp3.1 已成功创建模板...

噢~ 593

【CANoe】CAPL_ECU上电后首帧报文发出时间检测

【CANoe】CAPL_ECU上电后首帧报文发出时间检测,通过CANoe I/O口检测电压实现

星Yeah 的博客 2507

matlab RANSAC拟合圆柱【2025最新版】

RANSAC拟合圆柱

点云侠的博客 3135

Asp.net Core Mvc 7.0 Web 控制器接收Get/Post表单参数

记得.Net 5之前都是直接用 Request.Form 非常方便,但似乎6.0以后就不能这样用了。2、接受url的get参数,7.0好像可以继续使用Request.Query。1、接受post表单,使用 IFormCollection。get参数的获取,也可以把同名参数传入到函数的参数中。

"去昨天找你" 1408

数据库系统概论第五版课后习题答案第十二章

对元组的插入、删除、修改;语义检查的具体做法是,DBMS读取数据字典,检查是否存在与该语句中的表、视图或索引等要创建的对象相同的对象名,检查该用户是否具有创建数据库对象的权限。答:操作系统提供的封锁机制和DBMS的封锁机制在封锁对象、封锁对象的状态、封锁的粒度、及封锁的类型上存在很大的差别,操作系统的封锁机制不能直接应用在DBMS中,DBMS必须重新设计,来满足复杂的封锁需求。数据库的建立和维护包括数据库的初始建立、数据的转换、数据库的转储和恢复、数据库的重组织和重构造以及性能监测分析等功能。

wenling54321的博客 693

ASP.NET Core 面试宝典【刷题系列】

ASP.NET Core 是一个功能强大且灵活的框架,掌握其核心概念和高级特性对于面试和实际开发都非常重要。希望这篇博文能帮助你在 ASP.NET Core 面试中脱颖而出,顺利拿到心仪的职位!

李赛赛的专栏 2541

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

使用 ASP.NET Core 进行现代全栈 Web 开发: 14 React 与 ASP.NET Core 集成

将 React 与 ASP.NET Core 集成提供了一种开发可扩展全栈应用程序的强大方法。 React 能与 ASP.NET Core 无缝协作,构建动态、响应式的 Web 应用程序。 本章将指导您完成 React 与 ASP.NET Core 的配置步骤,使创建高性能、高效的全栈应用程序变得更加容易。 您将学习如何在 React 和 ASP.NET Core 之间进行 API 调用,确保这两种 强大技术之间的高效通信。您还将学习如何优化前端和后端,以提供响应式的用户体验,充分利用这两种技术的优势。 这

1149

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Asp.net core mvc项目发布及iis配置记录

基于NET 6.0的ASP.NET core MVC项目的发布到IIS的配置部署,小白操作版

众生本就皆苦的博客 4360

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

【asp.net core 系列】14 .net core 中的IOC

0.前言 通过前面几篇,我们了解到了如何实现项目的基本架构:数据源、路由设置、加密以及身份验证。那么在实现的时候,我们还会遇到这样的一个问题:当我们业务类和数据源越来越多的时候,我们无法通过普通的构造对象的方法为每个实例进行赋值。同时,传统意义上的赋值遇到底层切换或者其他修改的时候,就需要修改大量的代码,对改变不友好。为了改变这种现状,我们基于面向接口编程,然后使用一些DI功能和IOC框架。 1. IOC和DI 先来给大家解释几个概念,IOC全称Inversion of Control,翻译过来就是控制反

菜鸡成长日记 1933

.NET 10 的新特性_探索 C# 14、ASP.NET Core 和 .NET 10 中最有趣的新特性

本文介绍了C#和.NET生态中的多项新特性:1. C#14新增扩展成员功能,支持为类型添加属性、方法和事件;2. 空条件赋值简化空引用处理;3. 表达式树支持命名参数和可选参数;4. 复合赋值运算符允许自定义操作符。ASP.NET Core方面:1. 最小API支持数据注解验证;2. 新增服务器发送事件(SSE)支持。Blazor改进了路由配置,EF Core新增LeftJoin/RightJoin运算符和JSON类型支持。最后,.NET允许将应用程序存储在单个.cs文件中运行,无需项目文件。这些更新提升了

csdn_aspnet的专栏,请点击博客主页右上角三个点中的私信联系 5966

在docker中运行ASP.NET Core Web API应用程序

本文是一篇指导快速演练的文章,将介绍在docker中运行一个ASP.NET Core Web API应用程序的基本步骤,在介绍的过程中,也会对docker的使用进行一些简单的描述。对于.NET Core以及docker的基本概念,网上已经有很多文章对其进行介绍了,因此本文不会再详细讲解这些内容。对.NET Core和docker不了解的朋友,建议首先查阅与这些技术相关的文档,然后再阅读本...

DreamWeaver2015的博客 366

Linux 上的.NET Core Hello World

介绍 在本文中,我们将解释如何在 Linux 操作系统(Ubuntu 20.04 LTS)上安装 .NET Core 并能够运行 .NET Core 应用程序,此外还可以运行它们并能够运行例如 ASP.NET Core并且您可以从客户端看到以测试其操作。 为什么要在 Linux 上运行 Microsoft 应用程序?答案可能有很多,我们可以说是为了超越长期存在的壁垒,以及他们应该在这两个操作系统环境之间看到的“竞争”,另一个是为了吸引所有 Linux 社区,以便他们可以更多地工作舒适地,很好地..

allway2的博客 2553

2022年8月11日:使用 ASP.NET Core 为初学者构建 Web 应用程序--使用 ASP.NET Core 控制器创建 Web API(没看懂,需要再看一遍)

使用支持创建、读取、更新、删除 (CRUD) 操作的 ASP.NET Core 控制器创建 RESTful 服务。

DXB2021的博客 1977

Asp.Net Core 3.1 发布至Linux并使用Nginx托管

.Net Core最重要的特性就是跨平台(Cross-Platform),此前一直在Windows平台上打转,这次尝试将Asp.Net Core发布至Linux,特记录备查。 主要环境 本文用到的操作系统和软件版本如下: Visual Studio 2019 Professional .NET Core 3.1 CentOS 8 X64 nginx/1.14.1 创建和发布A...

ldy的专栏 4624

Jexus 托管方式运行 Asp.net core

详细可关注 这里不再赘述.net core 安装   .net core 安装  微软官网:https://www.microsoft.com/net/core#linuxredhat  首先我们介绍下:Jexus   Jexus web server for linux 是一款基于.NET兼容环境,运行于Linux/unix操作系统之上,以支持ASP.NET为核心功能的高

Coeus 1247

将ASP.NET Core2.1应用程序 部署到Windows和Docker中

开始之前,我们先做一些准备(第一次接触asp.net core的同学)。 1、安装VS2017以上VS(VS2015已经out了,还在用2015的抓紧更新吧!) 点我下载vs2017版本 点我下载vs2019版本 2、ASP.NET Core SDK下载 点我下载SDK 3、Docker下载 1)进入docker官网 2)必须登录之后才能下载,没有...

我家有个小喵咪 2964

Asp.Net Core WebApi 和Asp.Net WebApi上传文件

1. public class UpLoadController : ControllerBase 2. { 3. private readonly IHostingEnvironment _hostingEnvironment; 4.   5. public UpLoadController(IHostingEnvironment hostingEnvironment) 6. { 7. _hostingEnvi

mihaw的博客 1522

ASP.NET Core生命周期

要了解程序的运行原理,就要先知道程序的进入点及生命周期。以往ASP.NET MVC的启动方式,是继承HttpApplication作为网站开始的进入点,而ASP.NET Core 改变了网站的启动方式,变得比较像是 Console Application。 本篇将介绍ASP.NET Core 的程序生命周期 (Application Lifetime) 及捕捉 Application 停止启动事件。 程序进入点 .NET Core 把 Web 及 Console 项目都处理成一样的启动方式,默认以..

u013733643的博客 4075
上一篇: ASP.NET Core应用程序13:使用过滤器
下一篇: ASP.NET Core Blazor 1:创建准备项目
似水明俊德
博客等级 码龄8年 247粉丝 276原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值