ASP.NET MVC2 Login form in master page
I'm trying to find an elegant way of having login form in the master page. That means unless user logs in, login form should appear on every page. (This is very common these days)
Taking the example that comes with MVC2 Application in Visual Studio I have created this:
public class MasterViewModel
{
public string User { get; set; } // omitted validation attributes
public string Pass{ get; set; }
public bool RememberMe { get; set; }
}
Every view model inherits from MasterViewModel
public class RegisterViewModel : MasterViewModel
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
My master page renders partial view
<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage<MyLogon.ViewModels.MasterViewModel>" %>
.....
<div id="logindisplay">
<% Html.RenderPartial("LogOn"); %>
</div>
......
Strongly-typed partial view:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyLogon.ViewModels.MasterViewModel>" %>
<%
if (Request.IsAuthenticated) {
%>
Welcome <b><%= Html.Encode(Page.User.Identity.Name) %></b>!
<div>[ <%= Html.ActionLink("Change Password", "ChangePassword", "Account") %> ]</div>
<div>[ <%= Html.ActionLink("Log Off", "LogOff", "Account") %> ]</div>
<%
}
else {
%>
<%= Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.") %>
<% using (Html.BeginForm("LogOn", "Account",FormMethod.Post)) { %>
<div>
<fieldset>
<legend>Account Information</legend>
<div class="editor-label">
<%= Html.LabelFor(m => m.User) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(m => m.User) %>
<%= Html.ValidationMessageFor(m => m.User) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(m => m.Pass) %>
</div>
<div class="editor-field">
<%= Html.PasswordFor(m => m.Pass) %>
<%= Html.ValidationMessageFor(m => m.Pass) %>
</div>
<div class="editor-label">
<%= Html.CheckBoxFor(m => m.RememberMe) %>
<%= Html.LabelFor(m => m.RememberMe) %>
</div>
<p>
<input type开发者_Python百科="submit" value="Log On" />
</p>
</fieldset>
</div>
<% } %>
<%
}
%>
Register page:
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyLogon.Models.RegisterViewModel>" %>
...
<h2>Create a New Account</h2>
<p>
Use the form below to create a new account.
</p>
<p>
Passwords are required to be a minimum of <%= Html.Encode(ViewData["PasswordLength"]) %> characters in length.
</p>
<% using (Html.BeginForm("Register", "Account" ,FormMethod.Post))
{ %>
<%= Html.ValidationSummary(true, "Account creation was unsuccessful. Please correct the errors and try again.") %>
<div>
<fieldset>
<legend>Account Information</legend>
<div class="editor-label">
<%= Html.LabelFor(m => m.UserName) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(m => m.UserName) %>
<%= Html.ValidationMessageFor(m => m.UserName) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(m => m.Email) %>
</div>
<div class="editor-field">
<%= Html.TextBoxFor(m => m.Email) %>
<%= Html.ValidationMessageFor(m => m.Email) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(m => m.Password) %>
</div>
<div class="editor-field">
<%= Html.PasswordFor(m => m.Password) %>
<%= Html.ValidationMessageFor(m => m.Password) %>
</div>
<div class="editor-label">
<%= Html.LabelFor(m => m.ConfirmPassword) %>
</div>
<div class="editor-field">
<%= Html.PasswordFor(m => m.ConfirmPassword) %>
<%= Html.ValidationMessageFor(m => m.ConfirmPassword) %>
</div>
<p>
<input type="submit" value="Register" />
</p>
</fieldset>
</div>
<% } %>
</asp:Content>
Since all view models inherit from MasterViewModel LogOn partial view is always satisfied but I'm finding this solution very inelegant. Is there any other way to achieve this?
Use a partial view as you are but have different views based on the logged in status. Return one view for anonymous users with a login form/lunk to the register page and a second view for logged in users.
Partial views can have their own models which do not need to inherit from the master view model.
Inheriting models can cause problems later (It makes using html.EditForModel()
or .DisplayForModel()
tricky as they will render the common master view fields too).
Oh and in general, it's bad practice for the view to rely on anything outside the model - your test on Request.IsAuthenticated
may be fine for now but if you want to be absolutely correct, you should have a model.IsAuthenticated
property which is set by the action. That said, switching views will obviate this problem entirely.
Edit: One last improvement. On lines like this one:
Welcome <b><%= Html.Encode(Page.User.Identity.Name) %></b>!
Instead do:
Welcome <b><%: Page.User.Identity.Name %></b>!
Actually even better would be to only hit the model:
Welcome <b><%: model.Username %></b>!
(Note the <%:
not <%=
). This form of tag indicates that the contents should be HTML-encoded. What's even better is that if it encounters a string
, it will encode it. If it encounters an HTMLString
, it won't. All the .Net MVC functions return string or HTML string as appropriate so you can do this:
<%: html.EditFor(x => x.fieldname) %>
<%: model.somefield %>
The first will NOT be html encoded as EditFor()
returns an HTMLString
. The second WILL be encoded (if somefield
is a standard string)
This will make your code both neater and more robust. You can also use this to dynamically generate HTML should you have to and have it encoded/not as appropriate. Eg we have some helper functions to handle including CSS/JS. They return HTMLStrings...
<%: Helper.IncludeCSS("SomeCSS.css") %>
精彩评论