I create what I call a "ViewModel" for each view. I put them in a folder called ViewModels in my MVC Web project. I name them after the controller and action (or view) they represent. So if I need to pass data to the SignUp view on the Membership controller I create a MembershipSignUpViewModel.cs class and put it in the ViewModels folder.
Then I add the necessary properties and methods to facilitate the transfer of data from the controller to the view. I use the Automapper to get from my ViewModel to the Domain Model and back again if necessary.
This also works well for composite ViewModels that contain properties that are of the type of other ViewModels. For instance if you have 5 widgets on the index page in the membership controller, and you created a ViewModel for each partial view - how do you pass the data from the Index action to the partials? You add a property to the MembershipIndexViewModel of type MyPartialViewModel and when rendering the partial you would pass in Model.MyPartialViewModel.
Doing it this way allows you to adjust the partial ViewModel properties without having to change the Index view at all. It still just passes in Model.MyPartialViewModel so there is less of a chance that you will have to go through the whole chain of partials to fix something when all you're doing is adding a property to the partial ViewModel.
I will also add the namespace "MyProject.Web.ViewModels" to the web.config so as to allow me to reference them in any view without ever adding an explicit import statement on each view. Just makes it a little cleaner.
Here's how I do it.
I decided to use IPrincipal instead of IIdentity because it means I don't have to implement both IIdentity and IPrincipal.
Create the interface
interface ICustomPrincipal : IPrincipal
{
int Id { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
}
CustomPrincipal
public class CustomPrincipal : ICustomPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public CustomPrincipal(string email)
{
this.Identity = new GenericIdentity(email);
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
CustomPrincipalSerializeModel - for serializing custom information into userdata field in FormsAuthenticationTicket object.
public class CustomPrincipalSerializeModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
LogIn method - setting up a cookie with custom information
if (Membership.ValidateUser(viewModel.Email, viewModel.Password))
{
var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First();
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.Id = user.Id;
serializeModel.FirstName = user.FirstName;
serializeModel.LastName = user.LastName;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
viewModel.Email,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
return RedirectToAction("Index", "Home");
}
Global.asax.cs - Reading cookie and replacing HttpContext.User object, this is done by overriding PostAuthenticateRequest
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.Id = serializeModel.Id;
newUser.FirstName = serializeModel.FirstName;
newUser.LastName = serializeModel.LastName;
HttpContext.Current.User = newUser;
}
}
Access in Razor views
@((User as CustomPrincipal).Id)
@((User as CustomPrincipal).FirstName)
@((User as CustomPrincipal).LastName)
and in code:
(User as CustomPrincipal).Id
(User as CustomPrincipal).FirstName
(User as CustomPrincipal).LastName
I think the code is self-explanatory. If it isn't, let me know.
Additionally to make the access even easier you can create a base controller and override the returned User object (HttpContext.User):
public class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
and then, for each controller:
public class AccountController : BaseController
{
// ...
}
which will allow you to access custom fields in code like this:
User.Id
User.FirstName
User.LastName
But this will not work inside views. For that you would need to create a custom WebViewPage implementation:
public abstract class BaseViewPage : WebViewPage
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
Make it a default page type in Views/web.config:
<pages pageBaseType="Your.Namespace.BaseViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
and in views, you can access it like this:
@User.FirstName
@User.LastName
Best Answer
The Length=4 is coming from an attempt to serialize a string object. Your code is running this
ActionLink
method:This takes a
string
object "Home" for routeValues, which the MVC plumbing searches for public properties turning them into route values. In the case of astring
object, the only public property isLength
, and since there will be no routes defined with a Length parameter it appends the property name and value as a query string parameter. You'll probably find if you run this from a page not onHomeController
it will throw an error about a missingAbout
action method. Try using the following: