ASP.NET MVC User ID Link with other Tables - c#

I am working on a Data Entry system for storing users financial data. Each user will enter his Revenues and Expenses each in a table.
The tables were designed as follows:
Primary Key: Rev/Exp ID
Foreign Key: Organization ID
This is a sample for my models:
public class Revenue
{
[Key]
public int RevenueId { get; set; }
public int Year { get; set; }
public double Source1 { get; set; } = 0;
public double Source2 { get; set; } = 0;
public double Source3 { get; set; } = 0;
public double Source4 { get; set; } = 0;
// Foreign Key Relationship
public string OrganizationId{ get; set; }
public virtual Organization Organization{ get; set; }
}
public class Organization
{
public virtual ICollection<Revenue> Revenues { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
}
This is the DBContext:
public class AppDbContext : IdentityDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
// Create tables in DB
public DbSet<Organization > Organization { get; set; }
public DbSet<Revenue> Revenue { get; set; }
public DbSet<Expense> Expense { get; set; }
}
Here is the Create Action in the Controller:
// GET: Revenue/Create
public IActionResult Create()
{
return View();
}
// POST: Revenue/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("RevenueId,Year,Source1,Source2,...,OrganizationId")] Revenue revenue)
{
if (ModelState.IsValid)
{
_context.Add(revenue);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["OrganizationId"] = new SelectList(_context.OrganizationId, "Id", "Id", revenue.OrganizationId);
return View(revenue);
}
Finally, Create View:
#using Microsoft.AspNetCore.Identity
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Revenue</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Year" class="control-label"></label>
<input asp-for="Year" class="form-control" />
<span asp-validation-for="Year" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Source1" class="control-label"></label>
<input asp-for="Source1" class="form-control" />
<span asp-validation-for="Source1" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Source2" class="control-label"></label>
<input asp-for="Source2" class="form-control" />
<span asp-validation-for="Source2" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Source3" class="control-label"></label>
<input asp-for="Source3" class="form-control" />
<span asp-validation-for="Source3" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Source4" class="control-label"></label>
<input asp-for="Source4" class="form-control" />
<span asp-validation-for="Source4" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="OrganizationId" class="control-label"></label>
<input asp-for="OrganizationId" class="form-control" value="#UserManager.GetUserId(User)"/>
<span asp-validation-for="OrganizationId" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
So, after a lot of search I was able to capture user ID with UserManager and assigning it to a hidden field, then sending it with the form. However, that did not work, the form is not submitting and no error messages are displayed neither.
Is this is a correct way of capturing user ID as a Foreign Key and how to fix the Create Action ?

You didn't really specify anything about your authentication. If you are using typical ASP.Net authentication, you can probably use User.Identity.Name, like this:
if (ModelState.IsValid)
{
revenue.UserId = User.Identity.Name
_context.Add(revenue);
...

As from .NET 6, in order to assign an attribute in a model to be Nullable the ? should be added after the name of the attribute, otherwise it is required.
The problem was that the UserId is passed but the User object is null (which should be because it is just a reference).
So the model should be:
public class Revenue
{
[Key]
public int RevenueId { get; set; }
public int Year { get; set; }
public double Source1 { get; set; } = 0;
public double Source2 { get; set; } = 0;
public double Source3 { get; set; } = 0;
public double Source4 { get; set; } = 0;
// Foreign Key Relationship
public string OrganizationId{ get; set; }
public Organization? Organization{ get; set; }
}
And the view will be as is by passing user ID in a hidden field that we got from UserManager.

Related

How to bind dropdownlist in razor pages for modelstate validation?

I have this form built using Razor Pages in C#. The is the code for create.cshtml. It has a dropdown list.
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="MetaDropdown.Id" class="control-label"></label>
<input asp-for="MetaDropdown.Id" class="form-control" />
<span asp-validation-for="MetaDropdown.Id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="MetaDropdown.MetaFieldId" class="control-label"></label>
<select asp-for="MetaDropdown.MetaFieldId" class="form-control" asp-items="ViewBag.MetaFieldId"></select>
</div>
<div class="form-group">
<label asp-for="MetaDropdown.Value" class="control-label"></label>
<input asp-for="MetaDropdown.Value" class="form-control" />
<span asp-validation-for="MetaDropdown.Value" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="MetaDropdown.active" /> #Html.DisplayNameFor(model => model.MetaDropdown.active)
</label>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
This is the code behind.
public class CreateModel : PageModel
{
private readonly DataContext _context;
public CreateModel(DataContext context)
{
_context = context;
}
public IActionResult OnGet()
{
ViewData["MetaFieldId"] = new SelectList(_context.meta_fields, "Id", "FieldName");
return Page();
}
[BindProperty]
public MetaDropdown MetaDropdown { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.meta_dropdowns.Add(MetaDropdown);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
The issue is when I submit the form, its ModelState.IsValid is false. Upon checking, the error is due to MetaField field is missing. I think this is due to the way I bind the dropdownlist.
This is MetaDropdown model
public class MetaDropdown
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public int MetaFieldId { get; set; }
public string? Value { get; set; }
public bool active { get; set; }
public virtual MetaField MetaField { get; set; }
}
You may use .NET 6/.NET 7. From .NET 6 the non-nullable property must be required, otherwise the ModelState will be invalid.
To achieve your requirement, the first way is you can remove <Nullable>enable</Nullable> from your project file(double-click the project name or right-click the project to choose Edit Project File).
The second way, you can add ? to allow nullable:
public class MetaDropdown
{
public int Id { get; set; }
//other properties....
public virtual MetaField? MetaField{ get; set; } //change here...
}
The third way, you can initialize the model like below:
public class MetaDropdown
{
public int Id { get; set; }
//other properties....
public virtual MetaField MetaField{ get; set; } = new MetaField(); //change here...
}

Foreign Key not showing in dropdown box | MVC

I'm somewhat new to MVC and have been following along with a tutorial but it has no answers regarding my question. For my Create page, the Foreign keys are not showing up. Basically, on the Projects page I created a project, on the People page I created a person. So when I try to create a ProjectRole on the ProjectRoles page, the ProjectId and PersonId are not showing up in the drop-down menu. Down below all of my code, I have provided a screenshot of what I have tried to put into words.
My models:
public class Project
{
public int Id { get; set; }
[Required]
[MaxLength(30)]
public string Name { get; set; }
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime DueDate { get; set; }
public ICollection<ProjectRole> ProjectRoles { get; set; }
}
public class Person
{
public int Id { get; set; }
[Required]
[MaxLength(30)]
public string FirstName { get; set; }
[Required]
[MaxLength(30)]
public string MiddleName { get; set; }
[Required]
[MaxLength(30)]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
public ICollection<ProjectRole> ProjectRoles { get; set; }
}
public class ProjectRole
{
public int Id { get; set; }
[Required]
public double HourlyRate { get; set; }
[ForeignKey("Person")]
public int PersonId { get; set; }
[ForeignKey("Project")]
public int ProjectId { get; set; }
[ForeignKey("AppRole")]
public int RoleId { get; set; }
}
My Controller code:
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,HourlyRate,PersonId,ProjectId,RoleId")] ProjectRole projectRole)
{
if (ModelState.IsValid)
{
_context.Add(projectRole);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(projectRole);
}
And my view code here:
#model Project2.Models.Entities.ProjectRole
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>ProjectRole</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="HourlyRate" class="control-label"></label>
<input asp-for="HourlyRate" class="form-control" />
<span asp-validation-for="HourlyRate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PersonId" class="control-label"></label>
<select asp-for="PersonId" class ="form-control" asp-items="ViewBag.PersonId"></select>
</div>
<div class="form-group">
<label asp-for="ProjectId" class="control-label"></label>
<select asp-for="ProjectId" class ="form-control" asp-items="ViewBag.ProjectId"></select>
</div>
<div class="form-group">
<label asp-for="RoleId" class="control-label"></label>
<input asp-for="RoleId" class="form-control" />
<span asp-validation-for="RoleId" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Screenshot of example of what I mean:
I suppose more personable to display the Person Name (and the Project Name) in the select controls, but to pass the PersonId (and ProjectId) to the Create method when click on the Create button.
Therefore, prepare the Person list (and the Project list) like below:
public IActionResult Create()
{
var persons = new List<SelectListItem>();
// Iterate through `Persons`
foreach(var p in _context.Persons)
{
persons.Add(new SelectListItem() { Value= p.Id, Text = p.FirstName+", "+p.LastName});
}
ViewBag.Persons = persons;
// Prepare the projects list (like `Persons` list above)
// ... your code here
ViewBag.Projects = persons;
return View(new ProjectRole(){ /* Add your code here to create the ProjectRole .../* });
}
And in the view:
<div class="form-group">
<label asp-for="PersonId" class="control-label"></label>
<select asp-for="PersonId" class="form-control" asp-items="ViewBag.Persons"></select>
</div>
<div class="form-group">
<label asp-for="ProjectId" class="control-label"></label>
<select asp-for="ProjectId" class="form-control" asp-items="ViewBag.Projects"></select>
</div>
For additional information see The Select Tag Helper
*NOTE: And I would recommend to create compound view model to include all required information. *
ViewModels or ViewBag?
Understanding Best Way to Use Multiple Models in ASP.NET MVC

MVC - setting up Create.cshtml with related data

I'm quite new to C#, MVC and EF and I've hit a problem I don't seem o be able to resolve.
I'm trying to update the Create.cshtml view so that it shows/lists the itemName rather than itemID where the Item Name is in a different table.
Heres parts of my code so far:
Models:
using System;
using System.Collections.Generic;
namespace CIMSTest.Models
{
public class DirectActivityItem
{
public int ID { get; set; }
public int DirectTypeID { get; set; }
public string ActivityName { get; set; }
public DateTime DateActivityCreated { get; set; }
public bool ActivityLive { get; set; }
public ICollection<DirectActivityGroup> DirectActivityGroups { get; set; }
public DirectType DirectType { get; set; }
}
}
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace CIMSTest.Models
{
public class DirectType
{
public int DirectTypeID { get; set; }
public string DirectTypeName { get; set; }
public bool DirectTypeLive { get; set; }
public ICollection<DirectActivityItem> DirectActivityItems { get; set; }
}
}
Controller (Create):
public IActionResult Create()
{
ViewData["DirectTypeID"] = new SelectList(_context.DirectTypes, "DirectTypeID", "DirectTypeID");
return View();
}
// POST: DirectActivityItems/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,DirectTypeID,ActivityName,DateActivityCreated,ActivityLive")] DirectActivityItem directActivityItem)
{
if (ModelState.IsValid)
{
_context.Add(directActivityItem);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["DirectTypeID"] = new SelectList(_context.DirectTypes, "DirectTypeID", "DirectTypeID", directActivityItem.DirectTypeID);
return View(directActivityItem);
}
Create.cshtml
#model CIMSTest.Models.DirectActivityItem
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>DirectActivityItem</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="DirectTypeID" class="control-label"></label>
<select asp-for="DirectTypeID" class ="form-control" asp-items="ViewBag.DirectTypeID"></select>
</div>
<div class="form-group">
<label asp-for="ActivityName" class="control-label"></label>
<input asp-for="ActivityName" class="form-control" />
<span asp-validation-for="ActivityName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DateActivityCreated" class="control-label"></label>
<input asp-for="DateActivityCreated" class="form-control" />
<span asp-validation-for="DateActivityCreated" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="ActivityLive" /> #Html.DisplayNameFor(model => model.ActivityLive)
</label>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
As you can see the Direct ActivityItem table contains the DirectTypeID, but on the Create page for this I want to list the DirectTypeNames from the DirectType table and not the ID as users won't know what the ID's are.
If anyone can provide any information it would be gratefully received.
Change this:
ViewData["DirectTypeID"] = new SelectList(_context.DirectTypes, "DirectTypeID", "DirectTypeID");
to this:
ViewData["DirectTypeID"] = new SelectList(_context.DirectTypes, "DirectTypeID", "DirectTypeName");
You'll want your view model returning the DirectTypeID since that will be how you best resolve the relationship, but the third parameter tells the SelectList what to display for each selection.

creating category and subcategory dependent list box using asp.net core 2.2

I am trying a create a page where I have a form to create the product. here I trying to creating category and subcategory dependent list box.but not understand how I will do that.
Here is my code:
public class Category
{
public int Id { get; set; }
[Required]
[Display(Name = "Category Name")]
public string CategoryName { get; set; }
}
//my SubCategory model
public class SubCategory
{
public int Id { get; set; }
[Required]
[Display(Name = "SubCategory Name")]
public string SubCategoryName { get; set; }
}
//my product model
public class Product
{
public int Id { get; set; }
[Required]
public String Name { get; set; }
[Required]
[Display(Name = "Category Type")]
public int CategoryTypeId { get; set; }
[ForeignKey("CategoryTypeId")]
public Category Category { get; set; }
[Required]
[Display(Name = "SubCategory Type")]
public int SubCategoryTypeId { get; set; }
[ForeignKey("SubCategoryTypeId")]
public SubCategory SubCategory { get; set; }
}
Product Controller
[HttpGet]
public IActionResult Create()
{
ViewData["CategoryId"] = new SelectList(_db.Category.ToList(), "Id", "CategoryName");
ViewData["SubCategoryId"] = new SelectList(_db.SubCategory.ToList(), "Id", "SubCategoryName");
return View();
}
[HttpPost]
public async Task<IActionResult> Create(Product product)
{
if (ModelState.IsValid)
{
_db.Product.Add(product);
await _db.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(product);
}
Create.cshtml
#model Amazon.Models.Product
#{
ViewData["Title"] = "Create";
}
<br />
<h2 class="text-info">Add New Product</h2>
<form asp-action="Create" method="post" enctype="multipart/form-data">
<div class="p-4 rounded border">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<h3>#ViewBag.message</h3>
<div class="form-group row">
<div class="col-2">
<label asp-for="Name"></label>
</div>
<div class="col-5">
<input asp-for="Name" class="form-control" />
</div>
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group row">
<div class="col-2">
<label asp-for="CategoryTypeId"></label>
</div>
<div class="col-5">
<select asp-for="CategoryTypeId" asp-items="ViewBag.CategoryId" class="form-control"></select>
#*<input asp-for="ProductTypeId" class="form-control" />*#
</div>
<span asp-validation-for="CategoryTypeId" class="text-danger"></span>
</div>
<div class="form-group row">
<div class="col-2">
<label asp-for="SubCategoryTypeId"></label>
</div>
<div class="col-5">
<select asp-for="SubCategoryTypeId" asp-items="ViewBag.SubCategoryId" class="form-control"></select>
#*<input asp-for="SpecialTagId" class="form-control" />*#
</div>
<span asp-validation-for="SubCategoryTypeId" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Save" />
<a asp-action="Index" class="btn btn-success">Back To List</a>
</div>
</div>
</form>
#section Scripts{
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
above code, I did successfully data-bind of my product controller and view.but I trying to creating category and subcategory dependent list box.but not understand how I will do that.
here my expectation output :
I am an absolute beginner. please help anyone.
I trying to creating category and subcategory dependent list box.
To implement cascading dropdown for Category and Subcategory, as I mentioned in comment, you can populate SubCategory dropdown based on the previous selection of Category in Category dropdown change event, like below.
You can try to modify your model class and implement one-to-many relationship between Category and Subcategory entities, so that you can store data like below in database and retrieve all sub-categories based on the provided CategoryId.
Populate only Category dropdown when page loads
<div class="form-group">
<div class="col-2">
<label asp-for="CategoryTypeId" class="control-label"></label>
</div>
<div class="col-5">
<select asp-for="CategoryTypeId" asp-items="ViewBag.CategoryId" class="form-control">
<option value="">Select Category</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-2">
<label asp-for="SubCategoryTypeId" class="control-label"></label>
</div>
<div class="col-5">
<select asp-for="SubCategoryTypeId"></select>
</div>
</div>
Dynamically populate SubCategory dropdown in Category dropdown change event
$(function () {
$("select#CategoryTypeId").change(function () {
var cid = $(this).val();
$("select#SubCategoryTypeId").empty();
$.getJSON(`/Home/GetSubCategory?cid=${cid}`, function (data) {
//console.log(data);
$.each(data, function (i, item) {
$("select#SubCategoryTypeId").append(`<option value="${item.id}">${item.name}</option>`);
});
});
})
});
Action method GetSubCategory
public IActionResult GetSubCategory(int cid)
{
var SubCategory_List=_db.SubCategory.Where(s => s.CategoryId == cid).Select(c => new { Id = c.Id, Name = c.SubCategoryName }).ToList();
return Json(SubCategory_List);
}
Test Result
Update:
Model classes
public class Category
{
public int Id { get; set; }
[Required]
[Display(Name = "Category Name")]
public string CategoryName { get; set; }
public ICollection<SubCategory> SubCategories { get; set; }
}
public class SubCategory
{
public int Id { get; set; }
[Required]
[Display(Name = "SubCategory Name")]
public string SubCategoryName { get; set; }
public int CategoryID { get; set; }
public Category Category { get; set; }
}

How to upload an image into a data base and display it back to the view ASP.NET Core MVC

Data classes EF core
`
public class Manga
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? Updated { get; set; }
public byte[] Image { get; set; }
//Manga can have many chapters
public ICollection<Chapter> Chapters { get; set; }
}
public class Chapter
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? Updated { get; set; }
public byte[] Image { get; set; }
//Chapter can only have manga
public Manga Manga { get; set; }
//The chapter connected to the manga
public int MangaId { get; set; }
}
`I am trying to learn how to upload images and files. Right image is a priority. I want to allow users to upload images and be used publicly. I am using the binary array method. Is it a bad idea? What is better if this is a no go.
While working on this I am getting a model error:
The value 'narutotest.jpg' is not valid for Image
My code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Description,Image")] Manga manga, List<IFormFile> Image)
{
if (ModelState.IsValid)
{
foreach (var item in Image)
{
if(item.Length > 0)
{
using (var stream = new MemoryStream())
{
await item.CopyToAsync(stream);
manga.Image = stream.ToArray();
}
}
}
manga.DateCreated = DateTime.Now;
manga.Updated = DateTime.Now;
_context.Add(manga);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(manga);
}
View:
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Image" class="control-label"></label>
<input asp-for="Image" type="file" id="files" />
<span asp-validation-for="Image" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
You should provide more details such as Manga struct . But there are two things you should firstly notice . Firstly , in order to support file uploads, HTML forms must specify an enctype of multipart/form-data . The second thing is you should move _context.Add(manga) into the foreach file loop , loop the files , read to stream , save to Manga class , add/trace into context and at last call context.SaveChangesAsync() to save into database outside of the loop .

Categories

Resources