ASP.NET Core 5 Razor Pages - 编辑 master/detail 设置 - 丢失数据

ASP.NET Core 5 Razor Pages - editing a master/detail setup - loosing data

我有一段时间没有做任何 web/frontend 开发,所以我有点生疏。我正在使用 ASP.NET Core 5 和 Razor Pages。

我正在尝试创建一个允许我编辑给定实体的子项 (1:n) 的编辑页面。现在,我有一个 country/city 示例 - 国家/地区列表,每个国家/地区都有几个城市。我想要做的是显示所有国家(工作正常),然后选择一个国家并编辑其城市。这就是我挣扎的地方。

这些是我的模型 classes:

public class Country
{
    public string IsoCode { get; set; }
    public string Name { get; set; }
    // more properties in the future
    
    public ICollection<City> Cities { get; set; }
}

public class City
{
    public string Name { get; set; }
    public int Population { get; set; }

    public Country Country { get; set; }
    [ForeignKey("Country")] 
    public string CountryIsoCode { get; set; }

    // more properties to come
}   

我必须显示和编辑给定国家/地区的城市的 Razor 页面如下所示:

查看:

@page "{isocode}"
@using CountryCityDemo.Models
@model CountryCityDemo.Pages.EditCitiesModel

<h3>Edit cities for a country</h3>

<div class="container">
    <form method="post">
        <div class="row mb-1">
            <div class="col-md-5 font-weight-bold">
                <label class="control-label">@Model.SelectedCountry.Name</label>
            </div>
        </div>

        @foreach (City city in Model.SelectedCountry.Cities)
        {
            <div class="row mb-1">
                <div class="col-md-3 font-weight-bold">
                    <label class="control-label">Name of the city</label>
                </div>
                <div class="col-md-5 font-weight-bold">
                    <input asp-for="@city.Name" class="form-control" />
                </div>
            </div>
            <div class="row mb-1">
                <div class="col-md-3">
                    <label class="control-label">Population</label>
                </div>
                <div class="col-md-2">
                    <input asp-for="@city.Population" class="form-control" />
                </div>
            </div>
        }

        <input type="submit" value="Update" />
    </form>
</div>

页面模型class:

public class EditCitiesModel : PageModel
{
    [BindProperty]
    public Country SelectedCountry { get; set; }

    public async Task OnGetAsync(string isocode)
    {
        // get countries from provider - including their cities
        SelectedCountry = CountryProvider.GetCountry(isocode, true);
    }

    public async Task<IActionResult> OnPostAsync(string isocode)
    {
        // fetch the list of cities for this country
        List<City> citiesForCountry = CityProvider.GetCitiesForCountry(isocode).ToList();

        // update those cities
        if (await TryUpdateModelAsync<List<City>>(citiesForCountry, "city"))
        {
            // save the updated cities
        }

        /* I also tried to use the "SelectedCountry" bind property - but had the same results:
        
        SelectedCountry = CountryProvider.GetCountry(isocode);

        if (await TryUpdateModelAsync<Country>(SelectedCountry, "SelectedCountry", c => c.Name, c => c.IsoCode, c => c.PrimaryLanguage))
        { ..... }           
        */
        
        return RedirectToPage("Data");
    }
}

页面出现得很好,一切都很顺利,我可以编辑城市的名称和人口——没问题。当我单击“更新”时,将调用 PageModel class 上的 post 方法 - 一切看起来都很棒。当我检查 HttpContext.Form 时,我可以看到我输入的城市名称和人口的值 - 所以一切看起来都很好。

BUT:调用 TryUpdateModelAsync 方法后,citiesForCountry 列表为空 - 不再有任何条目,不应用手动输入的数据:-(

当我使用 SelectedCountry 绑定 属性 时会发生同样的情况 - 从提供者那里获取后它很好,它有它的城市列表(带有存储的值),但是在我之后调用 TryUpdateModelAsync,城市列表已删除,新值未存储在任何地方....

我还尝试将 Country 类型的“视图模型”传递到我的 OnPostAsync 方法中——就像我以前用 ASP.NET MVC 做的那样:

    public async Task<IActionResult> OnPostAsync(string isocode, Country edited)
    

我确实获得了国家/地区的基本信息 - 但同样,master/child Cities 未设置,未填充我在页面上输入的值。

那么我在这里缺少什么?我在想一定有一个细节我忽略了——但我似乎无法理解任何文章、博客 posts、教程等。我已经看到了....我 似乎 正在做他们描述的事情 - 唉,在我的例子中,数据并没有完全进入我在 post 上的视图模型(或者更确切地说:Razor PageModel) ......

您需要使用索引器来启用数据到集合的绑定:https://www.learnrazorpages.com/razor-pages/model-binding#binding-complex-collections。由于您正在编辑现有数据,因此通常会根据您正在编辑的每个项目的键值使用显式索引。不确定你的模型是什么样的,所以我将使用“键”来表示值:

@foreach (City city in Model.SelectedCountry.Cities)
{
    <input type="hidden" name="SelectedCountry.Cities.Index" value="@city.Key" />
    <input type="hidden" name="SelectedCountry.Cities[@city.Key].Key" value="@city.Key" />
    <div class="row mb-1">
        <div class="col-md-3 font-weight-bold">
            <label class="control-label">Name of the city</label>
        </div>
        <div class="col-md-5 font-weight-bold">
            <input name="SelectedCountry.Cities[@city.Key].Name" class="form-control" value="@city.Name" />
        </div>
    </div>
    <div class="row mb-1">
        <div class="col-md-3">
            <label class="control-label">Population</label>
        </div>
        <div class="col-md-2">
            <input name="SelectedCountry.Cities[@city.Key].Population" class="form-control" value="@city.Population"/>
        </div>
    </div>
}

对于模型,模型绑定系统在源中查找名称模式 prefix.property_name。如果未找到任何内容,它只查找 property_name 而没有前缀。但是对于列表模型,它的名称模式必须有索引。所以你需要具体的名字,比如:[index].property_nameprefix[index].property_name.

@{int i = 0;}   //add this..
@foreach (City city in Model.SelectedCountry.Cities)
{
    <div class="row mb-1">
        <div class="col-md-3 font-weight-bold">
            <label class="control-label">Name of the city</label>
        </div>
        <div class="col-md-5 font-weight-bold">
                                     //change the name pattern
            <input asp-for="@city.Name" name="[@i].Name" class="form-control" />
        </div>
    </div>
    <div class="row mb-1">
        <div class="col-md-3">
            <label class="control-label">Population</label>
        </div>
        <div class="col-md-2">
            <input asp-for="@city.Population" name="[@i].Population" class="form-control" />
        </div>
    </div>
    i++;     //be sure add here...
}

TryUpdateModelAsync() 只能保留它传递的顶级对象和任何完全未触及的属性。除非一个集合 属性 没有被绑定,否则它将被清除,然后用新的元素填充。你需要像 what this github issue 说的那样改变:

for (int i = 0; i < citiesForCountry.Count; i++)
{
    await TryUpdateModelAsync(citiesForCountry[i], $"[{i}]");
}
//...