Ошибка передачи ViewModel для просмотра с использованием EF Core MVC - PullRequest
0 голосов
/ 15 января 2020

У меня ViewModel, передаваемое в Views, и я получаю странную ошибку:

InvalidOperationException: элемент модели, переданный в ViewDataDictionary, имеет тип 'System.Collections.Generi c .List 1[BizDevHub.Models.Intermediary]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IEnumerable 1 [BizDevHub.ViewModels.IntermediaryViewModel] '.

Я не могу понять, почему, поскольку мне кажется, что я передаю ViewModel представлению, а не модели.

Мои модели

public class Intermediary
{
    public int IntermediaryID
    public string RegisteredName { get; set; }
    public string TradingName { get; set; }
    public DateTime CreationDate { get; set; }
    public string CreatedBy { get; set; }

    public ICollection<Branch> Branches { get; set; }
}

public class Branch
{
    public int BranchID { get; set; }
    public string Name { get; set; }

    [DisplayName("Creation Date")]
    public DateTime CreationDate { get; set; }

    [StringLength(100)]
    [DisplayName("Created By")]
    public string CreatedBy { get; set; }
}

Моя ViewModel

public class IntermediaryViewModel
{
    public int IntermediaryID { get; set; }
    [Required,StringLength(150),DisplayName("Registered Name")]
    public string RegisteredName { get; set; }

    [Required, StringLength(150), DisplayName("Registered Name")]
    public string TradingName { get; set; }
    public int Registration { get; set; }
    public int VATNumber { get; set; }

    [Required]
    public int FSPNumber { get; set; }

    [DisplayName("Creation Date")]
    public DateTime CreationDate { get; set; }

    [StringLength(100)]
    [DisplayName("Created By")]
    public string CreatedBy { get; set; }

    public int BranchID { get; set; }
    public ICollection<Branch> Branches { get; set; }
}

Мои просмотры

@model IEnumerable<BizDevHub.ViewModels.IntermediaryViewModel>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.RegisteredName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.TradingName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Registration)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.VATNumber)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.FSPNumber)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.CreationDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.CreatedBy)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.BranchID)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.RegisteredName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.TradingName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Registration)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.VATNumber)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FSPNumber)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.CreationDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.CreatedBy)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.BranchID)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.IntermediaryID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.IntermediaryID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.IntermediaryID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>


@model BizDevHub.ViewModels.IntermediaryViewModel

@{
    ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Intermediary</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="RegisteredName" class="control-label"></label>
                <input asp-for="RegisteredName" class="form-control" />
                <span asp-validation-for="RegisteredName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="TradingName" class="control-label"></label>
                <input asp-for="TradingName" class="form-control" />
                <span asp-validation-for="TradingName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Registration" class="control-label"></label>
                <input asp-for="Registration" class="form-control" />
                <span asp-validation-for="Registration" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="VATNumber" class="control-label"></label>
                <input asp-for="VATNumber" class="form-control" />
                <span asp-validation-for="VATNumber" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="FSPNumber" class="control-label"></label>
                <input asp-for="FSPNumber" class="form-control" />
                <span asp-validation-for="FSPNumber" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="CreatedBy" class="control-label"></label>
                <input asp-for="CreatedBy" class="form-control" />
                <span asp-validation-for="CreatedBy" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="BranchID" class="control-label"></label>
                <input asp-for="BranchID" class="form-control" />
                <span asp-validation-for="BranchID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label class="control-label col-md-2" for="DepartmentID">Branch</label>
                <div class="col-md-10">
                    @Html.DropDownList("DepartmentID", null, new {@class="form-group"})
                    @Html.ValidationMessageFor(model => model.BranchID)
                </div>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Мой контроллер

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using BizDevHub.Data;
using BizDevHub.Models;
using BizDevHub.ViewModels;

namespace BizDevHub.Controllers
{
    public class IntermediariesController : Controller
    {
        private readonly BizDevHubContext _context;

        public IntermediariesController(BizDevHubContext context)
        {
            _context = context;
        }

        // GET: Intermediaries
        public async Task<IActionResult> Index()
        {
            return View(await _context.Intermediaries.ToListAsync());
        }

        // GET: Intermediaries/Details/5
        public async Task<IActionResult> Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var intermediary = await _context.Intermediaries
                .FirstOrDefaultAsync(m => m.IntermediaryID == id);
            if (intermediary == null)
            {
                return NotFound();
            }

            return View(intermediary);
        }

        // GET: Intermediaries/Create
        public IActionResult Create()
        {
            PopulateBranchDropDownList();
            return View();
        }

        // POST: Intermediaries/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost, ActionName("Edit")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("IntermediaryID,RegisteredName,TradingName,Registration,VATNumber,FSPNumber,CreationDate,CreatedBy,BranchID")] IntermediaryViewModel intermediary)
        {
            if (ModelState.IsValid)
            {
                _context.Add(intermediary);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            PopulateBranchDropDownList(intermediary.BranchID);
            return View(intermediary);
        }

        // GET: Intermediaries/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var intermediary = await _context.Intermediaries.FindAsync(id);
            if (intermediary == null)
            {
                return NotFound();
            }
            return View(intermediary);
        }

        // POST: Intermediaries/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost, ActionName("Edit")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int id, [Bind("IntermediaryID,RegisteredName,TradingName,Registration,VATNumber,FSPNumber,CreationDate,CreatedBy,BranchID")] IntermediaryViewModel intermediary)
        {
            if (id != intermediary.IntermediaryID)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                try
                {
                    _context.Update(intermediary);
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!IntermediaryExists(intermediary.IntermediaryID))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction(nameof(Index));
            }
            PopulateBranchDropDownList(intermediary.BranchID);
            return View(intermediary);
        }

        // GET: Intermediaries/Delete/5
        public async Task<IActionResult> Delete(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var intermediary = await _context.Intermediaries
                .FirstOrDefaultAsync(m => m.IntermediaryID == id);
            if (intermediary == null)
            {
                return NotFound();
            }

            return View(intermediary);
        }

        // POST: Intermediaries/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var intermediary = await _context.Intermediaries.FindAsync(id);
            _context.Intermediaries.Remove(intermediary);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }

        private bool IntermediaryExists(int id)
        {
            return _context.Intermediaries.Any(e => e.IntermediaryID == id);
        }

        private void PopulateBranchDropDownList(object selectedBranch = null)
        {
            var branchesQuery = from d in _context.Branch
                                orderby d.Name
                                select d;
            ViewBag.DepartmentID = new SelectList(branchesQuery, "DepartmentID", "Name", selectedBranch);
        }
    }
}

