What is wrong with my Entity Framework and architecture? - asp.net-mvc

I am developing a task tracker web app and you could see the full code here: https://github.com/KimSergey94/TaskTracker
In short, admin, manager, client and employee are users of the app and admin is the boss of these roles. To illustrate, a manager can create a task received by a client and assign it to an employee. The task includes Statuses and Statuses includes Comments. The "roles" have user id as a foreign key to the User table that stores their email addresses and passwords. The Role table stores user id so that they have their roles right.
I need to develop basic functionality, make use some of AJAX, custom filters, stored procedures. There is something wrong with my authorization and roles logic. So, I would appreciate if you take a look and inform me about anything that you feel is not right.
Currently, when I am trying to launch the app and initialise the database, I get the following error:
System.Data.SqlClient.SqlException: 'Introducing FOREIGN KEY constraint 'FK_dbo.Employees_dbo.Users_UserId' on table 'Employees' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
User and Employee classes code:
public class User
{
public int UserId { get; set; }
[DataType(DataType.EmailAddress)]
[Required]
public string Email { get; set; }
public string Password { get; set; }
public int RoleId { get; set; }
}
public class Employee
{
public int EmployeeId { get; set; }
//[Required(ErrorMessage = "First Name is required")]
public string FirstName { get; set; }
//[Required(ErrorMessage = "Last Name is required")]
public string LastName { get; set; }
//[Required(ErrorMessage = "Country field is required")]
public string Country { get; set; }
public string Position { get; set; }
public int Salary { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
//public virtual ICollection<Task> Tasks { get; set; }
}
Globals.asax
protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer<TaskTrackerContext>(new TaskTrackerDbInitializer());
var db = new TaskTrackerContext("TaskTrackerContext");
db.Database.Initialize(true);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ModelValidatorProviders.Providers.Clear();
NinjectModule orderModule = new OrderModule();
NinjectModule serviceModule = new ServiceModule("TaskTrackerDb");
var kernel = new StandardKernel(orderModule, serviceModule);
DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel));
}
web.config:
<connectionStrings>
<add name="TaskTrackerContext"
connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename='|DataDirectory|\TaskTrackerContext.mdf';MultipleActiveResultSets=True; Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<!--
Initial Catalog=FormsAuth;
</appSettings>
By the way why do I need this Initial Catalog=FormsAuth;?
I noticed that with this parameter I could not instantiate database.
I want to get Include functionality for my models

In the EF Core, You should disable cascade delete with DeleteBehavior.Restrict or DeleteBehavior.SetNull, e.g. in the database context class, enter a new method
protected override void OnModelCreating(ModelBuilder modelBuilder){
modelBuilder.HasOne(x => x.Employee).WithMany().HasForeignKey(x =>
x.UserId).OnDelete(DeleteBehavior.Restrict)
}
If you want cascade behavior, you need to add a nullable integer to the UserId:
public int? UserId { get; set; }
Initial Catalog=FormsAuth; is from the System.Web.Security namespace and is used for form validations, you can read more about it on the Microsoft Docs: https://learn.microsoft.com/en-us/dotnet/api/system.web.security.formsauthentication?view=netframework-4.8
I recommend using asp.net identity, I can see a lot of what you are doing is re-inventing the wheel, whereas asp.net does the authentication and validation for you. You can read about it here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-3.0&tabs=visual-studio

I had a table that had a circular relationship with others and I was getting the same error. Turns out it is about the foreign key which was not nullable. If the key is not nullable related object must be deleted and circular relations doesn't allow that. So use a nullable foreign key.
public int? UserId { get; }
[ForeignKey("UserId")]
public virtual User User { get; set; }

Related

use multiple dbContext in one application

