Modifying the default MVC app to show additional properties - asp.net-mvc

I'm trying to modify the default MVC project so that instead of showing a username, I can display their full name. Eg, the default app shows
Hello <username>! Log off
I added a new property FullName to the ApplicationUser class. The code that shows the name currently is:
#Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Manage", "Account", routeValues:=Nothing, htmlAttributes:=New With {.title = "Manage"})
So how can I get lookup that value from the ApplicationUser class and display it here? Additionally, is there a way to cache this? It seems like a waste to perform a lookup for every request.
I also might want to show their email address instead, so I definitely need to use a new property.

I generally like to serialize a user object in the FormsAuthentication cookie when they login and then create a class inheriting from IPrincipal so that my views can read the de-serialized object:
public interface IUserPrincipal : IPrincipal
{
int Id { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
string Username { get; set; }
}
public class UserPrincipal : IUserPrincipal
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public IIdentity Identity { get; private set; }
public UserPrincipal(string Username)
{
this.Identity = new GenericIdentity(Username);
}
}
public class UserPrincipalPoco
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
}
and then when authenticating:
public ActionResult Login(LoginViewModel vm, string ReturnUrl)
{
// Check for valid authentication
if (_authenticationService.Authenticate(vm.Username, vm.Password))
{
// Add forms authentication cookie
Response.Cookies.Add(GetFormsAuthenticationCookie(vm.Username));
// Redirect after authentication
}
// Failed authentication, redirect to unauthorized
}
private HttpCookie GetFormsAuthenticationCookie(string Username)
{
var user = _userService.GetUserByUsername(Username);
UserPrincipalPoco pocoModel = new UserPrincipalPoco();
pocoModel.Id = user.Id.Value;
pocoModel.FirstName = user.FirstName;
pocoModel.LastName = user.LastName;
pocoModel.Username = Username;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(pocoModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
Username,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
return new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
}
and then in global.asax.cs:
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();
UserPrincipalPoco serializeModel = serializer.Deserialize<UserPrincipalPoco>(authTicket.UserData);
UserPrincipal newUser = new UserPrincipal(authTicket.Name);
newUser.Id = serializeModel.Id;
newUser.FirstName = serializeModel.FirstName;
newUser.LastName = serializeModel.LastName;
newUser.Username = serializeModel.Username;
HttpContext.Current.User = newUser;
}
}
Now you need to create a BaseViewPage that inherits from WebViewPage to tell your views to use your UserPrincipal object:
public abstract class BaseViewPage : WebViewPage
{
public virtual new UserPrincipal User
{
get { return base.User as UserPrincipal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new UserPrincipal User
{
get { return base.User as UserPrincipal; }
}
}
and in your Web.config tell your views to always use this BaseViewPage:
<pages pageBaseType="MyNameSpace.Views.BaseViewPage">
Now in my views I can access the user like:
#User.Username
or
#User.FirstName #User.LastName

few ways you can do it.
if the application is not too big, you may cache the user models that log in for a specific period of time so you can pull the entire user info based on the username.
you may save a list if info in User.Identity, including username, firstname, last name etc, separating them with a comma or etc.
a bad way: every time you need the extra info, hit the database and get them.
my opinion: cache the recent users who have been logged in for a specific amount of time. you will be able to create slick solutions using cashing. let me know if you need info.

Related

ASP.NET MVC Authentication cast to IIdentity from IdentityUser

I am trying to implement authentication in my MVC project. The issue is that I have implemented ASP.NET Identity OWIN. I can successfully authenticate and everything looks good.
The issue is that there are other Projects (such as a Service Layer) that retrieves the authenticated user via System.Threading.Thread.CurrentPrincipal.Identity
In this service layer the value is casted to a custom class as following:
public MyCompanyIdentity Identity
{
get { return System.Threading.Thread.CurrentPrincipal.Identity as MyCompanyIdentity ; }
}
The content of the MyCompanyIdentity class is as Follows:
public class MyCompanyIdentity : IIdentity
{
public MyCompanyIdentity ();
public string EndUserName { get; set; }
public string Exception { get; set; }
public string Ticket { get; set; }
public string Company { get; set; }
public string Fullname { get; set; }
public string Email { get; set; }
public bool IsAuthenticated { get; }
public string Name { get; }
public string AuthenticationType { get; }
}
In my controller class I set the authentication as follow:
var userStore = new UserStore<DemoApplicationUser>(new PeopleSaaIdentityContext());
var userManager = new UserManager<DemoApplicationUser>(userStore);
var user = userManager.Find(username, password);
if (user != null)
{
var authManager = HttpContext.Current.GetOwinContext().Authentication;
ClaimsIdentity userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
authManager.SignIn(new Microsoft.Owin.Security.AuthenticationProperties() { IsPersistent = true }, userIdentity);
//How do I cast/convert the System.Threading.Thread.CurrentPrincipal.Identity to a MyCompanyIdentity?
}
Since I can't change the MyCompanyIdentity to implement IdentityUser as I don't have access to the source code.
How do I "cast/convert" to IIdenityUser? I would like to resuse the same class as it used literally everywhere in the application.
I really hope it makes sense else let me know and I would try to explain further.
It had no choice other than rewritting the logic from scratch.

Add existing entities to a collection from View Model - EF Code First

I am trying to add existing users to my Recipients collection in my MVC controllers CreateMessage action which passes a CreateMessageViewModel which should populate my domain Message object.
public class CreateMessageViewModel
{
public string Body { get; set; }
public ICollection<int> Recipients { get; set; }
}
Recipients are the posted user ID's I've selected in my view and the body is the message body.
My domain object is as follows:
public partial class Message : ModelBase
{
public Message() {
this.Recipients = new HashSet<User>();
}
[Key]
public int Id { get; set; }
public int SenderId { get; set; }
[ForeignKey("SenderId")]
public virtual User Sender { get; set; }
public virtual ICollection<User> Recipients { get; set; }
public string Body { get; set; }
}
My controller action and respository is as follows:
UnitOfWork db = new UnitOfWork();
[HttpPost]
public ActionResult Create(Model.ViewModel.CreateMessageViewModel messagevm)
{
if (ModelState.IsValid)
{
var CurrentUserId = WebSecurity.CurrentUserId;
MessageNotification message = new MessageNotification();
message.Message = new Message
{
Body = messagevm.Body,
SenderId = CurrentUserId
};
foreach (int userId in messagevm.Recipients)
{
// How do I add the existing recipients here?
// I get an exception here if I run this code and then save...
User recipient = new User { UserId = userId };
db.UserRepository.Attach(recipient);
message.Message.Recipients.Add(recipient);
}
message.Sent = DateTime.Now;
message.UserId = CurrentUserId;
db.MessagingRepository.Add(message);
db.Save();
return RedirectToAction("Index");
}
return View(messagevm);
}
public class Repository<TEntity> : IDisposable where TEntity : class
{
internal MyDB Context;
internal DbSet<TEntity> dbSet;
public Repository(MyDB context)
{
this.Context = context;
this.dbSet = Context.Set<TEntity>();
}
public virtual void Attach(TEntity entityToAttach)
{
dbSet.Attach(entityToAttach);
}
............
}
How do I attach the user entities to the Recipients collection without fetching them from the database? I am using the same implementation of the repository pattern as stated in the ASP.NET MVC tutorials .
With the code above I get the following exception:
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded. Refresh ObjectStateManager entries
You can do
var user = new User { UserId = userId };
db.Users.Attach(user);
message.Message.Recipients.Add(user);
You may want to check whether the user is already loaded in the context:
var user = db.Users.Local.FirstOrDefault(u => u.UserId == userId);
if (user == null)
... // create and attach user as above.
Edit
Your modification made me notice that db is not a context, but a unit of work. You could wrap the logic above in a method in db.MessagingRepository, like GetUser(int userId).

How to use custom httpcontext user when using custom Membership/Role Provider

Hi guys so I'm using a custom membership provider and custom role provider. And it is logging in using these correctly. I also implemented my own Membership user object so that I can gain access to other user information, and I don't need to load all the data everytime I change page, but I currently cannot get this to work properly. Below is my user object:
public class User : MembershipUser
{
[Required(ErrorMessage = "Username cannot be blank")]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required(ErrorMessage = "Password cannot be blank")]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "User ID")]
public long UserID { get; set; }
[Display(Name = "Family Name")]
[StringLength(50, ErrorMessage = "Family name cannot be longer than 50 characters")]
public string FamilyName { get; set; }
[Display(Name = "Given Name")]
[StringLength(50, ErrorMessage = "Given name cannot be longer than 50 characters")]
public string GivenName { get; set; }
public Company Company { get; set; }
public virtual IIdentity Identity { get; set; }
}
And when the user logs in I call the following login method:
[AllowAnonymous]
[HttpPost]
public ActionResult Login(User model, string returnUrl)
{
FormsAuthentication.SignOut();
if(Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, true);
return RedirectToAction("Index", "");
}
ViewBag.Message = "Failed to login";
return View();
}
But when I call HttpContext.User in index it just contains name/ID, not the rest of my user object. Do I need to create a custom FormAuthentication object? Or is it standard process to store all this user information inside the HttpContext.Session object? Or get my user to extend the System.Security.Principle.IPrinciple? Or even in the Controller.TempData? Or somewhere else I'm not familiar with. I dont want to have to hit the database everytime to load the user data.
Sorry if these are obvious questions I'm fairly new to web development and not sure what the generic way of doing these things are. Trying to use the in build Authorize attributes.
I did it by implementing my own identity. That way it's easy to add as many properties as I need. Below is a code example with custom property friendlyName
public class Identity : IIdentity
{
public Identity(int id, string name, string friendlyName, string roles)
{
this.ID = id;
this.Name = name;
this.FriendlyName = friendlyName;
this.Roles = roles;
}
public Identity(string name, string data)
{
if (string.IsNullOrWhiteSpace(data))
throw new ArgumentException();
string[] values = data.Split('|');
if (values.Length != 3)
throw new ArgumentException();
this.Name = name;
this.ID = Convert.ToInt32(values[0]);
this.FriendlyName = values[1];
Roles = values[2];
}
public string AuthenticationType
{
get { return "Custom"; }
}
public bool IsAuthenticated
{
get { return true; }
}
public override string ToString()
{
return FriendlyName;
}
public string GetUserData()
{
return string.Format("{0}|{1}|{2}", ID, FriendlyName, Roles);
}
public int ID { get; private set; }
public string Name { get; private set; }
public string FriendlyName { get; private set; }
public string Roles { get; private set; }
}
//in controller on login action:
Identity id = new Identity(user.ID, user.Username, "some friendly name", user.Roles);
DateTime expire = DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(id.ID, user.Username, DateTime.Now, expire, false, id.GetUserData());
string hashTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hashTicket);
HttpContext.Response.Cookies.Add(cookie);
In global.asax you have:
public override void Init()
{
base.Init();
PostAuthenticateRequest += new EventHandler(MvcApplication_PostAuthenticateRequest);
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket == null || authTicket.Expired)
return;
Identity id = new Identity(authTicket.Name, authTicket.UserData);
Principal user = new Principal(id);
Context.User = user;
Thread.CurrentPrincipal = user;
}
}

