I'm trying to get Entity Framework Code First to create a database for me but seem to be running into issues with it not actually creating the tables.
In my Web.Config I have the connection string setup and pointed to the database (without tables) I setup:
<connectionStrings>
<add name="DataContext" connectionString="Server=.\SQLEXPRESS;Database=MyDatabase;Trusted_Connection=Yes;" providerName="System.Data.SqlClient" />
</connectionStrings>
In my DataConext.cs class located in my Domain project I have the following: (I am using the Altairis.Web.Security plugin to help with membership stuff)
public class DataContext : DbContext
{
public DbSet<Faq> Faqs { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Maps to the expected many-to-many join table name for roles to users.
modelBuilder.Entity<User>()
.HasMany(u => u.Roles)
.WithMany(r => r.Users)
.Map(m =>
{
m.ToTable("RoleMemberships");
m.MapLeftKey("UserName");
m.MapRightKey("RoleName");
});
}
}
public class DataContextInitializer : CreateDatabaseIfNotExists<DataContext>
{
protected override void Seed(DataContext context)
{
// create roles
var roles = new List<Role>{
new Role{RoleName = "Administrator"}, // admin area: admin user
new Role{RoleName = "User"}, // admin area: normal user
new Role{RoleName = "Client"} // front end: normal user
};
roles.ForEach(r => context.Roles.Add(r));
}
}
In my Web project Global.asax.cs I have:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
Database.SetInitializer(new DataContextInitializer());
}
Everything compiles and works away but the database tables are never generated for me. From reading around online I think that this could be down to Database.SetInitializer but I'm not really sure what it should be adjusted to?
Any help to point me in the right direction would be great! I had this working on my home computer but running this project on another PC just isn't working for some reason - typical ;)
Thanks,
Rich
I think you need to use "DataContext" type when you set your initializer:
Database.SetInitializer<DataContext>(new DataContextInitializer())
And when you seed table data, you need to save changes:
protected override void Seed(DataContext context)
{
// create roles
var roles = new List<Role>{
new Role{RoleName = "Administrator"}, // admin area: admin user
new Role{RoleName = "User"}, // admin area: normal user
new Role{RoleName = "Client"} // front end: normal user
};
roles.ForEach(r => context.Roles.Add(r));
context.SaveChanges(); // save changes to db
}
Ok, got this working finally. Thanks to Ladislav Mrnka for the link I altered my global.asax to be:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
var initr = new DataContextInitializer();
Database.SetInitializer<DataContext>(new DataContextInitializer());
using (var db = new DataContext())
{
db.Database.CreateIfNotExists();
}
using (var db1 = new DataContext())
{
initr.Seed(db1);
}
}
i also adjusted my DataContextInitializer using BumbleB2na's suggestion. The finished class looks like this:
public class DataContextInitializer : CreateDatabaseIfNotExists<DataContext>
{
public void Seed(DataContext context)
{
// create roles
var roles = new List<Role>{
new Role{RoleName = "Administrator"}, // admin area: admin user
new Role{RoleName = "User"}, // admin area: normal user
new Role{RoleName = "Client"} // front end: normal user
};
roles.ForEach(r => context.Roles.Add(r));
context.SaveChanges();
}
}
It now works perfectly if I leave Entity Framework create the database for me and not Create one myself before running the code. Hopefully this helps someone else!
Related
In ASP.NET MVC Identity,the relations data for Users and Roles is saved in AspNetUserRoles table, this table has two field:UserId,RoleId, but i want to add other fields to this table, such as department field.
So if an user logins in different departments,he will have different roles.
Anyone knows how to do it? Thanks in advance!
I Would Suggest you investigate ASPNet User Claims. You can assign different claims to a user with the identity manager, and based on the claim type of the user you will allow him access or not. Create a custom Claims Attribute which will be placed on top of the various controller to authenticate the user. this must be implemented based on your needs. the custom attribute will then fire before the controller gets executed and if the uses is allowed he will pass. else return to error page of you choice.
Sample Attribute usage
[ClaimsAuthorize(ClaimsData.EditAddress)]
public ActionResult CitiesPartial()
Attribute Authentication
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _claimType;
public ClaimsAuthorizeAttribute(string type)
{
_claimType = type;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
if (user.HasClaim(_claimType, "True"))
{
base.OnAuthorization(filterContext);
}
else
{
HandleUnauthorizedRequest(filterContext, _claimType + " Not Allowed ");
}
}
protected void HandleUnauthorizedRequest(AuthorizationContext filterContext, string message)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "ClaimNotAuthorized" },
{ "controller", "Home" },
{"errorMessage", message }
});
}
public static bool AuthorizedFor(string claimType)
{
var user = (ClaimsPrincipal)HttpContext.Current.User;
return user.HasClaim(claimType, "True");
}
}
hope this helps.
Im trying nto enable Migration with code first but im having some issues.
Im working on a mvc5 website and I have a dll with the datalayer, dll with the domain classes and then the website itself.
I have already run the website and all the tables have been created. Now I change the name of a single property on one of the domain classes and want to update the db. But instead it tries to create everything again!
I change the property, enter add-migration into the console and then the migration file is created, but it contains CreateTable for all my classes instead of just the change for that one table/class.
I have alredy used the console to enable-migration so that should be in order. It created the Migration folder and the Configuration.cs file in my website project.
Here is how my code looks:
internal sealed class Configuration :DbMigrationsConfiguration<Playground.Web.Models.ApplicationDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(Playground.Web.Models.ApplicationDbContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("PlaygroundContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
When you first configure migrations you need to add a baseline migration. If you have existing tables, you need to tell EF to make a no-op migration:
Add-Migration InitialCreate –IgnoreChanges
Now, future migrations will be incremental based on the baseline. https://msdn.microsoft.com/en-us/data/dn579398.aspx#option1
I am trying to implement a role authorization mechanism which checks the roles of the current logged in user, if the user is in the right role, he/she is allowed, else display error view.
The problem is that when the user tries to access the below method in the controller, he does get into the RoleAuthorizationAttribute class and gets verfied but then the method in the controller is not executed.
Note : the user has the Client role
Controller method
[RoleAuthorization(Roles = "Client, Adminsitrator")]
public ActionResult addToCart(int ProductID, string Quantity)
{
tempShoppingCart t = new tempShoppingCart();
t.ProductID = ProductID;
t.Username = User.Identity.Name;
t.Quantity = Convert.ToInt16(Quantity);
new OrdersService.OrdersClient().addToCart(t);
ViewData["numberOfItemsInShoppingCart"] = new OrdersService.OrdersClient().getNoOfItemsInShoppingCart(User.Identity.Name);
ViewData["totalPriceInSC"] = new OrdersService.OrdersClient().getTotalPriceOfItemsInSC(User.Identity.Name);
return PartialView("quickShoppingCart", "Orders");
}
Role Authentication class
[System.AttributeUsage(System.AttributeTargets.All,AllowMultiple = false, Inherited = true)]
public sealed class RoleAuthorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
List<String> requiredRoles = Roles.Split(Convert.ToChar(",")).ToList();
List<Role> allRoles = new UsersService.UsersClient().GetUserRoles(filterContext.HttpContext.User.Identity.Name).ToList();
bool Match = false;
foreach (String s in requiredRoles)
{
foreach (Role r in allRoles)
{
string rName = r.RoleName.Trim().ToString();
string sName = s.Trim();
if (rName == sName)
{
Match = true;
}
}
}
if (!Match)
{
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
base.OnAuthorization(filterContext);
}
}
Could you please tell me what I am doing wrong
Since I had the roles of the users in the database I had to check against the database so I included this method in the global.asax
protected void Application_AuthenticateRequest(object sender, EventArgs args)
{
if (Context.User != null)
{
IEnumerable<Role> roles = new UsersService.UsersClient().GetUserRoles(
Context.User.Identity.Name);
string[] rolesArray = new string[roles.Count()];
for (int i = 0; i < roles.Count(); i++)
{
rolesArray[i] = roles.ElementAt(i).RoleName;
}
GenericPrincipal gp = new GenericPrincipal(Context.User.Identity, rolesArray);
Context.User = gp;
}
}
Then I could use the normal
[Authorize(Roles = "Client, Administrator")]
On top of the actionResult methods in the controllers
This worked.
Your original code was close, but the problem lies here:
base.OnAuthorization(filterContext);
Unconditionally calling the base class means you are requiring the decorated roles to be found in BOTH the UsersService and the built-in Role provider. If the role provider isn't configured to return the same set of roles (which they wouldn't if the default AuthorizeAttribute isn't sufficient for you) then this will obviously result in the Authorization test always returning false.
Instead you could add a separate property to the derived Attribute such as
public string RemoteRoles { get; set; }
and replace
List<String> requiredRoles = Roles.Split(Convert.ToChar(",")).ToList();
with:
List<String> requiredRoles = RemoteRoles.Split(Convert.ToChar(",")).ToList();
And decorate your controller like such:
[RoleAuthorization (RemoteRoles = "Client, Administrator")]
If you're using MVC 5 you have to enable lazy loading in your DbContext by putting the following line in your DbContext initialisation.
this.Configuration.LazyLoadingEnabled = true;
In MVC 5 default project you'll add it to ApplicationDbContext.cs file.
I'm not sure if this is particular to MVC 5, to Identity 2.0, or affect other versions. I'm using this setup and enabling lazy loading make all the default role schema works. See https://stackoverflow.com/a/20433316/2401947 for more info.
Additionally, if you're using ASP.NET Identity 2.0 default permission schema, you don't have to implement Application_AuthenticateRequest as Darren mentioned. But if you're using custom authorisation tables, then you have to implement it as well.
I want to seed the database with default data but I get an exception thrown.
I added this initialize class in the DAL namespace
public class MyModelInitialise : DropCreateDatabaseIfModelChanges<MyModelContext>
{
protected override void Seed(MyModelContext context)
{
base.Seed(context);
var MyMarks = new List<MyMark>
{
new Mark{ Name="Mark1", Value="250"},
new Mark{ Name="Mark2", Value="350"},
new Mark{ Name="Mark3", Value="450"}
};
Marks.ForEach(bm => context.Marks.Add(bm));
context.SaveChanges();
}
}
I added this initialiser to app startup
protected void Application_Start()
{
Database.SetInitializer<MyModelContext>(new MyModelInitialise());
}
I get the following error while invoking this
Model compatibility cannot be checked because the DbContext instance was not created
using Code First patterns. DbContext instances created from an ObjectContext or using
an EDMX file cannot be checked for compatibility.
Use the IDatabaseInitializer<TContext> interface instead since DropCreateDatabaseIfModelChanges only works with Code-First models:
public class MyModelInitialise : IDatabaseInitializer<MyModelContext>
{
public void InitializeDatabase(MyModelContext context)
{
// Runs everytime so just avoid if the table already has records.
if (context.Marks.Count() == 0)
{
var MyMarks = new List<MyMark>
{
new Mark{ Name="Mark1", Value="250"},
new Mark{ Name="Mark2", Value="350"},
new Mark{ Name="Mark3", Value="450"}
};
Marks.ForEach(bm => context.Marks.Add(bm));
context.SaveChanges();
}
}
}
Leave the Database.SetInitializer exactly the same. You probably should wrap it in a pre-processor directive to ensure it never ends up in production code.
#if DEBUG
Database.SetInitializer<MyModelContext>(new MyModelInitialise());
#endif
Make sure you have public DbSet<MyMark> MyMarks { get; set; } in the MyModelContext class.
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.