I am struggling with using multiple dbContext with an single web application in ASP.NET MVC 5. I am following code First existing database design approach.
i need guideline how to do that say in example if i am creating 5 models using ADO.NET, it will create 5 dbContext along with its model classes.
how it will change in web.config file?
Many Thanks
public partial class DefaultContext : DbContext
{
public DefaultContext()
: base("name=DefaultContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<AspNetRole> AspNetRoles { get; set; }
public virtual DbSet<sys_Actions> sys_Actions { get; set; }
public virtual DbSet<sys_ActionsInRole> sys_ActionsInRole { get; set; }
public virtual DbSet<sys_Controllers> sys_Controllers { get; set; }
public virtual DbSet<sys_Functions> sys_Functions { get; set; }
public virtual DbSet<sys_FunctionsHierarchy> sys_FunctionsHierarchy { get; set; }
}
basically for each dbContext you need to add a new connection string with unique name in the connectionStrings section in your web.config file
here is an example:
<connectionStrings>
<add name="dbContext1" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="data source=dbServer;initial catalog=db1;integrated security = true;multipleactiveresultsets=True;application name=EntityFramework"" providerName="System.Data.EntityClient" />
<add name="dbContext2" connectionString="metadata=res://*/Model2.csdl|res://*/Model2.ssdl|res://*/Model2.msl;provider=System.Data.SqlClient;provider connection string="data source=dbServer;initial catalog=db1;integrated security = true;multipleactiveresultsets=True;application name=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>

MVC4 role based controller's action access

I want to build a Registration system where, while adding an user u can select the type of role you can give to him/her. And depending upon the role he/she it would be decided whether certain action in controller can be accessed or not.
For example, let's say there are two roles, admin and developer.
And having something like mentioned below would only allow user with roles as admin to acces following action.
[Authorize(Roles = "admin"]
public ActionResult CreateUser()
{
return View();
}
As far as I know I have to implement my custom RoleProvider or IPrincipal?
I tried to find some example on that but didn't quite get what i'm exactly looking for.
Here is how my RegisterModel currently looks like
public class RegisterModel
{
[Key]
public Guid Id;
[Required]
[Display(Name="First Name")]
public string FirstName {get; set;}
[Required]
[Display(Name="Last Name")]
public string LastName {get; set;}
[Required]
[Display(Name="Email Id")]
[DataType(DataType.EmailAddress)]
public string EmailId {get; set;}
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[Display(Name = "Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Required]
[Display(Name = "Confirm Password")]
[DataType(DataType.Password)]
public string ConfirmPassword { get; set; }
[Required]
[Display(Name = "Role")]
public UserRole Role { get; set; }
}
public class UserRole
{
[Key]
public int RoleId { get; set; }
public string RoleName { get; set; }
}
Thing is I want the role to be decided when adding a user and use the Custom Authorize attribute. Any article or blog that anyone knows that can solve my issue? Or any suggestions, how to do it?
Recently i implemented Role authorization without using Memberhip provider. Thought this might help you.I have a database table with UserName, Password and a Role and i needed to check the role against the database.
Below is my custom RoleFilter class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplicationrazor.Models.ActionFilters
{
public class RoleFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (GetCurrentUserRole() != "Admin")// Check the Role Against the database Value
{
filterContext.Result = new RedirectResult("~/Redirect/NoPermission");
return;
}
}
}
}
Controller:
[RoleFilter]//Check the Role, if not allowed redirect to NoPermission view
public ActionResult Index()
{
return View();
}
MVC 4 uses some helper classes from WebMatrix to implement Security and Memberhip. You can read a very nice tutorial here:
http://www.asp.net/web-pages/tutorials/security/16-adding-security-and-membership
If you don't have any special requirements, it's usually not worth it to come up with your own implementation of a Role Provider.
Good Luck!
EDIT: A QUICK TUTORIAL
The following is based on a Model class called "UserProfile" with a corresponding table named the same. This table has a column called "UserId" for the id and one called "UserName" for login. Of course it can have all the info you need, but these are the only ones needed by the WebSecurity to initialize the DB.
Step 1: the web.config. Put this in the system.web section. This instructs ASP.NET to use the two Simple providers for Role and Membership:
<roleManager enabled="true" defaultProvider="simple">
<providers>
<clear/>
<add name="simple" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
</providers>
</roleManager>
<membership defaultProvider="simple">
<providers>
<clear/>
<add name="simple" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData"/>
</providers>
</membership>
Step 2: Application_Start. Add the initialization for your DB for roles and membership tables:
protected void Application_Start()
{
try
{
// Initializes the DB, using the "DefaultConnection" connection string from the web.config,
// the "UserProfile" table, the "UserId" as the column for the ID,
// the "UserName" as the column for usernames and will create the tables if they don't exists.
// Check the docs for this. Basically the table you specify
// is a table that already exists and where you already save your user information.
// WebSecurity will simply link this to its own security info.
if (!WebSecurity.Initialized)
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Cannot init ASP.NET Simple Membership database", ex);
}
}
When the InitializeDatabaseConnection fires for the first time, it will create 4 tables:
webpages_Membership
webpages_OAuthMembership
webpages_Roles
webpages_UsersInRoles
Step 3: You can now use the Authorize attribute:
[Authorize(Roles="Admin")]
Also, you will now have a lot of methods to create and login your users:
WebSecurity.CreateUserAndAccount(model.UserName, model.Password); // to create users. You can also pass extra properties as anonymous objects
WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe); // for logins
WebSecurity.Logout();
WebSecurity.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword);
// and so on...
I find this approach to be a lot more flexible (and quicker) than rolling your own implementation.

