I am trying to restrict user entries so that only that specific user is able to see their entries and not anyone else. In other words, after all I've done, my application still displays every entry that was ever entered; and any user is able to see the entries.
I've created a one-to-many relationship by referencing the foreign key from my Expenses table to the primary key of my AspNetUsers using the Code First convention in Entity Framework, however, when I log in as different users, I am still able to see entries (expenses) that other users have entered.
I'm not sure whether the problem lies in my view, model, or controller.
Here is the code I currently have:
IdentityModel:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
Expenses = new List<Expense>();
}
[Required]
public string Fullname { get; set; }
[Required]
public string Province { get; set; }
[Required]
public string Company { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
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("PacificPetEntities", throwIfV1Schema: false)
{
}
public IDbSet<Expense> Expenses { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
Expense Model:
public class Expense : IValidatableObject
{
public Expense() { }
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public string Category { get; set; }
public string Description { get; set; }
[Required]
[Display(Name = "Gross Amount")]
public double GrossAmount { get; set; }
[Required]
[Display(Name = "Tax Amount")]
public double TaxAmount { get; set; }
[Required]
[Display(Name = "Net Amount")]
public double NetAmount { get; set; }
public int Mileage { get; set; }
[Display(Name = "Mileage Rate")]
public double MileageRate { get; set; }
[Required]
[Display(Name = "Date Submitted")]
public DateTime? DateSubmitted { get; set; }
[Required]
[Display(Name = "Expense Date")]
public DateTime? ExpenseDate { get; set; }
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual ApplicationUser ApplicationUser { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Category == "Auto - Mileage" && Mileage == 0)
{
yield return new ValidationResult("You must enter a mileage amount if the chosen category is mileage.");
}
}
}
Controller:
public class ExpensesController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Expenses
[Authorize]
public ActionResult Index()
{
var expenses = db.Expenses.Include(e => e.ApplicationUser);
return View(expenses.ToList());
}
// GET: Expenses/Details/5
[Authorize]
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Expense expense = db.Expenses.Find(id);
if (expense == null)
{
return HttpNotFound();
}
return View(expense);
}
// GET: Expenses/Create
[Authorize]
public ActionResult Create()
{
ViewBag.UserId = new SelectList(db.Users, "Id", "Fullname");
return View();
}
// POST: Expenses/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult Create([Bind(Include = "ID,Category,Description,GrossAmount,TaxAmount,NetAmount,Mileage,MileageRate,DateSubmitted,ExpenseDate,UserId")] Expense expense)
{
if (ModelState.IsValid)
{
db.Expenses.Add(expense);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UserId = new SelectList(db.Users, "Id", "Fullname", expense.UserId);
return View(expense);
}
// GET: Expenses/Edit/5
[Authorize]
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Expense expense = db.Expenses.Find(id);
if (expense == null)
{
return HttpNotFound();
}
ViewBag.UserId = new SelectList(db.Users, "Id", "Fullname", expense.UserId);
return View(expense);
}
// POST: Expenses/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult Edit([Bind(Include = "ID,Category,Description,GrossAmount,TaxAmount,NetAmount,Mileage,MileageRate,DateSubmitted,ExpenseDate,UserId")] Expense expense)
{
if (ModelState.IsValid)
{
db.Entry(expense).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UserId = new SelectList(db.Users, "Id", "Fullname", expense.UserId);
return View(expense);
}
// GET: Expenses/Delete/5
[Authorize]
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Expense expense = db.Expenses.Find(id);
if (expense == null)
{
return HttpNotFound();
}
return View(expense);
}
// POST: Expenses/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult DeleteConfirmed(int id)
{
Expense expense = db.Expenses.Find(id);
db.Expenses.Remove(expense);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
Index.cshtml:
@model IEnumerable<PacificPetExpenses.Models.Expense>
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.ApplicationUser.Fullname)
</th>
<th>
@Html.DisplayNameFor(model => model.Category)
</th>
<th>
@Html.DisplayNameFor(model => model.Description)
</th>
<th>
@Html.DisplayNameFor(model => model.GrossAmount)
</th>
<th>
@Html.DisplayNameFor(model => model.TaxAmount)
</th>
<th>
@Html.DisplayNameFor(model => model.NetAmount)
</th>
<th>
@Html.DisplayNameFor(model => model.Mileage)
</th>
<th>
@Html.DisplayNameFor(model => model.MileageRate)
</th>
<th>
@Html.DisplayNameFor(model => model.DateSubmitted)
</th>
<th>
@Html.DisplayNameFor(model => model.ExpenseDate)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.ApplicationUser.Fullname)
</td>
<td>
@Html.DisplayFor(modelItem => item.Category)
</td>
<td>
@Html.DisplayFor(modelItem => item.Description)
</td>
<td>
@Html.DisplayFor(modelItem => item.GrossAmount)
</td>
<td>
@Html.DisplayFor(modelItem => item.TaxAmount)
</td>
<td>
@Html.DisplayFor(modelItem => item.NetAmount)
</td>
<td>
@Html.DisplayFor(modelItem => item.Mileage)
</td>
<td>
@Html.DisplayFor(modelItem => item.MileageRate)
</td>
<td>
@Html.DisplayFor(modelItem => item.DateSubmitted)
</td>
<td>
@Html.DisplayFor(modelItem => item.ExpenseDate)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
Please help.
Thank you.
Select your
Expenses
with the current user id. Like this.I have found the answer. Padhraic was really close, but his answer helped me solve my problem.
In my controller, I had:
Instead, this should be:
According to Stephen Muecke's comment on my question, db.Expenses.Include(e => e.ApplicationUser) was returning all rows in my database. Instead I needed to filter the results to the current user.