ASP.NET MVC Custom Validation in View Model Best Practice

I am trying to use a combination of Domain Driven Design with Test Driven Development for this application I am building in ASP.NET MVC 3. My archictecture is set up with Repositories, Domain Models, View Models, Controllers and Views. All validation will be handled in the view model. I set up my view model to inherit from "IValidatableObject" so that my validation attributes and my custom validation that i set up in the "Validate" method are both executed when my controller method calls "ModelState.IsValid". The problem I am running into is accessing my repository in the Validate method of my view model. I need to access the repository to check for duplicate records in the database. It seems like the best idea would be to create a property of IRepository type and set that property by passing injecting my repository into the constructor of the view model. For example:
public class UserViewModel : IValidatableObject
{
public UserViewModel(User user, IUserRepository userRepository)
{
FirstName = user.FirstName;
LastName = user.LastName;
UserRepository = userRepository;
UserName = user.UserName;
}
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IUserRepository UserRepository { get; set; }
public IEnumerable<ValidationResult> Validate()
{
UserCriteria criteria = new UserCriteria { UserName = this.UserName };
IList<User> users = UserRepository.SearchUsers(criteria);
if (users != null && users.count() > 0)
{
yield return new ValidationResult("User with username " + this.UserName + " already exists."
}
}
}
Do you guys think this is a good idea?
It is good enough but if I were you, I would use
...
private readonly Func<IUserRepository> userRepositoryFactory;
...
public IEnumerable<ValidationResult> Validate()
{
UserCriteria criteria = new UserCriteria { UserName = this.UserName };
using(var UserRepository = userRepositoryFactory())
{
IList<User> users = UserRepository.SearchUsers(criteria);
if (users != null && users.count() > 0)
{
yield return new ValidationResult("User with username " + this.UserName + " already exists."
}
}
}
You can add Domain Service class to get object match with your criteria and validated at domain service level
public class PurchaseOrder
{
public string Id { get; private set; }
public string PONumber { get; private set; }
public string Description { get; private set; }
public decimal Total { get; private set; }
public DateTime SubmissionDate { get; private set; }
public ICollection<Invoice> Invoices { get; private set; }
public decimal InvoiceTotal
{
get { return this.Invoices.Select(x => x.Amount).Sum(); }
}
}
public class PurchaseOrderService
{
public PurchaseOrderService(IPurchaseOrderRepository repository)
{
this.repository = repository;
}
readonly IPurchaseOrderRepository repository;
public void CheckPurchasedOrderExsist(string purchaseOrderId)
{
var purchaseOrder = this.repository.Get(purchaseOrderId);
if (purchaseOrder != null)
throw new Exception("PO already exist!");
}
}

Problems implementing IPrincipal

Trying to implement IPrincipal (ASP.NET MVC 3) and having problems:
my custom IPrincipal:
interface IDealsPrincipal: IPrincipal
{
int UserId { get; set; }
string Firstname { get; set; }
string Lastname { get; set; }
}
public class DealsPrincipal : IDealsPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public DealsPrincipal(string email)
{
this.Identity = new GenericIdentity(email);
}
public int UserId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
}
To serialize/deserialize i use the following class:
public class DealsPrincipalSerializeModel
{
public int UserId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
}
The Application authenticate event is as follows (works fine!)
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//get the forms ticket
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
//instantiate a new Deserializer
JavaScriptSerializer serializer = new JavaScriptSerializer();
//deserialize the model
DealsPrincipalSerializeModel serializeModel = serializer.Deserialize<DealsPrincipalSerializeModel>(authTicket.UserData);
//put the values of the deserialized model into the HttpContext
DealsPrincipal newUser = new DealsPrincipal(authTicket.Name); //this implements IPrincipal
newUser.UserId = serializeModel.UserId;
newUser.Firstname = serializeModel.Firstname;
newUser.Lastname = serializeModel.Lastname;
HttpContext.Current.User = newUser;
}
}
As you can see in the last statement the HttpContext gets assigned this new DealsPrincipal (which works fine).
The problem is that if want to access this User in a Controller(Action) i always get a base class object. If i cast the User as follows:
User as DealsPrincipal
to get for example the UserId (sample:
( User as DealsPrincipal).UserId
this is always null!!! Why? What am i missing?
I would need to investigate more to give you correct answer but look this part of the code and it could help you (part of the source of WindowsAuthenticationModule.cs)
void OnAuthenticate(WindowsAuthenticationEventArgs e) {
////////////////////////////////////////////////////////////
// If there are event handlers, invoke the handlers
if (_eventHandler != null)
_eventHandler(this, e);
if (e.Context.User == null)
{
if (e.User != null)
e.Context.User = e.User;
else if (e.Identity == _anonymousIdentity)
e.Context.SetPrincipalNoDemand(_anonymousPrincipal, false /*needToSetNativePrincipal*/);
else
e.Context.SetPrincipalNoDemand(new WindowsPrincipal(e.Identity), false /*needToSetNativePrincipal*/);
}
}
From this code I would suggest you to check if user is anonymous before assigning instance of your custom IPrincipal inmplementation. Also, not sure if this method is executed before or after "protected void Application_AuthenticateRequest". Will try to take more time to investigate this.
Also, please look at this article:
http://msdn.microsoft.com/en-us/library/ff649210.aspx

Resources