Reputation: 1826
On my home(index) page I would like to have the login form sit directly in my home page if the user is not logged in but if the user has logged in already the index page will be displayed. If I use _LoginPartial
this page is loaded as expected, But with other pages in folders such as Account I get an error
CSHTML:
@if (SignInManager.IsSignedIn(User))
{
<p> Welcome to my website</p>
}
else
{
@await Html.PartialAsync("Account/Login")
}
`InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'CS.Pages.Manifest.IndexModel', but this ViewDataDictionary instance requires a model item of type 'CS.Pages.Account.LoginModel'.
LoginModel Constructor:
public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
Upvotes: 0
Views: 201
Reputation: 239430
When you load a partial like:
@await Html.PartialAsync("Account/Login")
The model of the view you're calling it from is passed in implicitly, just as if you had done:
@await Html.PartialAsync("Account/Login", Model)
Hence, the error your received. The partial expects a model of type LoginModel
but you're passing it a model of type IndexModel
. The solution of course is to pass in a model of the right type, i.e. LoginModel
. However, your LoginModel
has to be constructed with with parameters of types SignInManager<ApplicationUser>
and ILogger<LoginModel
. Hence why you received the second error when you attempted to pass just new LoginModel()
.
All this really boils down to this being an improper use of a partial, in the first place. A partial that requires this complex of instantiation should really be a view component instead, which would give you a ton more flexibility with setup and allows you to do injection, which is what you're going to need here.
Essentially, you just create a class that inherits from ViewComponent
and implements the method InvokeAsync
:
public class LoginViewComponent : ViewComponent
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginViewComponent(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var model = await GetModelAsync().ConfigureAwait(false);
return View(model);
}
public Task<LoginModel> GetModelAsync()
{
return Task.FromResult(new LoginModel(_signInManager, _logger));
}
}
Notice that the view component's constructor takes the parameters you need to instantiate your LoginModel
with. This allows these dependencies to be injected by the framework. The constructor, then, merely stores these in some private fields.
The InvokeAsync
method calls GetModelAsync
to get the LoginModel
instance, and then returns a view that uses this model. Since, here, there's no actual async work that needs to be done, the GetModelAsync
method just returns a Task
created from the result of newing up a LoginModel
. This has to be done to satisfy the Task
-returning nature of InvokeAsync
. If you actually needed to something asynchronous, you could simply await here instead.
With that in place, you just need to create the view Views\Shared\Components\Login\Default.cshtml
. Which would have the contents of the partial view you're currently using. The Login\Default.cshtml
portion is by convention. The directory is the name of the view component class, minus the ViewComponent
suffix, and Default.cshtml
is the default view it will look for there. This can be customized, if you like, but there's really no reason to.
Finally, where you want this appear, you simply call:
@await Component.InvokeAsync("Login");
With is approach, all the functionality (including the creation and passing in of the model) is all self-contained, so this can be called anywhere, either in individual views, or even your layout.
Upvotes: 3
Reputation: 96
Try passing LoginModel instance to the partial view
@if (SignInManager.IsSignedIn(User))
{
<p> Welcome to my website</p>
}
else
{
@await Html.PartialAsync("Account/Login", new LoginModel())
}
Upvotes: 1