Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to use a ViewModel on a partial view

Tags:

So I'm editing the default ASP.NET MVC with authentication template, and I'm trying to create a ViewModel for _PartialLogin.cshtml in order to display a name instead of the user's e-mail. I searched a lot of things, but they were pretty outdated so I hope someone can help. I don't even know if creating a ViewModel is the best option.

Here's the LoginPartialViewModel:

namespace AplicacaoRoles2.Models.SharedViewModels
{
    public class PartialLoginViewModel
    {
        [Required]
        public string Nome { get; set; }
    }
}

_LoginPartial.cshtml:

@using Microsoft.AspNetCore.Identity
@using AplicacaoRoles2.Models

@model AplicacaoRoles2.Models.SharedViewModels.PartialLoginViewModel

@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager

@if (SignInManager.IsSignedIn(User))
{
    <form asp-area="" asp-controller="Account" asp-action="Logout" method="post" id="logoutForm" class="navbar-right">
        <ul class="nav navbar-nav navbar-right">
            <li>
                <a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @Html.DisplayFor(model => model.Nome) !</a>
            </li>

(...) HomeController:

public class HomeController : Controller
{
    private readonly ApplicationDbContext _context;

    public HomeController(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<IActionResult> Index()
    {
        PartialLoginViewModel model = new PartialLoginViewModel();
        var userName = User.Identity.Name;
        var applicationUser = await _context.ApplicationUser.SingleOrDefaultAsync(m => m.UserName == userName);

        model.Nome = applicationUser.Nome;

        return View(model);
    }

On _Layout.cshtml there's this portion useful:

@Html.Partial("_LoginPartial")

(the code is simplified to shorted this)

So this is working fine while I'm on Home/index because it as no Model, but as soon as I enter a page with a model it shows an model error thing.

I could replace:

@Html.Partial("_LoginPartial")

for

@Html.Partial("_LoginPartial", new AplicacaoRoles2.Models.SharedViewModels.PartialLoginViewModel())

But the problem is that I lose the information by creating a new instance... Thank you.

**Some words like 'Nome' are not misspelled, they are in portuguese

like image 277
Leonardo Henriques Avatar asked Feb 26 '18 18:02

Leonardo Henriques


People also ask

How do I pass a model to a partial view?

To create a partial view, right click on Shared folder -> select Add -> click on View.. Note: If the partial view will be shared with multiple views, then create it in the Shared folder; otherwise you can create the partial view in the same folder where it is going to be used.

Can we use model in partial view?

Partial Views can use the Page Model for their data whereas Child Actions use independent data from the Controller. Editor/Display templates pass items from the model to the system but can be overridden by user partial views.


1 Answers

Let's first see what the issue is that your are running into and then we will apply a fix.

The Issue/Problem

The _LoginPartial.cshtml is called from the Shared/_Layout.cshtml unless you have changed it; it seems you have not changed it so it is using the defaults. That is fine, you do not have to change it. When you visit Home/Index, you create an instance of PartialLoginViewModel and pass it to the Home/Index.cshtml. By default, all .cshtml files use Shared/_Layout.cshtml so essentially this is how your PartialLoginViewModel is being passed around:

Home/Index (Controller) => Home/Index.cshtml => Shared/_Layout.cshtml => _LoginPartial.cshtml

So in the above, it is pretty clear that _LoginPartial.cshtml eventually gets a hold of PartialLoginViewModel and that is why it works when you visit Home/Index. All good and dandy :)

Now let's see what happens when you visit another page; say SomeController.SomeAction. Let's further imagine the other page has a model of type SomeModel. Here is how the SomeModel will be passed around:

Some/SomeAction (Controller) => Some/SomeAction.cshtml => Shared/_Layout.cshtml => _LoginPartial.cshtml

And boom it fails because _LoginPartial.cshtml is expecting a model which has the properties of PartialLoginViewModel but instead you have given it SomeModel.

Not a Fix

This will not work:

@Html.Partial("_LoginPartial", new AplicacaoRoles2.Models.SharedViewModels.PartialLoginViewModel())

because you are sending the correct model (PartialLoginViewModel) to the _LoginPartial but it is a new instance and the properties have not been set. Therefore, the Name (you spelled it Nome) will be empty.

This will also not work:

@Html.Partial("_LoginPartial", Model)

because you are sending it the model from your main view which is of a different type.

Fix

One way to fix your issue is to inherit all your models for your views (view models) from PartialLoginViewModel so the partial stays happy.

public class PartialLoginViewModel
{
    [Required]
    public string Nome { get; set; }
}

public class SomeModel : PartialLoginViewModel
{
    // Code ...
}

You would also need to populate the Name property in all your controller actions. So simply create a base class and inherit all your controllers from that. Like this:

public class CustomController : Controller
{
    protected virtual async Task<TModel> CreateModel<TModel>() where TModel : PartialLoginViewModel, new()
    {
        TModel model = new TModel();
        var userName = User.Identity.Name;
        var applicationUser = await _context.ApplicationUser.SingleOrDefaultAsync(m => m.UserName == userName);

        // Set common properties
        model.Nome = applicationUser.Nome;

        return model;
    }
}

Then inherit all your controllers from the above controller and always call the above controller's CreateModel method so it can populate the common properties as shown below:

public class EveryoneOfYourControllers : CustomController
{
    public async Task<IActionResult> Index()
    {
        var model = await base.CreateModel<SomeModel>();

        // Set other properties on your model

        return View();
    }
}

All the code is in one place. In the future if you decide to add another property to PartialLoginViewModel, you will only need the change the code in in one place to set the additional property.

Now that you know the issue, you can come up with different solutions.

like image 180
CodingYoshi Avatar answered Sep 19 '22 05:09

CodingYoshi