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
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With