Home > Back-end >  Selected item transfer issue, There is no ViewData item of type
Selected item transfer issue, There is no ViewData item of type

Time:06-14

I am building ASP.NET MVC project, All other posts about this topic did not help me. i have 2 models, Client and City.

public class Client
{
    [Key]
    public int Id { get; set; }
    public string Surname { get; set; }
    public string Name { get; set; }     
    public City City { get; set; }       
}

 public class City
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

And when i want to create a client a have an exception There is no ViewData item of type 'IEnumerable' that has the key 'City'.

This is my get and post method

 private readonly ApplicationDbContext _context;

    private List<City> _cities;
    public ClientsController(ApplicationDbContext context)
    {
        _context = context;

    }

// GET: Clients/Create
    public IActionResult Create()
    {
        if (_context.City != null) _cities = _context.City.ToList();
        ViewBag.Cities = new SelectList(_cities, "Id", "Name");


        return View();
    }

    // POST: Clients/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("Id,Surname,Name,Patronymic,Telephone,City,Adress,SeriaNumberPassport,IdentificalCode")]
        Client client)
    {
        if (ModelState.IsValid)
        {
            _context.Add(client);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        return View(client);
    }

And View code:

<div  mb-3>
            <label asp-for="City" ></label>
            @Html.DropDownListFor(model => model.City, ViewBag.Cities as SelectList, new { @class = "form-select" })
        </div>

The data is displayed correctly, but I cannot create a client.

CodePudding user response:

Think that the ModelState.IsValid is false, hence it returns to Create View rather than redirect to Index view (the flow for successful inserting Client).

While for the failure inserting case, you didn't provide the ViewBag.Cities value before returning to Create View (Check the Create method with [HttpPost]).

Talk about why the ModelState.IsValid was false, there is conflict in the type that you are passing CityId which is int type to City property with City type.

Updated:

Recommend creating and using the ViewModel class instead of Model. The main reason is to we can design the class in which the properties are only required for view, create/edit purposes.

Exposing the Model class is unsafe as the users will know how is your database entity looks like.

But the trouble with using ViewModel will be you need to map the property value from ViewModel to Model manually or with reflection. Of course, there are open-source libraries that can automate the mapping such as AutoMapper.


These are the steps you need to do for the fix:

Model

  1. Add for CityId foreign key property.
public class Client
{
    [Key]
    public int Id { get; set; }
    public string Surname { get; set; }
    public string Name { get; set; } 
    public int CityId { get; set; }  // Add this foreign key property    
    public City City { get; set; }       
}

Note: If you use Entity Framework Code First approach, you need to create migration and update the database via command for this change.

ViewModel

Create ViewModel class with the properties require for Create Client.

public class CreateClientModel
{
    public string Surname { get; set; }
    public string Name { get; set; }  
    public int CityId { get; set; }

    // Other properties that needed
}

View

2.1. Change the @model to CreateClientModel. 2.2. Use model.CityId instead of model.City. The CityId property is used to pass the selected city's Id.

@model CreateClientModel

<div  mb-3>
    <label asp-for="CityId" ></label>
    @Html.DropDownListFor(model => model.CityId, ViewBag.Cities as SelectList, new { @class = "form-select" })
</div>

Controller

3.1. Replace City with CityId in Bind attribute. (To add properties based on CreateClientModel).

3.2. Use CreateClientModel as request body.

3.3. Initialize the ViewBag.Cities value before returning to Create view for the ModelState.IsValid is false case.

public IActionResult Create()
{
    if (_context.City != null) _cities = _context.City.ToList();
    ViewBag.Cities = new SelectList(_cities, "Id", "Name");
      
    return View(new CreateClientModel());
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Surname,Name,CityId")]
    ClientClientModel client)
{
    if (ModelState.IsValid)
    {
        // Perform mapping from CreateClientModel to 
        _context.Add(new Client
        {
            Surname = client.Surname,
            Name = client.Name,
            CityId = client.CityId
            // Other properties
        });
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }

    // Initialize ViewBag.Cities value
    if (_context.City != null) _cities = _context.City.ToList();
    ViewBag.Cities = new SelectList(_cities, "Id", "Name");

    return View(client);
}

Suggested have a read and follow this tutorial which is similar to your scenario:

Tutorial: Update related data - ASP.NET MVC with EF Core

  • Related