ViewModel Controller Action Method Syntax

Dean Everhart 1,541 Reputation points
2023-05-03T17:06:11.0766667+00:00

How do I have to change the controller code (below) to allow viewmodel view to display? I know the action method needs to point to the AB model, but I don't know how to change the syntax to do this.

Models appear in the context file. My understanding is the viewmodels do not appear in the context. Am I correct?


Environment: Net Core 6 MVC, Visual Studio Community 2022 (64 bit), WIndows 11

GitHub: View Model Project

Please: whenever possible, provide help in the form of actual code - preferably changes to the code I've provided (a fair amount of time and effort goes into providing this code as the premise for the question) - or in example(s) of code you provide, in either case in the format / syntax the code needs to be in. Abstraction obfuscates the answer.


Models

A

namespace ViewModel.Models
{
    public class A
    {
        public int Id { get; set; }

        public string? One { get; set; }

        public string? Two { get; set; }

        public string? Three { get; set; }

        public IEnumerable<C>? C { get; set; }                 <- IEnumerable

    }
}

B

namespace ViewModel.Models
{
    public class B
    {
        public int Id { get; set; }

        public string? One { get; set; }

        public string? Two { get; set; }

        public string? Three { get; set; }

        public IEnumerable<C>? C { get; set; }             <- IEnumerable
    }
}

C

using System.Security.Cryptography.Xml;

namespace ViewModel.Models
{
    public class C
    {
        public int Id { get; set; }

        public int AId { get; set; }

        public int BId { get; set; }

        // _______________________________________

        public A? A { get; set; }
        public B? B { get; set; }
    }
}

ViewModel (Project.Models.View.AB)

using System.Security.Cryptography.Xml;

namespace ViewModel.Models.View
{
    public class AB
    {
        public A? A { get; set; }

        public B? B { get; set; }
    }
}

View

@model IEnumerable<ViewModel.Models.View.AB>               <- IEnumerable

@{
    ViewData["AB"] = "AB";
}

<h1>AB</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.A.One)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.B.Two)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.A.One)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.B.Two)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.A.Id">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.A.Id">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.A.Id">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Controller

        // GET: AB
        public async Task<IActionResult> AB()
        {
            return _context.A != null ?
                        View(await _context.A.ToListAsync()) :
                        Problem("Entity set 'ViewModelContext.A'  is null.");
        }

Project builds successfully

**
Error when Run**
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List1[ViewModel.Models.A]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IEnumerable1[ViewModel.Models.View.AB]'.

Re: IEnumerable - the two models navigation properties are IEnumerable

        public IEnumerable<C>? C { get; set; }

and the view is IEnumberable

@model IEnumerable<ViewModel.Models.View.AB>

Question
How do I have to change the controller code syntax (above) to allow viewmodel view to display?


Attempted Change 1

The controller action method below needs to point to the AB viewmodel. Changing A to AB in the current context not successful.

**
Changing _context.A to _context.AB**

        // GET: AB
        public async Task<IActionResult> AB()
        {
            return _context.AB != null ?
                        View(await _context.AB.ToListAsync()) :
                        Problem("Entity set 'ViewModelContext.A'  is null.");
        }

Error

CS1061 'ViewModelContext' does not contain a definition for 'AB' and no accessible extension method 'AB' accepting a first argument of type 'ViewModelContext' could be found (are you missing a using directive or an assembly reference?) ViewModel C:\Users\User1\Desktop_Tech\Net\ViewModel\ViewModel\ViewModel\Controllers\AController.cs 33 Active

Question:
Models appear in the context file. My understanding is the viewmodels do not appear in the context. Am I wrong?

Developer technologies ASP.NET ASP.NET Core
Developer technologies ASP.NET Other
0 comments No comments
{count} votes

Accepted answer
  1. Anonymous
    2023-05-04T06:36:36.18+00:00

    Hi @Dean Everhart

    Models appear in the context file. My understanding is the viewmodels do not appear in the context. Am I wrong?

    Yes, you are right, the view model is used to transfer data between view and controller.

    From your code, the A and B was configured many-to-many relationship, to display the related entities, you can query the C table (join table), and then convert the result to the ViewModels.

    For example, the data table contains the following data:

    User's image

    The controller:

                var ab = _dbContext.C.Select(c => new AB()
                {
                    A = c.A,
                    B = c.B,
                }).ToList();
                return View(ab);
    

    The result as below:

    User's image

    Besides, you can also query the A and B table, then filter the result and create the AB instance, like this:

                //define a variable to store the return data.
                var ab2 = new List<AB>();
                //query A and B table, 
                var Alist = _dbContext.A.ToList();
                var Blist = _dbContext.B.ToList();
                //use foreach statement to filter data. And Add A and B to the ab list.
                foreach (var item in Alist)
                {
                    var newab = new AB()
                    {
                        A = item
                    };
                    //add the new item into the list.
                    ab2.Add(newab);
                }
                return View(ab2); // return the list to the view page.
    

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Dillion

    1 person found this answer helpful.
    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. AgaveJoe 30,126 Reputation points
    2023-05-03T18:39:22.73+00:00

    Models appear in the context file. My understanding is the viewmodels do not appear in the context. Am I wrong?

    The problem has nothing to do with the ViewModel or DbContext.  The problem is the code tries to assign a collection ViewModel.Models.A types to a collection of ViewModel.Models.View.AB types which is not allowed in C# because ViewModel.Models.A and ViewModel.Models.View.AB are totally different types. 

    Below is a Console application that illustrates the problem.

    // See https://aka.ms/new-console-template for more information
    //Console.WriteLine(DateTime.Now.Ticks.ToString());
    
    
    Console.WriteLine(typeof(Class_A) == typeof(Class_A));
    Console.WriteLine(typeof(Class_AB) == typeof(Class_A));
    
    
    public class Class_A { }
    public class Class_AB
    {
        public Class_A? A { get; set; }
    }
    

    The results are...

    True
    False
    

    As you can see Class_A and Class_AB are not the same types. Therefore, it is not possible to equate one to the other which is exactly what you are trying to do.

    Capture

    Casting and type conversions (C# Programming Guide)

    I think you are trying to display a many-to-many relationship in HTML which is a totally different question and design. The common method to represent a many-to-many in the UI (View) is to break the relationship into two one-to-many relationships. Using your class names, the View would display one A record and many B records. Or one B and many A records. Typically, the "id" route parameter is used to select "one" record.

    I think part of the problem is you are trying to display AB records which is a list of records in the AB table. If you want to edit the A records the you have to select the A records which might have many B records along with the A record fields.

    I recommend that you go through the Getting Started with Data tutorials on this site which has example programming patterns that solve this common UI programming problem.

    ASP.NET Core MVC with EF Core - tutorial series

    1 person found this answer helpful.

  2. Bruce (SqlWork.com) 77,686 Reputation points Volunteer Moderator
    2023-05-03T18:18:31.36+00:00

    again you seem to have little understanding of classes and types. a dbcontext has classes that represent the database table rows.

    you created a view mode that hold one "A" row and one "B". you view is expecting a collections of these. not sure how an "A" relates to a "B" row. but you need to map the rows to a view model. in linq mapping is done via the Select.

    return View(await _context.A.Select(r => new ViewModel.Models.View.AB
    {
       A = r
    }.ToListAsync());
    
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.