Built in Authentication with Custom Class. Code First - asp.net-mvc

MVC 5 comes with its own built in authentication which is great and exactly what I need. I don't want to re-invent the wheel, so I will use that. I have also seen lots of posts and information on how to extend that if I need more information for a user.
The question I have is that I need to have another Model that links to the user that is logged in. Imagine a forum where there are logged in users who have 'Posts'. I need a 'Post' class with a relationship to the user that ASP.NET has done all the work to create.
I tried doing something like:
public virtual ApplicationUser CreatedBy { get; set; }
However that didn't work, and I don't know enough to figure out how I get it to work. Any tutorials or examples online focus on the authentication element of MVC on its own, or the entity framework side where you make your own dbContext and go off to do everything in there. How can I link these two up?

To add a reference to your applicationuser from another class you can try something like this:
public class Post{
public int Id { get; set;
public string Name { get; set; }
public virtual ApplicationUser CreatedBy { get; set; }
}
And in your controller create action (or where you are creating your new Post):
(Added code lines for usermanager etc for clarity)
var post = new Post { Name = "My new post" }
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var currenApplicationUser = userManager.FindById(User.Identity.GetUserId());
var currentUser = db.Users.Find(currenApplicationUser.Id);
post.CreatedBy = currentUser;
db.Posts.Add(post);
db.SaveChanges();

Related

Best way to check if record exists and use site wide

I am fairly new to c# and MVC but I am building an intranet app. Being on the internal network there is no need to sign in to use the app but I do have it connected to a database which has an 'Administration' table. In this table are the administrator's email addresses. I am also using System.DirectoryServices.AccountManagement and then UserPrincipal.Current.EmailAddress to get the users email address. What I would like to do is compare the UserPrincipal.Current.EmailAddress to the database table and if there is a match then set a boolean to TRUE that I can reference/call upon within my entire site.
I have a model matching the database tables and I can also query the database using a where statement to the value of UserPrincipal.Current.EmailAddress but only within a set method (ActionResult) and return the boolean value within a viewbag to that particular controller that is accessed by the related view only.
I would like to know what is best practice for setting up my site so that whichever page a users visits their email is compaired to the database and a boolean is set to true/false if they are/aren't in the database administrator table.
Edit: Would this be to create a base controller and then inherit it in all other controllers and within the base controller perform the database query - if so a little guidance would be greatly appricated
My current set up is an EmailEntityModel:
using System;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
public partial class EmailEntities : DbContext
{
public EmailEntities()
: base("name=EmailEntities")
{
}
public virtual DbSet<Audience> Audiences { get; set; }
public virtual DbSet<CallToAction> CallToActions { get; set; }
public virtual DbSet<ColourScheme> ColourSchemes { get; set; }
public virtual DbSet<Email> Emails { get; set; }
public virtual DbSet<EmailType> EmailTypes { get; set; }
public virtual DbSet<Administrator> Administrators { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
Then I have an email Controller:
public class EmailsController : Controller
{
private EmailEntities db = new EmailEntities();
public ActionResult Index()
{
return View(db.Emails.ToList());
}
Can I use the EmailEntities to query the Administator DBset within my controller but can I use this elsewhere?
If I understood your question correctly, you want to query the DB on every request and compare the current user's email against the admin email. If that's the case, then you have many options.
If it was me, I would keep the Admin email in a constant/static variable (so I don't have to make the trip to the DB on every request):
public static class StaticCache
{
// static constructor would run only once, the first it is used
// this value is maintained for the entire life-time of the application
static StaticCache()
{
using (var context = MyApplicationDbContext.Create())
{
// get your admin email for the DB
AdminEmail = context.Email.Where(/*some admin flag == true*/).FirstOrDefault();
}
}
public static string AdminEmail;
public static bool IsAdminUser(string curEmail)
{
return string.Equal(curEmail, AdminEmail, StringComparison.InvariantCultureIgnoreCase);
}
}
That's it. Now you can call StaticCache.IsAminUser() anywhere in your program (even in your view). All you need is to pass the current email to the method.

How to cache and get properties from extended Identity, AspUser in ASP Identity 2.2

To support tenan/companies, I added/extended a new property to the AspUser Table called OrgId in ASP MVC 5, Identity 2.2 role management, I added the corresponding OrgId to some other tables, looked here but no answers
During User Login() and the UserSession
how do I cache, configure & retrieve the OrgId, so that I can perform DBConext filtering/CRUD of table for Org specific records?
Advice: is better to save this in the Claims, FormsToken or Session - and
how to set the tenanId context in session?
I know how to get user, but not the extended Org
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>().FindById(System.Web.HttpContext.Current.User.Identity.GetUserId());
Your customized user class should be like this:
public class CustomizedUser : IdentityUser
{
public int OrgId {get; set;}
public DateTime JoinDate { get; set; }
//...
// and other simple fields
//Fields related to other tables
public virtual ICollection<Article> Articles { get; set; } = new List<Article>();
//...
}
And your CustomizedApplicationDbContext class
public class CustomizedApplicationDbContext : IdentityDbContext<CustomizedUser>, IApplicationDbContext
{
public CustomizedApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static CustomizedApplicationDbContext Create()
{
return new CustomizedApplicationDbContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//your entity configurations go here if you have any
base.OnModelCreating(modelBuilder);
}
//These fields are neccessary in order to maintain the power of base class
public DbChangeTracker Changetracker => base.ChangeTracker;
public Database DatabBase => base.Database;
//your own dbsets go here except CustomizedUser, because the base class IdentityDbContext<CustomizedUser> handles it
public IDbSet<Article> Articles { get; set; }
//...
}
Now, Remember to replace every ApplicationDbContext references with CustomizedApplicationDbContext and every IdentityUser refrences with CustomizedUser in your code (specially in your ManageController created by mvc).
From now on, you can easily access users table just like other tables:
var db = new CustomizedApplicationDbContext();
var user = db.CustomizedUsers.First(x=> x.OrgId == 10 || Email == "sth#yahoo.com");
And to get the current user OrgId, you can use something like this(without querying db):
var currentUserOrgId = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>().FindById(User.Identity.GetUserId()).OrgId;
Hope this was helpful
You can get the current user in ASP.NET Identity as shown below:
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.FindById(System.Web.HttpContext.Current.User.Identity.GetUserId());
//If you use int instead of string for primary key, use this:
ApplicationUser user = System.Web.HttpContext.Current.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.FindById(Convert.ToInt32(System.Web.HttpContext.Current.User.Identity.GetUserId()));
For getting custom properties from AspNetUsers table:
ApplicationUser user = UserManager.FindByName(userName);
string name = user.Name;
Hope this helps...

Editing some properties of View Model in ASP.NET MVC

I'm using Entity Framework Database First approach. Let's say I have a model class called Product and that class has a NumberOfViews property. In the Edit page I pass an instance of the product class to the controller.
The problem is I can't add #Html.EditorFor(model => model.NumberOfViews) in the Edit page, because it's supposed that NumberOfViews is updated with every visit to the product page, and NOT by the website Admin.
And I can't add it as #Html.HiddenFor(model => model.NumberOfViews), because if the Admin Inspected the element, he can edit it manually.
Also If I try to programmatically set the value on the server-side (e.g., Product.NumberOfViews = db.Products.Find(Product.Id).NumberOfViews;), I get the following error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
And if I don't add it to either the view or the controller, the value will be null, thus overriding any previous value.
So what should I do?
I have noticed a lot of people use the same model for their Entity Framework as they do for their MVC Controller. I generally discourage this practice. In my opinion, a database model is not the same as a view model.
Sometimes a view needs less information than what the database model is supplying. For example while modifying account password, view does not need first name, last name, or email address even though they may all reside in the same table.
Sometimes it needs information from more than one database table. For example if a user can store unlimited number of telephone numbers for their profile, then user information will be in user table and then contact information with be in contact table. However when modifying user profile, they may want to add/edit/delete one or more of their numbers, so the view needs all of the numbers along with first name, last name and email address.
This is what I would do in your case:
// This is your Entity Framework Model class
[Table("Product")]
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
public string Name { get; set; }
public int NumberOfPageViews { get; set; }
}
// This is the model you will use in your Edit action.
public class EditProductViewModel
{
public int ProductId { get; set; }
public string Name { get; set; }
}
public class ProductController : Controller
{
IProductService service;
//...
[HttpGet]
public ActionResult Edit(int productId)
{
var product = service.GetProduct(productId);
var model = new EditProductViewModel()
{
ProductId = product.ProductId,
Name = product.Name
};
return View(model);
}
[HttpPost]
public ActionResult Edit(EditProductViewModel model)
{
if (ModelState.IsValid)
{
var product = service.GetProduct(model.ProductId);
product.Name = model.Name;
service.Update(product);
}
// ...
}
}

How to get user object and save it with model?

I've created my incredibly simplistic model:
public class ImageModel
{
public int Id { get; set; }
public string FileName { get; set; }
}
And now I want to store the logged-in user with the record. Presumably, I would do this by adding another property:
public User User { get; set; }
Which Visual Studio is telling me is telling me is in
using System.Web.Providers.Entities;
Assuming that's the right User class that corresponds with the presently authenticated user, how do I save that with my model?
My Create action looks like this:
[HttpPost]
public ActionResult Create(ImageModel imagemodel)
{
if (ModelState.IsValid)
{
db.ImageModels.Add(imagemodel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(imagemodel);
}
I imagine I would want to add something like
imagemodel.User = User;
Just above db.ImageModels.Add, but those appear to be two different types. The User object in Controller appears to be an IPrincipal which really doesn't seem to hold much information at all.
How do I get the rest of the user data for the authenticated user? What do I need to assign imagemodel.User to? Is that even the way to do it, or do I need to explicitly tell it I just want to save the User ID (I'm assuming this much it could figure out) -- and if so, how do I keep a User object on my model such that it points to the real User object (not just an ID), and how do I get the User ID for the currently logged in user?
It is dependent upon what technology you are using to manage logons or sessions.
Inside the controller method, you probably just want to set your model property to the value in 'User.Identity.Name', which is a string value.
That assumes the user is logged in and that you have forms authentication configured. You've probably previously authenticated the user, and given them a token (basically just an encrypted cookie) containing the value of '.Name', via the FormsAuthentication.SetAuthCookie method.
So, to keep things very simple, your Image model should probably just have a Username string property. If you have access to the user identity table, you might want to store a reference to the related user instead.
public class ImageModel
{
public int Id { get; set; }
public string FileName { get; set; }
public string Username { get; set; }
}
Controller...
[HttpPost]
public ActionResult Create(ImageModel imagemodel)
{
if (ModelState.IsValid && User.Identity.IsAuthenticated)
{
imagemodel.Username = User.Identity.Name;
db.ImageModels.Add(imagemodel);
db.SaveChanges();
return RedirectToAction("Index");
}
The interfaces exposed without knowing what provider you are using are very minimal.
IPrinicipal User
bool IsInRole(string role)
IIdentity Identity
string AuthenticationType
bool IsAuthenticated
string Name
That's it.
Once you select a provider or decide to implement a custom one there's a whole range of SO articles that will fall into your lap.
You may also be better served looking for ASP.NET references than MVC references when researching this topic.

Using NerdDinner as base reference, how to perform data access tasks in controller?

I am trying to follow the Nerd Dinner MVC application as a base to learn the correct way to develop MVC applications.
I have created Interfaces and Repositories as the reference code suggests and am using Entity Framework for data access.
If I want to insert data when a user registers into a table [dbo].[Users], I do not have a controller for Users, how do I do it?
AccountController.cs
[HandleError]
public class AccountController : BaseController
{
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email);
if (createStatus == MembershipCreateStatus.Success)
{
// TODO: Enter record into [Users] get reference to [Aspnet_UserId]
// How do I do this??
//FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
If I create a UsersController to display views based on the Users table, how would I then add a new record when the user is registering?
I have a separate table [Users] that I wish to populate when a new user registers adding the [Aspnet_UserId] Guid.
You don't need to have a controller for each table in your database. In the code above, the MembershipService is the code that is actually creating the record (via the Repository for users).
The controllers should represent various areas and groups of functionality your website provides. While in many cases, you might have a controller with View, Create, and Update actions that do relate to a specific entity, that does relate to a specific database table, that isn't and shouldn't always be the case.
If it makes sense to have a UsersController because you want to view a list of users, or a specific users profile, that's fine, but the form for creating a user doesn't have to be a part of that controller. Having it be a part of a membership, or admin, or account, or registration controller is ok too.
Update
I'll try to provide you sample code of how I would expect the code to look. But you might have something else in mind, which is fine too, there's no true single way to do these things.
In your code above, I'm not sure what your MembershipService class is doing. It appears there is a static method on it that does something related to User Creation. I would expect that your MembershipService class should be calling your UserRepository to actually do the user creation. But you probably wouldn't want a static class/method for this.
public class MembershipCreationResult
{
public User Member { get; private set; }
public MembershipCreateStatus MembershipCreateStatus { get; private set; }
public MembershipCreationResult(User user, MembershipCreateStatus status)
{
Member = user;
MembershipCreateStatus = status;
}
public bool Success
{
get { return MembershipCreateStatus == MembershipCreateStatus.Success; }
}
}
public class MembershipService
{
public IUserRepository { get; private set; }
public MembershipService(IUserRepository userRepository)
{
UserRepository = userRepository;
}
public MembershipCreateResult CreateUser(string name, string password, string email)
{
User member = UserRepository.Create(name, password, email);
bool status = member != null ? MembershipCreateStatus.Success : MembershipCreateStatus.Failure;
return new MembershipCreationResult(status, member)
}
}
I haven't taken a very close look at the NerdDinner sample, and I haven't used the ASP.NET membership provider, but the concept I have outlined above should work. If MembershipService does something way different from what I have outlined, then you could create a new service to wrap the functionality and leave the existing MembershipService alone.

Resources