I am creating an employee scheduling site in ASP.net MVC 6. I have an employee table, shift table and a shiftEmployee table to handle the many to many relationship.
It's configured so that each employee logs into the site using their employee ID number and a password. Then they can see each future shift they are scheduled to. They must acknowledge each assigned shift in a process known as "pulling their pin".
So far everything is working as expected. My goal and my question is this:
When an employee pulls their pin for each shift, I would like them to have to confirm this action by entering their password again, keeping in mind the user is already signed into the site. What is the easiest/correct/most secure way to accomplish this?
The Pull GET/POST methods are basically the same as a standard MVC edit action, simply renamed Pull.
// GET: PullPin/Pull/5
public IActionResult Pull(int? id)
{
if (id == null)
{
return HttpNotFound();
}
var shiftEmp = _context.ShiftEmployees.Single(m => m.ShiftEmployeeID == id);
if (shiftEmployee == null)
{
return HttpNotFound();
}
}
// POST: PullPin/Pull/5
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Pull(ShiftEmployee shiftEmployee)
{
var user = GetCurrentUserAsync();
pullPin.PinStatusID = 3; // Status ID #3 = Pulled
if (ModelState.IsValid)
{
_context.Update(shiftEmployee);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(shiftEmployee);
}
And here is my ShiftEmployee class
public class ShiftEmployee
{
public int ShiftEmployeeID { get; set; }
public int ShiftID { get; set; }
public int EmployeeID { get; set; }
public int PinStatusID { get; set; }
public virtual Shift Shift { get; set; }
[JsonIgnore]
public virtual Employee Employee { get; set; }
public virtual PinStatus PinStatus { get; set; }
}
In the standard MVC6 template, it uses ASP.NET Core Identity for the login functionality. Part of that package is the UserManager object (you also get a SignInManager among other things.)
The UserManager object has a method specifically for checking passwords called CheckPasswordAsync and is used like this:
_userManager.CheckPasswordAsync(user, password)
Related
I'm developing an API on .Net Core 6 and i'm having a little confusing about all the requests im doing to the database.
For example i have a Company class and Employee Class
public class Company
{
public int Id { get; set; }
public string name { get; set; }
public List<Employees> employees { get; set; }
public User CreatedBy { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Company Company { get; set; }
public bool IsAdmin { get; set; }
public User user { get; set; } // This is the IdentityUser
}
I have this JSON object i send to api to add an employe
{
"name": "First Last",
"userId": "1E715D09-D1A4-4853-960B-08D9DED34E35",
"isAdmin": false
}
A user can add a company and immediately is inserted as an employee (Admin employee) because he created that Company. Another thing is that, to be able to add another employee, the Employee (User logged in) needs to be an admin on that company.
I have this endpoint for adding employees: api/company/{companyId}/employees -> POST
and this is the logic i have to add the employee
// Verifies that the company sent is a valid one
var company = await _repository.Company.GetCompany(companyId, trackChanges);
if (company == null)
throw new NotFoundException("Company could not be found");
// Verifies that the user (getting the userId from the JWT) logged in is an employee in that company so he can add a new employee
var userEmployee = await _repository.Employee.GetEmployeeByUserId(userId, companyId, trackChanges);
if (userEmployee == null)
throw new NotFoundException("Employee was not found");
// Validates the user object fetched that he's an admin
if (userEmployee.IsAdmin)
{
// Validates that the sent user Id in the DTO is valid to add
var userToAdd = GetUserById(employeeCreationDto.userId, trackChanges);
if (userToAdd == null)
throw new NotFoundException("User trying to be added as emmployee is not found");
// Verify that the userId from the DTO doesn't exists in the DB already
var userToAddAsEmployee = await _repository.Employee.GetEmployeeByUserId(employeeCreationDto.userId, companyId, trackChanges);
if (userToAddAsEmployee != null)
throw new ConflictException("User is already an employee in this company");
//Proceed to add employee logic
}
Now my question is, should i do all of this Database calls to validate every single step or should i try to add and if a foreign key error returns from the db just handle that error as response
I just think i am doing to many calls and almost all of my endpoints have lot's of validations calling the db. Is it okay all of those calls?
I have an Employee class, taken from a database. In that Employee class, I want to add a Manager object, which is also an Employee, based on the managerCode that is in the Employee database. See below for the class.
The managerCode is not defined as a key, . I don’t need recursion, i.e. I don’t need the Manager’s manager, etc. Just one level, the Employee and his manager.
Using .NET 4.5, c#, OData v4
I am using OData to send back the Employee, but the Manager part isn’t added in the response, even if it’s there in the object I try to send.
What am I missing? Something in the WebApiConfig?
Thanks
Employee class, first 4 fields are directly taken from database.
Class Employee
{
[Key]
public int id { get; set; }
public string employeeCode { get; set; }
public string employeeName { get; set; }
public string managerCode { get; set; }
[NotMapped]
public Employee Manager { get; set; }
}
Controller class. GetEmployeeById(id) will get Employee(s) with their Manager.
[HttpGet]
[EnableQuery]
[ODataRoute("employeeById(id={id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
WebApiConfig
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("ODataRoute", "odata",GetEdmModel(),
new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.Namespace = "employee_odata";
builder.ContainerName = "employee_odataContainer";
builder.EntitySet<Employee>("Employee");
builder.EnableLowerCamelCase();
var unboundGetEmployee = builder.Function("employeeById");
unboundGetEmployee.Returns<Employee>();
unboundGetEmployee.Parameter<int>("id");
unboundGetEmployee.Namespace = "employee_odata.Functions";
return builder.GetEdmModel();
}
SOLUTION
Remove unboundGetEmployee from WebApiConfig (not needed).
Make Manager item in Employee class virtual, without the [NotMapped]:
public virtual Manager Manager { get; set; }
Controller:
[EnableQuery]
[ODataRoute("Employee({id})")]
public IHttpActionResult employeeById([FromODataUri]int id)
{
//handle data return normally...
//I have to detect $expand=Manager, to fetch the actual Manager object.
//otherwise it's null (not linked with primary key)
}
With these fee changes, $expand is working well.
You need to add $expand to show navigation property, like
localhost\odata\employee_odata.Functions.employeeById(id=1)?$expand=Manager
And for get employee by Id, I suggest you to use this method in controller:
[EnableQuery]
public IHttpActionResult Get(int id)
{
var sets = dbContext.GetEmployeeById(id);
if (!sets.Any())
return NotFound();
return this.CreateOKHttpActionResult(sets);
}
Then request like localhost\odata\Employee(1) can route to that method.
I am currently building an ASP.NET MVC application that allows admin users to add, edit and delete users. For the application, I have 3 different database tables called AdminUsers, Users and AuditChanges. Every time a user is added, edited or deleted, I would like it to update the Users table as necessary, which isn't a problem. On top of this, I would also like the application write an entry into the AuditChanges table which states which admin it was that processed the function, which user was updated and also what type of function was processed e.g. an edit or delete. What I am currently struggling with is updating more than one database and was wondering if this was possible?
For Example:
If I was to access my Create User page and add a user I would like my User table to look like this:
UserID BranchNumber
U123456 1234
But then I would also like my AuditChanges table to look like this:
AdminID UserID BranchNumber Type
U654321 U123456 1234 Add
U235874 U192395 4321 Edit
U827734 U283849 9999 Delete
This is so I am able to keep track of who has been updating the Users table and what they have been updating it with.
Controller
public ActionResult Edit(int id)
{
Users user = db.Users.Find(id);
return View(user);
}
[HttpPost]
public ActionResult Edit(Users user)
{
if (ModelState.IsValid)
{
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Users user, AuditChanges changes)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
public ActionResult Delete(int id)
{
Users user = db.Users.Find(id);
return View(user);
}
//
// POST: /Errors/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Users user = db.Users.Find(id);
db.Users.Remove(user);
db.SaveChanges();
return RedirectToAction("Index");
}
Models
[Table("CTP_Users")]
public class Users
{
[Key]
public int Id { get; set; }
public string LogonId { get; set; }
public string BranchNumber { get; set; }
}
[Table("CTP_Users")]
public class Users
{
[Key]
public int Id { get; set; }
public string LogonId { get; set; }
public string BranchNumber { get; set; }
}
If you require anymore information then let me know.
Thanks in advance.
For simplicity and testing, i'm doing this from a new project and then apply it to my actual project once i understand the problem.
I made a model, named Person, which contains a List property, named ServiceNeeded. Users in the front end may encode as much string of services as they wish, so the input field for ServiceNeeded is dynamically created. In the POST method, those string input binds as expected. I save the Person object into the database, and works as expected. When I try to retrieve the people objects from the database, all but ServicesNeeded are present.
Here are my codes
Model (Person.cs):
public class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public List<String> ServiceNeeded { get; set; }
public Person()
{
this.ServiceNeeded = new List<String>();
}
}
Controller(Index and [POST]Create methods):
public ActionResult Index()
{
var x = db.People.ToList();
return View(db.People.ToList());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person)
{
if (ModelState.IsValid)
{
person.Id = Guid.NewGuid();
db.People.Add(person);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
As I said the input fields for ServicesNeeded are dynamically create but properly bind to the model.
Here are some screenshots during runtime:
After user do some input:
The I added a variable before Index returns the View to check in runtime:
As seen, the same Person is present and everything but ServicesNeeded. I am fairly new with ASP.NET MVC and web development in general. I do handle other collections in my project, although are collection of objects. Only this particular case I do not understand. Where could my error be?
You need to tell the dbcontext to load the references if lazy loading is not enabled.
First change ServiceNeeded to a virtual property
public virtual List<String> ServiceNeeded { get; set; }
then load the property when you pull down the person by using Include
var x = db.People.Include("ServiceNeeded").ToList();
return View(x);
Article on loading related entities https://msdn.microsoft.com/en-us/data/jj574232.aspx
Look in the database; are the records there? I think the issue is that the ServicesNeeded collection is not persisted to the database; try adding:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person)
{
if (ModelState.IsValid)
{
person.Id = Guid.NewGuid();
db.People.Add(person);
foreach (var service in person.ServicesNeeded)
db.ServicesNeeded.Add(service);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
I'm don't believe the relationships attached to the Person object auto-persist on SaveChanges(), and are probably not making it to the database.
I think the best idea that suits Entity Frame work is to create another Model for your 'ServiceNeeded', add it to your 'DbContext', save any services in the corresponding DbSet and retrieve them using .Include in LINQ. After this lengthy introduction look at the following codes:
In your Models:
public class MyService{
public Guid Id {get;set;}
public string ServiceName {get;set;}
public Guid PersonId {get;set;}
}
public class Person
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual IList<MyService> ServiceNeeded { get; set; }
}
In your ApplicationDbContext:
public System.Data.Entity.DbSet<MyService> MyServices { get; set; }
public System.Data.Entity.DbSet<Person> People { get; set; }
In your POST ActionResult:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,ServiceNeeded")] Person person, List<String> ServiceNeeded)
{
if (ModelState.IsValid)
{
db.People.Add(person);
db.SaveChanges();
db.Entry(person).GetDatabaseValues();
foreach (string service in ServiceNeeded){
db.MyServices.Add( new MyService {ServiceName = service,
PersonId = person.Id})
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
Please note that I removed person.Id = Guid.NewGuid(); so that the Id
be generated by Database Identity and then I re-get the person.Id so that I create the MyService record.
and finally as #JamieD77 suggested, in your GET ActionResult:
public ActionResult Index()
{
var x = db.People.Include("ServiceNeeded").ToList();
return View(x);
}
I want to have a drop down that won't require to to query the database in the controller POST section in order to get the ID for the drop down selection so that it can be placed in table as a foreign key. I don't understand how it could be down without needing to make that query. I want entity framework to do the heavy lifting for it if that makes sense? Is this possible?
public class BillRate
{
public BillRate()
{
this.BillRateTickets = new List<Ticket>();
}
public long BillRateID { get; set; }
public decimal TicketBillRate { get; set; }
public virtual ICollection<Ticket> BillRateTickets { get; set; }
}
public class Ticket
{
public long TicketID { get; set; }
public virtual BillRate BillRate { get; set; }
}
It's not clear what exactly do you mean. If you do not query your database where where do you think the items to be displayed on the dropdown list will come from? They will definetely won't come from the view because in HTML when you submit a form containing a <select> element, only the selected value is ever sent to the server. The collection values are never sent, so ASP.NET MVC cannot invent those values for you.
If you want to avoid hitting your database you could store this list into the cache and inside your POST action try looking for the values in the cache first. But those values must be persisted somewhere on your server. So you could have a method which will look for the values in the cache first and if not found query the database:
private IEnumerable<Ticket> GetTickets()
{
// Try to get the tickets from the cache first:
var tickets = MemoryCache.Default["tickets"] as IEnumerable<Ticket>;
if (tickets == null)
{
// not found in cache, let's fetch them from the database:
tickets = db.Tickets.ToList();
// and now store them into the cache so that next time they will be available
MemoryCache.Default.Add("tickets", tickets, new CacheItemPolicy { Priority = CacheItemPriority.NotRemovable });
}
return tickets;
}
and then you could have the 2 controller actions:
public ActionResult Index()
{
var model = new BillRate();
model.BillRateTickets = GetTickets();
return View(model);
}
[HttpPost]
public ActionResult Index(BillRate model)
{
model.BillRateTickets = GetTickets();
return View(model);
}