Любая помощь будет оценена!

Ответы [ 2 ]

2 голосов
/ 15 января 2020

Для:

return View(await _context.Intermediaries.ToListAsync());

и

return View(intermediary);

Похоже, вы передаете объекты View типа Intermediary, а не IntermediaryViewModel, если ваш контекст каким-то образом не преобразует их в другой код. Я не вижу кода, который отображает сущность (Intermediary) в ViewModel (IntermediaryViewModel). Возможно, используйте AutoMapper или что-то подобное для преобразования ваших EF-сущностей в ViewModel.

1 голос
/ 15 января 2020

Я вижу пару проблем с вашим кодом.

  1. Ваши представления принимают ViewModel / s в качестве входных данных. Но вы возвращаете объект Model в действии Index. Итак, чтобы исправить ошибку, отображаемую в заголовке этого POST , обновите действие index, чтобы преобразовать коллекцию объектов модели в коллекцию объектов viewmodel.
public async Task<IActionResult> Index()
{
    var intermediaryViewModels = ModelsToViewModelsConversion(_context.Intermediaries.ToListAsync());
    return View(intermediaryViewModels);
}

private async List<IntermediaryViewModel>() ModelsToViewModelsConversion(IEnumerable<Intermediary> models)
{
    // conversion code goes here.
    // return collection of IntermediaryViewModel;
}
Действие Create GET (// GET: Intermediaries / Create) не возвращает никакого объекта, но представление ожидает экземпляр IntermediaryViewModel. Итак, вам нужно изменить действие Create GET, как показано ниже:
// GET: Intermediaries/Create
public IActionResult Create()
{
  // PopulateBranchDropDownList();
  var branchesQuery = from d in _context.Branch
      orderby d.Name
      select d;
  return View(new IntermediaryViewModel{Branches = branchesQuery.AsEnumerable() });
}

И обновить файл Create.cs html, как показано ниже, для отображения ветвей:

<div class="form-group">
    <label class="control-label col-md-2" for="DepartmentID">Branch</label>
    <div class="col-md-10">
        @Html.DropDownList("DepartmentID", new SelectList(Model.Branches, "DepartmentID", "Name"), new { @class = "form-group" })
        @Html.ValidationMessageFor(model => model.BranchID)
    </div>
</div>

Я заметил, что вы перепутали модели и модели представления (например, IntermediaryViewModel имеет модель Branch вместо BranchViewModel), и в действии Create POST вы добавляете модель представления в контекстные модели (сущности).

Весь ваш код хорош, если вы позаботились о процессе преобразования модель-представление-модель через некоторый оператор или что-то в этом роде. Но если это так, то вы не получите ошибку, которую вы видите сейчас, из-за которой я думаю, что в вашем коде нет такого преобразования.

Обновлен ваш код, чтобы включить преобразование от модели к ViewModel.

public class Intermediary
{
    public int IntermediaryID  { get; set; }
    public string RegisteredName { get; set; }
    public string TradingName { get; set; }
    public DateTime CreationDate { get; set; }
    public string CreatedBy { get; set; }

    public ICollection<Branch> Branches { get; set; }
}

public class Branch
{
    public int BranchID { get; set; }
    public string Name { get; set; }

    // Generally Model classes should not have any idea on Display related stuff.
    // [DisplayName("Creation Date")]
    public DateTime CreationDate { get; set; }

    [StringLength(100)]
    // [DisplayName("Created By")]
    public string CreatedBy { get; set; }
}

public class IntermediaryViewModel
{
    // I just created common expression so it can be used from Index as well as Details actions.
    // Feel free to remove this common expression and have the code inside controller actions if you prefer that way. 
    /// <summary>
    /// Lambda expression converting Intermediary to IntermediaryViewModel
    /// </summary>
    public static readonly Expression<Func<Intermediary, IntermediaryViewModel>> AsIntermediaryViewModel =
    i => new IntermediaryViewModel{
        // Please add other required properties mapping here. I just showed couple 
        IntermediaryID = i.IntermediaryID,
        RegisteredName = i.RegisteredName,
        BranchID       = i.BranchID,
        // if you want you can populate Branches (by Intermediary) here like this in a single database call as we have mentioned Include in actions
        Branches = i.Branches.AsQueryable().Select(b => new BranchViewModel{ BranchID = b.BranchID}).ToList()
    };

    public int? IntermediaryID { get; set; }

    [Required,StringLength(150),DisplayName("Registered Name")]
    public string RegisteredName { get; set; }

    [Required, StringLength(150), DisplayName("Registered Name")]
    public string TradingName { get; set; }
    public int Registration { get; set; }
    public int VATNumber { get; set; }

    [Required]
    public int FSPNumber { get; set; }

    // As now you have separate ViewModel for display purposes, 
    // you can have string version of CreationDate which is (DateTime to string) converted as per your requirement.
    [DisplayName("Creation Date")]
    public DateTime CreationDate { get; set; }

    [StringLength(100)]
    [DisplayName("Created By")]
    public string CreatedBy { get; set; }
    // Since you are defining BranchId as non-nullable, you will need to default to some existing BranchId so, your (create) view will show this branch when page is loaded. Otherwise make it nullable int (int ?) so, dropdown would display your message "Select Branch" when BranchId is null
    public int BranchID { get; set; }
    public ICollection<BranchViewModel> Branches { get; set; }
}

// since you are using Branches to populate drop down, other audit properties are not required here.
// Does not hurt having them if you want.
public class BranchViewModel
{
    public int BranchID { get; set; }
    public string Name { get; set; }    
}




// GET: Intermediaries
public async Task<IActionResult> Index()
{
    return View(await _context.Intermediaries.Include(i => i.Branches).Select(IntermediaryViewModel.AsIntermediaryViewModel).ToListAsync());
}

// GET: Intermediaries/Details/5
public async Task<IActionResult> Details(int? id)
{
    if (id == null)
    {
        return NotFound();
    }
    // I just renamed the variable to speak what it represents. 
    var intermediaryViewModel = await _context.Intermediaries.Include(i => i.Branches).Select(IntermediaryViewModel.AsIntermediaryViewModel)
        .FirstOrDefaultAsync(m => m.IntermediaryID == id);
    if (intermediaryViewModel == null)
    {
        return NotFound();
    }

    return View(intermediaryViewModel);
}

private void PopulateBranchDropDownList(object selectedBranch = null)
{
    ViewBag.BranchList = from d in _context.Branch
                        orderby d.Name
                        select new BranchViewModel
                        {
                            BranchID = d.BranchID,
                            Name = d.Name
                        }.ToList();

    ViewBag.BranchID = selectedBranch;  //Set to some predefined BranchID selection, so when page is loaded, dropdown will be defaulted to this value.
}

Пожалуйста, используйте то же самое для других действий контроллера. Не нужно ничего менять в представлениях, так как они уже используют ViewModels, кроме выпадающего списка Branch.

Обратите внимание, я заменил DepartmentID на BranchID

@{
    var Branchist = new SelectList(ViewBag.LocList, "Id", "Text");
    int? BranchID = ViewBag.BranchID ?? (int ?)null; // please test this line.
}

<div class="form-group">
    <label class="control-label col-md-2" for="BranchID">Branch</label>
    <div class="col-md-10">
        @Html.DropDownList(@BranchID, @Branchist , "Select Branch", new {@class="form-group"})
        @Html.ValidationMessageFor(model => model.BranchID)
    </div>
</div>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...