Error in webconfig file

I am new to MVC 4.I am working on a tutorial which i found on browsing the internet and I am trying to add a model to my application.I have created one and when i try to add a controller it gives me a error message like..
Unable to retrive metadata for 'Practice.Models.Customer'.Invalid value for Key 'attachdbfilename'.
MODEL:
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public int Amount { get; set;}
}
public class CustomerDBContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
}
Connection string:
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=aspnet-practice-20130320183458;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
<add name="CustomerDBContext"
connectionString="Data Source=(LocalDB)\\v11.0;AttachDbFilename=|DataDirectory|\Customers.mdf;Integrated Security=SSPI"
providerName="System.Data.SqlClient" />
I think there is some problem with ConnectionString but dont know where is it.Please help me solve this problem..Thanks in advance
I think you have to make your enity virtual.
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public virtual int Amount { get; set;}

MVC 3 - DB Context - Web.Config

How can I share my DBcontext in my web.config without creating multiple connections?
WEB.config:
<configuration>
<connectionStrings>
<add name="daC_Companies" connectionString="Data Source=10.0.2.100;Initial Catalog=XXXXX;User ID=XXXXXXXXXx;Password=XXXXXXXX;Persist Security Info=False" providerName="System.Data.SqlClient" />
</connectionStrings>
Data Access:
public class daC_Companies : DbContext
{
public DbSet<ClassLibrary.Companies.C_Companies> dbsetC_Companies { get; set; }
}
And then I have a class called C_Companies referenced above. It works fine but I don't want to have a new DBContext for every class I want to access.
The DbContext should be specific to the database, not to the object. To add a reference to more db tables, add them as properties into the daC_Companies object:
public class daC_Companies : DbContext
{
public DbSet<ClassLibrary.Companies.C_Companies> dbsetC_Companies { get; set; }
public DbSet<ClassLibrary.Companies.Object2> Object2s { get; set; }
public DbSet<ClassLibrary.Companies.Object3> Object3s { get; set; }
public DbSet<ClassLibrary.Companies.Object4> Object4s { get; set; }
}

How can I prevent my database from being recreated without seeding?

I would like to prevent my program from dropping the database each time I run the debugger during the developing stage. I want to do this without the usual seeding the database. Since I will be using the Import and Export Data wizard, I would like to use this method of populating my database.
Is there a method to prevent the program from dropping the database?
Here's more information that I hope will help:
My Initializer
DropCreateDatabaseIfModelChanges<SchoolInDB>
{
protected override void Seed(SchoolInDB context)
{
context.Parents.Add(new Parent { Name = "Mary Lawson", Phone = "949-999-9999", Notes = "Please see IEP " });
base.Seed(context);
}
}
My application start
protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseAlways<SchoolIn.Models.SchoolInDB>());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
My DBContext
public class SchoolInDB : DbContext
{
public DbSet<Course> Courses { get; set; }
public DbSet<Teacher> Teachers { get; set; }
public DbSet<Parent> Parents { get; set; }
public DbSet<PdfReport> PdfReports { get; set; }
public DbSet<CourseProgress> CourseProgresses { get; set; }
public DbSet<ParentContact> ParentContacts { get; set; }
public DbSet<RedAlert> RedAlerts { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Assignment> Assignments { get; set; }
}
My connection string
<connectionStrings>
<add name="ApplicationServices"
connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
providerName="System.Data.SqlClient" />
I guess I want to avoid using the SetInitializer to DropCreateDatabaseAlways and just load the database whether the models changes or not. I'll be using the Import and Export Wizard to populate the database.
Thanks for helping!
I guess I want to avoid using the SetInitializer to DropCreateDatabaseAlways and just load the database whether the models changes or not. I'll be using the Import and Export Wizard to populate the database
So don't call SetInitializer. Just use the connection string to open the DB. BTW, DropCreateDatabaseIfModelChanges as the name implies, only drops/creates the DB when the schema changes.
That's how traditional DB first works.
To elaborate on RickAnd's answer, for future searchers looking for an answer.
I assume your full initializer class declaration is ..
public class ApplicationDbInitializer : DropCreateDatabaseAlways<SchoolInDB>
You simply need to replace this with...
public class ApplicationDbInitializer : CreateDatabaseIfNotExists<SchoolInDB>
That way your database will still be initialised with your seed values, but won't be dropped each time you start the application.

Resources