Is there a way to set a friendly name on FormsAuthentication so I can have access to both the ID and friendly name in the Context.User.Identity
I'd Like to display the First/Last name with a url pointing to the profile page of the user by the userid.
This is what I currently have:
View
#var user = Context.User.Identity;
#if (Request.IsAuthenticated)
{
#Html.ActionLink(user.Name, "Details", "Users", new { id = user.Name });
}
As you can see, it will show only the UserId.
Controller
User user = authService.ValidateUser(model.Email, model.Password);
string alias = string.Format("{0} {1}", user.FirstName, user.LastName);
//I'd like some way to set both the user.UserId and alias in the cookie and access it in the view
FormsAuthentication.SetAuthCookie(user.UserId, createPersistentCookie);
Yes, just create your own implementation of IPrincipal. Then hook up an HttpModule to the PostAuthenticated event where you instantiate your principal object and set CurrentUser to that instance. Now anytime you access CurrentUser you will get your instance of the IPrincipal that is decorated with all of the extra data you need.
I think the best way to do this is to use a common view model that has this property. Have all of your other view models derive from this model. Use a base controller and override the OnActionExecuted method, setting the common view model properties when the result being returned is a ViewResult. Your views would be strongly typed either to the common view model or a subclass from it, allowing you to reference the properties directly.
public class CommonViewModel
{
public string UserDisplayName { get; set; }
public string Username { get; set; }
}
public class FooViewModel : CommonViewModel
{
// view-specific properties
}
public class BaseController : Controller
{
public override void OnActionExecuted( ActionExecutedContext context )
{
if (context.Result is ViewResult)
{
UpdateCommonModel( ((ViewResult)context.Result).ViewData.Model as CommonViewModel );
}
}
private void UpdateCommonModel( CommonViewModel model )
{
User user = authService.ValidateUser(model.Email, model.Password);
modelUserDisplayName = string.Format("{0} {1}", user.FirstName, user.LastName);
model.Username = user.Name;
}
}
View
#if (Request.IsAuthenticated)
{
#Html.ActionLink(model.UserDisplayName, "Details", "Users", new { id = Model.Username });
}
If you're using MVC 3 you can use a global filter instead if you really did not want to add a base controller.
Here is a good article on this topic, and it has been written in regards of MVC 4:
http://www.codeproject.com/Tips/574576/How-to-implement-a-custom-IPrincipal-in-ASP-NET-MV
There are two little mistakes in the code, but I have pointed them out in a comment at the bottom of the article.
Related
I have 3 roles in my webapp: Admin,Moderator,User.
I have a user #model WebApplication2.Models.ApplicationUser I want to check inside Razor view if user.Roles contains role Moderator. How to do so? I tried #if(#Model.Roles.Contains(DON'T_KNOW_WHAT_TO_WRITE_HERE){}.
NOTE: I am not asking how to check if currently authorized user is in certain role.
What you could do is create an extension method on IPrincipal that operates the same way as User.IsInRole(...)
public static bool IsInAppRole(this IPrincipal user, string role)
{
using(var db = new MyEntities())
{
var dbUser = db.Users.Find(user.Identity.GetUserId());
return dbUser.Roles.Any(r => r.RoleName == role)
}
}
Import extensions into a view
#using MyApplication.Web.Extensions
Use like you would IsInRole()
#if(User.IsInAppRole("Admin"))
{
}
Though, not sure why you'd do this as the user's roles can be put into their Identity object.
The simplest way that I could find is to use:
#Model.Roles.SingleOrDefault().RoleId
This, of course, requires you to work with the ID rather than the name in your comparison. Without creating a new ViewModel, I have not found a good way to get direct access to the name.
EDIT: If multiple roles are assigned, you should be able to do something like this:
#{
var isMod = false;
}
foreach (var r in Model.Roles)
{
if(r.RoleId == "1")
{
isMod = true;
}
}
I think you should use User.IsInRole(...) in your view code
Why don't you just print them out to preview possible values?
Your code seems to have minor bug to me. I believe it should be
#if(Model.Roles.Contains(DON'T_KNOW_WHAT_TO_WRITE_HERE){}
(just one '#')
EDIT:
Since Model.Roles are just plain Many-To-Many references, you need to call UserManager to obtain user roles. For example:
public class UserDetailsModel {
public string Id { get; set; }
public ApplicationUser User { get; set; }
public IList<string> Roles { get; set; }
}
and Details action in controller:
public ActionResult Details(string id) {
var model = new UserDetailsModel
{
Id = id,
User = UserManager.FindById(id),
Roles = UserManager.GetRoles(id)
};
return View(model);
}
You can get UserManager from OwinContext or inject it in controller:
private readonly ApplicationUserManager _userManager;
public UsersController(){}
public UsersController(ApplicationUserManager userManager) {
_userManager = userManager;
}
public ApplicationUserManager UserManager {
get {
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
}
I'm attempting to create a single Controller class to handle all foreseeable surveys that I'll end up creating in the future. Currently I have a 'Surveys' table with fields: Id, SurveyName, Active. On the 'master' Surveys' Index page I list out every SurveyName found in that table. Each SurveyName is clickable, and when clicked on, the page sends the SurveyName as a string to the receiving controller action. Said controller action looks like this:
//
//GET: /Surveys/TakeSurvey/
public ActionResult TakeSurvey(string surveyName)
{
Assembly thisAssembly = Assembly.GetExecutingAssembly();
Type typeToCreate = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
object newSurvey = Activator.CreateInstance(typeToCreate);
ViewBag.surveyName = surveyName;
return View(surveyName, newSurvey);
}
Using reflection I am able to create a new instance of the type (Model) designated by the passed-in string 'surveyName' and am able to pass that Model off to a view with the same name.
EXAMPLE
Someone clicks on "SummerPicnic," the string "SummerPicnic" is passed to the controller. The controller, using reflection, creates a new instance of the SummerPicnic class and passes it to a view with the same name. A person is then able to fill out a form for their summer picnic plans.
This works all fine and dandy. The part that I'm stuck at is trying to save the form passed back by the POST method into the correct corresponding DB table. Since I don't know ahead of time what sort of Model the controller will be getting back, I not only don't know how to tell it what sort of Model to save, but where to save it to, either, since I can't do something ridiculous like:
//
//POST: Surveys/TakeSurvey
[HttpPost]
public ActionResult TakeSurvey(Model survey)
{
if (ModelState.IsValid)
{
_db. + typeof(survey) + .Add(survey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
Is there a way to do this, or should I go about this from a whole different angle? My ultimate goal is to have a single Controller orchestrating every simple-survey, so I don't have to create a separate controller for every single survey I end up making down the road.
An alternative solution I can think of is to have a separate method for every survey, and to have which method to call defined inside of every survey's view. For example, if I had a SummerPicnic survey, the submit button would call an ActionMethod called 'SummerPicnic':
#Ajax.ActionLink("Create", "SummerPicnic", "Surveys", new AjaxOptions { HttpMethod = "POST" })
A survey for PartyAttendance would call an ActionMethod 'PartyAttendance,' etc. I'd rather not have to do that, though...
UPDATE 1
When I call:
_db.Articles.Add(article);
_db.SaveChanges();
This is what _db is:
private IntranetDb _db = new IntranetDb();
Which is...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace Intranet.Models
{
public class IntranetDb : DbContext
{
public DbSet<Article> Articles { get; set; }
public DbSet<ScrollingNews> ScrollingNews { get; set; }
public DbSet<Survey> Surveys { get; set; }
public DbSet<Surveys.test> tests { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
You can try something like this,
UPDATE:
The built-in UpdateModel will work with generic model see this post, so we got little more work.
[HttpPost]
public ActionResult TakeSurvey(FormCollection form, surveyName)
{
var surveyType = Type.GetType(surveyName);
var surveyObj = Activator.CreateInstance(surveyType);
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => surveyObj, surveyType),
ModelState = ModelState,
ValueProvider = form
};
binder.BindModel(ControllerContext, bindingContext);
if (ModelState.IsValid)
{
// if "db" derives from ObjectContext then..
db.AddObject(surveyType, surveyObj);
db.SaveChanges();
// if "db" derives from DbContext then..
var objCtx = ((IObjectContextAdapter)db).ObjectContext;
objCtx.AddObject(surveyType, surveyObj);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
Check this two know the diff between DbContext and ObjectContext
I ended up with a slightly modified version of Mark's code:
[HttpPost]
public ActionResult TakeSurvey(string surveyName, FormCollection form)
{
//var surveyType = Type.GetType(surveyName);
//var surveyObj = Activator.CreateInstance(surveyType);
// Get survey type and create new instance of it
var thisAssembly = Assembly.GetExecutingAssembly();
var surveyType = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
var newSurvey = Activator.CreateInstance(surveyType);
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => newSurvey, surveyType),
ModelState = ModelState,
ValueProvider = form
};
binder.BindModel(ControllerContext, bindingContext);
if (ModelState.IsValid)
{
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
objCtx.AddObject(surveyName, newSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
I was running into surveyType being 'null' when it was set to Type.GetType(surveyName); so I went ahead and retrieved the Type via Reflection.
The only trouble I'm running into now is here:
if (ModelState.IsValid)
{
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
objCtx.AddObject(surveyName, newSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
When it tries to AddObject I'm getting the exception "The EntitySet name 'IntranetDb.test' could not be found." I just need to figure out to strip off the prefix 'IntranetDb.' and hopefully I'll be in business.
UPDATE
One thing I completely overlooked was passing the Model to the controller from the View...oh bother. I currently have an ActionLink replacing the normal 'Submit' button, as I wasn't sure how else to pass to the controller the string it needs to create the correct instance of Survey model:
<p>
#Ajax.ActionLink("Create", "TakeSurvey", "Surveys", new { surveyName = ViewBag.surveyName }, new AjaxOptions { HttpMethod = "POST" })
#*<input type="submit" value="Create" />*#
</p>
So once I figure out how to turn 'IntranetDb.test' to just 'test' I'll tackle how to make the Survey fields not all 'null' on submission.
UPDATE 2
I changed my submission method from using an Ajax ActionLink to a normal submit button. This fixed null values being set for my Model values after I realized that Mark's bindingContext was doing the binding for me (injecting form values onto the Model values). So now my View submits with a simple:
<input type="submit" value="Submit" />
Back to figuring out how to truncate 'IntranetDb.test' to just 'test'...
Got It
The problem lies in my IntranetDb class:
public class IntranetDb : DbContext
{
public DbSet<Article> Articles { get; set; }
public DbSet<ScrollingNews> ScrollingNews { get; set; }
public DbSet<SurveyMaster> SurveyMaster { get; set; }
public DbSet<Surveys.test> tests { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
objCtx.AddObject(surveyName, newSurveyEntry); was looking for an entry (an "EntitySet") in the IntranetDb class called "test." The problem lies in the fact that I don't have an EntitySet by the name of "test" but rather by the name of "tests" with an 's' for pluralization. Turns out I don't need to truncate anything at all, I just need to point to the right object :P Once I get that straight I should be in business! Thank you Mark and Abhijit for your assistance! ^_^
FINISHED
//
//POST: Surveys/TakeSurvey
[HttpPost]
public ActionResult TakeSurvey(string surveyName, FormCollection form)
{
//var surveyType = Type.GetType(surveyName);
//var surveyObj = Activator.CreateInstance(surveyType);
// Create Survey Type using Reflection
var thisAssembly = Assembly.GetExecutingAssembly();
var surveyType = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
var newSurveyEntry = Activator.CreateInstance(surveyType);
// Set up binder
var binder = Binders.GetBinder(surveyType);
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => newSurveyEntry, surveyType),
ModelState = ModelState,
ValueProvider = form // Get values from form
};
var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
// Retrieve EntitySet name for Survey type
var container = objCtx.MetadataWorkspace.GetEntityContainer(objCtx.DefaultContainerName, DataSpace.CSpace);
string setName = (from meta in container.BaseEntitySets
where meta.ElementType.Name == surveyName
select meta.Name).First();
binder.BindModel(ControllerContext, bindingContext); // bind form values to survey object
if (ModelState.IsValid)
{
objCtx.AddObject(setName, newSurveyEntry); // Add survey entry to appropriate EntitySet
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
It's kind of bloated but it works for now. This post helped me get the EntitySet from the Survey object itself so I didn't need to worry about establishing some sort of EntitySet naming convention.
The main problem I see is to bind to the model to the TakeSurvey POST method. If you want different types of survey models should be handled by this method and MVC should bind to this model before calling the action, I believe you can have a wrapper model class over all such generic model, say SurveyModel and use custom model binder to bind to these models.
public class SurveyModel
{
public string GetSurveyModelType();
public SummerPicnicSurvey SummerPicnicSurvey { get; set; }
public PartyAttendanceSurvey PartyAttendanceSurvey { get; set; }
}
Then write a custom mobel binder to bind this model. From the request form fields we can see what type of survey model is posted and then accordingly fetch all the fields and initialize the SurveyModel class. If SummerPicnicSurvey is posted then class SurveyModel will be set with this class and PartyAttendanceSurvey will be null. Example custom model binder.
From the controller action TakeSurvey POST method, You can update db like this:
[HttpPost]
public ActionResult TakeSurvey(SurveyModel survey)
{
if (ModelState.IsValid)
{
if(survey.GetSurveyModelType() == "SummerPicnicSurvey")
_db.UpdateSummerPicnicSurvey(survey.SummerPicnicSurvey);
else if (survey.GetSurveyModelType() == "PartyAttendanceSurvey")
_db.UpdateSummerPicnicSurvey(survey.PartyAttendanceSurvey);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
return View();
}
Instead of SurveyModel encapsulating the other surveys you can have inheritance and use .net as to typecast with a check and use the Model.
Having said this, I think there is no harm in using different methods for each model. This will enable you to unit test the code well. Too many if else is not healthy to maintain. Or you can transfer the generic model SurveyModel to the repository or data access layer and let it handle that in a polymorphic way. I would prefer more small functions and keep the code clean.
Edit: The inheritance way:
public class SurveyModel
{
public virtual bool Save();
}
public partial class SummerPicnicSurvey : SurveyModel
{
public bool Save(SummerPicnicSurvey survey)
{
using(var _dbContext = new MyContext())
{
_dbContex.SummerPicnicSurveys.Add(survey);
_dbContex.SaveChanges();
}
}
}
[HttpPost]
public ActionResult TakeSurvey(SurveyModel survey)
{
if (ModelState.IsValid)
{
survey.Save();
return RedirectToAction("Index", "Home");
}
return View();
}
Any new Survey model type you add has to implement the SaveChanges or Save method, Which would call the proper dbcontext method. The controller action would just call Save on the generic `SurveyModel' reference passed to it. Thus the action will be closed for modification but open for modification. The open-close design principle.
I am making an ASP.Net MVC3 application. I use for now the built in Authentication code that comes with a Visual Studio 2010 project. The problem is dat I need to retrieve the logged in user's database ID as soon as he has logged in. I do that now by adding code to the Login Action of the Account controller that retrieves the ID from the database by looking it up by username. This works for new logins, but not for "remembered" ones. On restarting the application the last user is automatically logged in again, but the Login code is not fired, so I do not get the database ID.
How can I solve this?
EDIT:
I tried to implement Daniel's solutions which looks promising and I came up with this code. It nevers gets called though! Where have I gone wrong?
Global.asax.cs:
protected void Application_Start()
{
Database.SetInitializer<StandInContext>(new StandInInitializer());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
this.AuthenticateRequest +=
new EventHandler(MvcApplication_AuthenticateRequest);
}
void MvcApplication_AuthenticateRequest(object sender, EventArgs e)
{
if(Request.IsAuthenticated)
{
using (var db = new StandInContext())
{
var authenticatedUser = db.AuthenticatedUsers.SingleOrDefault(
user => user.Username == User.Identity.Name);
if (authenticatedUser == null)
return;
var person = db.Persons.Find(authenticatedUser.PersonID);
if (person == null)
return;
Context.User = new CustomPrincipal(
User.Identity, new string[] { "user" })
{
Fullname = person.FullName,
PersonID = person.PersonID,
};
}
}
}
You can use the AuthenticateRequest event in your Global.asax.cs:
protected void Application_AuthenticateRequest()
{
if (Request.IsAuthenticated)
{
// retrieve user from repository
var user = _membershipService.GetUserByName(User.Identity.Name);
// do other stuff
}
}
Update:
Now that I see what you're trying to do a little clearer, I would recommend against using sessions in this particular case. One reason is that Session requires a reference to System.Web, which you don't have access to from some places, like a business logic layer in a separate class library. IPrincipal, on the other hand, exists for this very reason.
If you need to store more user information than what IPrincioal provides, you simply implement it and add your own properties to it. Easier yet, you can just derive from GenericPrincipal, which implements IPrincipal and adds some basic role checking functionality:
CustomPrincipal.cs
public class CustomPrincipal : GenericPrincipal
{
public CustomPrincipal(IIdentity identity, string[] roles)
: base(identity, roles) { }
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
...
}
So then you replace the default principal with your own in AuthenticateRequest, as before:
Global.asax.cs
protected void Application_AuthenticateRequest()
{
if (Request.IsAuthenticated)
Context.User = _securityService.GetCustomPrincipal(User.Identity.Name);
}
And that is it. The greatest advantage you get is that you automatically get access to your user data from literally everywhere, without having to stick a userId parameter into all your methods. All you need to do is cast the current principal back to CustomPrincipal, and access your data like so:
From your razor views:
<p>Hello, #((CustomPrincipal)User).FirstName!</p>
From your controllers:
var firstName = ((CustomPrincipal)User).FirstName;
From a business logic layer in another assembly:
var firstName = ((CustomPrincipal)Thread.CurrentPrincipal).FirstName;
To keep things DRY, you could pack this into an extension method and hang it off IPrincipal, like so:
public static class PrincipalExtensions
{
public static string GetFirstName(this IPrincipal principal)
{
var customPrincipal = principal as CustomPrincipal;
return customPrincipal != null ? customPrincipal.FirstName : "";
}
}
And then you would just do #User.GetFirstName(), var userName = User.GetFirstName(), Thread.CurrentPrincipal.GetFirstName(), etc.
Hope this helps.
I wasn´t thinking clear. I was trying to store the userinfo in the Session object, while it available through the User object. Sorry to have wasted your time.
I have the following code in my controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] BackupSet AccountToCreate)
{
if (!ModelState.IsValid)
return View();
_DBE.AddToBackupSet(AccountToCreate);
_DBE.SaveChanges();
return RedirectToAction("Index");
}
I need to have the value of User.Identity.Name set to be the value of one of the fields in the create view when I post it to the database.
I am sure its very simple but really don't know how.
Thanks,
Steve.
Why do you need to store the username in the view? You will surely be initiating the DB transaction from within a controller so if it's the username for the user that is currently logged in use the MembershipProvider as per the last suggestion:
HttpContext.Current.User.Identity.Name
If not perhaps you should consider creating a container/wrapper class that clearly represents your View model - some might consider this overkill for one extra property but I hate "magic strings" in code.
public class MyView
{
public string UserName { get; set; }
public MyObject MyMainObject { get; set;}
public MyView(string username, MyObject myMainObject)
{
this.Username = username;
this.MyMainObject = myMainObject;
}
}
then set your view model type as:
System.Web.Mvc.ViewPage<MyNamespace.MyView>
this then allows you to have strongly typed properties for everything in your view e.g.
<%=Model.Username %>
<%=Model.MyMainObject.Title %>
and in your controller you can parameterize your Action as
public ActionResult(MyMainObject myMainObject, string username)
{
//Do something here
//if not correct
return View(new MyView(username, myMainObject));
}
If instead you wanted to go down this path:
ViewData["Name"] = User.Identity.Name;
or
ViewData.Add("Name", User.Identity.Name);
Consider creating Enums to once again avoid using string literals e.g.
public enum UserEnum
{
Username,
Password
}
then use:
ViewData.Add(UserEnum.Username.ToString(), User.Identity.Name);
one of the fields in view?
how about simply setting
ViewData["Name"] = User.Identity.Name
and then in View use it wherever you want.
Short Answer:
HttpContext.Current.User.Identity.Name
Long Answer:
You should probably make a membership service to provide that value. The default MVC2 project will provide IMembershipService interface which you can expand to provide property: CurrentUserName (or whatever you like)
public string CurrentUserName
{
get
{
var context = HttpContext.Current;
if (null != context)
return context.User.Identity.Name;
var user = Thread.CurrentPrincipal;
return (null == user)
? string.Empty
: user.Identity.Name;
}
}
How can I include a user regardless of his role, dependent on a matching userID, and not always same user:
[Authorize(Roles="Group1") AND userID=uniqueID]
You won't be able to do this with the default AuthorizeAttribute. You will need to extend AuthorizeAttribute with a custom class that adds the user behavior. Typically it uses named users, but you could provide an alternative. Normally if you supply both Users and Roles, it will require that the user be in the list of users and have one of the indicated roles.
public class UserOrRoleAuthorizeAttribute : AuthorizeAttribute
{
public int? SuperUserID { get; set; }
protected override bool AuthorizeCore( HttpContextBase httpContext )
{
if (base.AuthorizeCore( httpContext ))
{
var userid == ...get id of current user from db...
return userid == this.SuperUserID;
}
return false;
}
}
Used as:
[UserOrRoleAuthorize(Roles="Admin",SuperUserID=15)]
public ActionResult SomeAction() ...
Note that you could also add in some way of specifing where to look for the id for this action, .i.e.,
Table="Admins",Column="AdminID",MatchProperty="Company",MatchParameter="company"
then put some code into the attribute to look up the value in the property table and column and comparing it to the specified RouteValue entry instead of hard-coding it.
You could write a custom Authorize filter (implement IAuthorizationFilter)
Your custom Authorize filter could take the userId as a parameter.
Something like
public class
YourAuthorizeFilterAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public string UserId { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
if(filterContext.HttpContext.User.Identity.Name != UserId &&
!filterContext.HttpContext.User.IsInRole(base.Roles))
{
filterContext.Result = new RedirectResult("/Account/LogOn");
}
}
}
Then use your own filter like so
[YourAuthorizeFilter(UserId = "theuser", Roles ="Group1")]
Kindness,
Dan