How to bind data from different SQL tables using dropdown? - c#

I am trying to create an ASP.NET Core 3.1 MVC web app using identity. The app has users and admin. The admin can create new tasks and assign them to the users.
I get the list of users and tasks in 2 different dropdowns as follows:
View: Assign.cshtml
#model TaskManager2.ViewModels.AssignViewModel
#{
ViewData["Title"] = "Assign";
}
<h1>Assign</h1>
<h4>Assign</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Assign">
<div class="form-group">
#Html.DropDownListFor(t => t.TaskId,
Model.Tasks, "--Select task--")
</div>
<div class="form-group">
#Html.DropDownListFor(u => u.Id,
Model.Users, "--Select user--")
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
AdminController:
private readonly ApplicationDbContext _context;
public AdminController(ApplicationDbContext context)
{
_context = context;
}
public IActionResult Index()
{
return View();
}
public IActionResult Assign()
{
var users = (from u in _context.Users select new SelectListItem { Value = u.Id, Text = u.FirstName }).ToList();
var tasks = (from t in _context.Task select new SelectListItem { Value = t.TaskId.ToString(), Text = t.Description }).ToList();
var user = _context.Users.FirstOrDefault();
return View(new AssignViewModel { Users = users, Tasks = tasks });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Assign([Bind("TaskId, Id")] Assign assign)
{
if (ModelState.IsValid)
{
_context.Add(assign);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(assign);
}
ViewModel :
public class AssignViewModel
{
public IList<SelectListItem> Tasks;
public IList<SelectListItem> Users;
//public long SelectedUserId { get; set; }
//public long SelectedTaskId { get; set; }
//Added these lines instead
public Task TaskId { get; set; }
public IdentityUser Id { get; set; }
}
Model: Assign.cs
public partial class Assign
{
public long AssignId { get; set; }
public long TaskId { get; set; }
//[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
}
I got an error that is asked previously on stackoverflow: here and I tried the solution. I added the commented line on Assign.cs but the result that I get is not the one that I want. The idea is to save the Id of the user from AspNetUser and taskId from Task table in a third table called Assign. Here's how it looks like:
I am new to this, so I don't really understand how it works. I would really appreciate your help! Thank you!
Edit
I made the changes to the code above and also set Identity Specification to Yes for the primary key. It works and records the user Id and the corresponding taskId to the database. Now I'm trying to create something like:
#if ((bool)ViewData["HasError"]) //not working
{ <div class="alert alert-danger" role="alert">
Please select!
</div>
}
This will give an alert if the user doesn't select any of the options instead of throwing this error:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'TaskManager2.Models.Assign', but this ViewDataDictionary instance requires a model item of type 'TaskManager2.ViewModels.AssignViewModel'.

In the http post line, you are sending Assign (entity model)
To the controller, so the model state would be done on Assign class,
I think you should be using AssignViewModel there, because all the interaction with views should happen with viewmodel, if your view model is valid then covert to actual entity.

Related

Cannot update record in entity framework

The error message I get is this : DbUpdateConcurrencyException
I am using updating the IdentityRole table. I am using a ViewModel to display and capture data, then passing it back in, in my OnPost(). The data comes into my IdentityRole property as expected. I made sure the primary key is hidden in the form. I have also tried _context.Update(Role) in combination with SaveChanges() and i always get the same error. I also ensured i have the [BindProperty] on both my ViewModel and my IdentityRole properties.
Here is my Controller or code behind.
public IActionResult OnPost()
{
Role.Name = RoleViewModel.RoleName;
Role.NormalizedName = RoleViewModel.RoleName.ToUpper();
Role.ConcurrencyStamp = DateTime.Now.ToString();
if (!ModelState.IsValid)
{
return Page();
}
_context.Entry(Role).Property(x => x.Name).IsModified = true;
_context.Entry(Role).Property(x => x.NormalizedName).IsModified = true;
_context.Entry(Role).Property(x => x.ConcurrencyStamp).IsModified = true;
_context.SaveChanges();
return Redirect("/Admin/HomeRole");
}
My ViewModel
public class EditRoleViewModel
{
public EditRoleViewModel()
{
Users = new List<string>();
}
public string Id { get; set; }
[Required(ErrorMessage = "Role Name is required")]
public string RoleName { get; set; }
public List<string> Users { get; set; }
}
Here is the form
<form method="post" class="mt-3">
<input asp-for="RoleViewModel.Id" type="hidden" />
<input asp-for="RoleViewModel.Users" type="hidden" />
<div class="form-group row">
<label asp-for="RoleViewModel.RoleName" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input asp-for="RoleViewModel.RoleName" class="form-control">
<span asp-validation-for="RoleViewModel.RoleName" class="text-danger"></span>
</div>
</div>
</form>
From what I see in your question, you need to pass the Id for Http Post. I understand that you are hiding the Id in the form but you need pass it to the controller and then to the Model to save. So, something like below,
public IActionResult OnPost(RoleViewModel model){......}
then assign the Id. If you are updating the row with no Id, then you will get this exception in DbContext.
For further reading in MSDN
Asp.Net controller snippets
Check these too. Try and let me know.
Here is the answer to my question
public async Task<IActionResult> OnPost(EditRoleViewModel roleViewModel)
{
var role = await roleManager.FindByIdAsync(roleViewModel.Id);
role.Name = roleViewModel.RoleName;
var result = await roleManager.UpdateAsync(role);
return Page();
}

Fill #Html.DropDownListFor with data from ViewModel

I have working code for adding role to user. Now i want to replace text input by dropdownlist with available (all) roles in application. I now that there is html tag #Html.DropDownListFor so I created new ViewModel containing every data I need but now i'am in dead end. I try foreach and fill data for every item on list but then it doesn't work. Trying with #Htm.DropDownListFor have the same effect. Can someone can show me the right way how to do that ? Thanks !
Controller:
public async Task<IActionResult> AddRoleToUser(string id, string role)
{
var user = await _userManager.FindByIdAsync(id);
if (await _roleManager.RoleExistsAsync(role))
{
await _userManager.AddToRoleAsync(user, role);
}
return RedirectToAction("UserGroups", new { id });
}
ViewModel:
public class UserGroupsViewModel
{
public ApplicationUser ApplicationUser { get; set; }
public IList<string> RolesList { get; set; }
public IQueryable<ApplicationRole> AllRoles { get; set; }
}
View
#model AdminMVC.Models.Admin.UserGroupsViewModel
#using (Html.BeginForm("AddRoleToUser", "Admin", new { id = Model.ApplicationUser.Id }))
{
<div classs="input-group">
<p><b>Name new role for user:</b></p>
<input class="form-control form-control-sm" type="text" name="role" placeholder="Role name">
<span class="input-group-btn">
<input type="submit" value="Add Role" class="btn btn-primary btn-sm" />
</span>
</div>
}
My question has been identified as a possible duplicate of another question. But i saw that question and still cant do it.
Can I must change my IList to list containing two values id and string ? And add additional position in viewmodel to store the result?
Change your RolesList to a SelectList and add a role property:
public SelectList RolesList { get; set; }
public string Role{get;set;}
Build the SelectList with
model.RolesList = new SelectList(listOfRoles);
Then in your view
#Html.DropDownListFor(x => x.Role, Model.RolesList)
If everything is in order, your post should contain the role populated.

saving text box string value to database c#

I have a textbox in which the user can enter their desired username and save it. Once they save it and they happen to revisit their profile page that textbox should be populated with the last username they saved to display and the user will still have the ability to change it and resave. I am fairly new to this and not sure how to start this properly. I am using vs 2012 asp.net mvc 4 c#. Here is my code so far:
#model School.Models.StudentNameModel
#using (Html.BeginForm("_StudentNamePartial", "Profile")) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset>
<ol>
<li>
#Html.LabelFor(m => m.StudentName)
#Html.DisplayFor(m => m.StudentName)
#Html.TextBoxFor(m=>m.StudentName)
<button type="button" value="save" />
</li>
</ol>
</fieldset>
}
This is my Model:
public class StudentNameModel
{
[Display(Name = "Student Name")]
public string StudentName{ get; set; }
}
My controller:
GET - To get the student name from database if one exists.
[HttpPost]
public ActionResult _StudentNamePartial(int id)
{
id = WebSecurity.CurrentStudentId;
var model = new StudentNameModel();
using (var db = new StudentsDataContext())
{
var result = (from u in db.Students
where u.ID == id
select u.StudentName).FirstOrDefault();
if(result != null)
model.StudentName= result;
}
return View(model);
}
POST - This is where i want to save the new username for the student
[HttpPost]
public ActionResult _StudentNamePartial(StudentNameModel model)
{
if (ModelState.IsValid)
{
using (var db = new StudentDataContext())
{
try
{
}
catch (Exception)
{
throw;
}
}
return RedirectToAction("ProfileAccount");
}
return View(model);
}
Also i am having trouble that when i am displaying the username it is not hitting my Action method and it always reports that the Object reference is null. Any help will be great. Thanks :D
It would seem that you're trying to render a partial view from a controller action as part of the larger view. In this case, the partial view should be rendered within the ProfileAccount view.
You can structure the controller and views like this (rough outline):
ProfileAccount View Model:
public class ProfileAccountView
{
public StudentNameModel StudentName { get; set; }
}
Profile Controller:
[HttpGet]
public ActionResult ProfileAccount(int id)
{
// Get whatever info you need and store in a ViewModel
var model = new ProfileAccountView();
// Get the student info and store within ProfileAccountView
// Do your database reads
model.StudentName = new StudentNameModel { StudentName = result };
return View(model);
}
[HttpPost]
public ActionResult ProfileAccount(ProfileAccountView profile)
{
// Do whatever processing here
}
ProfileAccount View
#model School.Models.ProfileAccountView
#using (Html.BeginForm("ProfileAccount", "Profile"))
{
#Html.RenderPartial('_StudentNamePartial', Model.StudentName);
<button type="button" value="save" />
}
_StudentNamePartial Partial View
#model School.Models.StudentNameModel
<fieldset>
<ol>
<li>
#Html.LabelFor(m => m.StudentName)
#Html.TextBoxFor(m=>m.StudentName)
</li>
</ol>
</fieldset>

jQuery AutoComplete text not saving to db via create view using MVC3

This is my first post so please go easy on me fellas. I am trying to implement a create form that utilizes jquery autocomplete. The create form allows users to enter data that will be saved to my database, via a submit button. Here is my code:
Controller
// GET: /Inspection1/Create
public ActionResult Create()
{
InspectionInfo model = new InspectionInfo
{
Submitted = DateTime.Now,
Contact = new Contact()
};
ViewBag.CountyName = new SelectList(db.Counties, "CountyName", "CountyName");
return View(model);
}
//
// POST: /Inspection1/Create
[HttpPost]
public ActionResult Create(InspectionInfo inspectioninfo)
{
if (ModelState.IsValid)
{
db.InspectionInfos.Add(inspectioninfo);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(inspectioninfo);
}
// this allows for autocompletion behavior
public ActionResult QuickSearchContact(string term)
{
var contacts = db.Contacts
.Where(r => r.ContactName.Contains(term))
.Take(10)
.Select(r => new { label = r.ContactName });
return Json(contacts, JsonRequestBehavior.AllowGet);
}
Models
public class InspectionInfo
{
[Key]
public int InspectionId { get; set; }
[DataType(DataType.Date)]
public virtual DateTime Submitted { get; set; }
[DataType(DataType.MultilineText)]
[MaxLength(1000)]
public string Comments { get; set; }
[Required]
public Contact Contact { get; set; }
public class Contact
{
[Key]
public string ContactName { get; set; }
View:
<div class="editor-label">
#Html.LabelFor(model => model.Contact)
</div>
<div class="editor-field">
<input type ="text" name ="q" data-autocomplete=
"#Url.Action("QuickSearchContact", "Inspection")"/>
#Html.ValidationMessageFor(model => model.Contact.ContactName)
</div>
JS
$(document).ready(function () {
$(":input[data-autocomplete]").each(function () {
$(this).autocomplete({ source: $(this).attr("data-autocomplete")});
});
The autocomplete function seems to be working fine. It will pull column data from the database as I require. However, any data entered in the autocomplete text box, appears NULL in the database after the user has saved the form. Help here would be greatly appreciated.
For model binding to work, generally input names must match property names of your model. Surprisingly, you have named your input "q"
<input type ="text" name ="q" data-autocomplete="..."/>
Just rename it according to your model
<input type ="text" name="Contact.ContactName" data-autocomplete="..."/>
You don't have your on the code above but, instead of using
<input type ="text" name ="q" data-autocomplete= "#Url.Action("QuickSearchContact", "Inspection")"/>
use:
#EditorFor(x = x.NameOfTextBox)
then either have an input button wrapped in a using tag
#using (Html.BeginForm("Create", "NameOfController", FormMethod.Post){
//all your model stuff goes here
}
or use and actionlink instead of :
#Html.ActionLink("Submit", "Create", "NameOfController", Model)
The provided information doesn't tell, but is is likely that the autocomplete part is not written within the form elements of the view:
#using (Html.BeginForm())
{
<p>
...
</p>
}
In MVC the form is defined within the brackets { .... } like above.

Create controller logic for ASP.Net MVC for logged in user

I have the following models:
public class Expense
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
[Column(TypeName = "Money")]
public decimal Limit { get; set; }
[Required]
[ForeignKey("UserProfile")]
public int UserProfileId { get; set; }
[Required]
public virtual UserProfile UserProfile { get; set; }
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
}
I have a logged in user, and I would like that user to be able to add new Expenses. The create controller actions looks as follows:
//
// GET: /Expenses/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Expenses/Create
[HttpPost]
public ActionResult Create(Expense expense)
{
if (ModelState.IsValid)
{
UserProfile user = db.UserProfiles.Single(u => u.UserName == User.Identity.Name)
user.Expenses.Add(expense);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(expense);
}
With the view:
#model MoneyDrainPlug.Models.Expense
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Expense</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Limit)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Limit)
#Html.ValidationMessageFor(model => model.Limit)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
When the post happens, ModelState.IsValid is false in the controller. This is because the there is no UserProfileId set in the expense. It does not help if I set the UserProfile and\or UserProfileId before calling ModelState.IsValid.
What would be the correct and secure way of handling this? Surely I should not trust a UserProfileId sent from the client?
In case it matters, I am using asp.net MVC 4.
Thanks in advance
Possible solutions
remove the Required attribute on UserProfileId (DataAnnotation is just related to views : if you don't want something in your views, don't mark it as Required : RequiredAttribute is not a DB constraint)
or
remove the UserProfileId property (you have the UserProfile proeprty, it's probably enough)
or
create a ViewModel without the UserProfileId property
EDIT :
To be clear : you can perfectly use a ViewModel. Just be conscient that Required attributes have nothing to do directly with your DB. If you don't use the Expense class directly in a view, you can remove all your Required attributes in Expense, they will never be used.
for one, You should make sure those pages can only be accessed by authorized users (Add [Authorize] above every method in the controller that requires the user to be logged in)
There are a couple of ways actually of doing this:
One would be a hidden field with the name UserProfileId and value of the logged in users Id
This would be the least safe method of doing it as the Id will be posted from the client to the server therefor it could be manipulated by the client.
I notice that those fields have the required annotation so either you remove the [Required] to make it validate OR you make a new viewmodel with all those attributes without having UserProfileId required (and obviously you dont include the virtual attribute as it isnt needed in this case).
It would turn out to something like this:
public class ExpenseModel
{
[Required]
public string Name { get; set; }
[Required]
public decimal Limit { get; set; }
public int UserProfileId { get; set; }
}
As a final touch, you change the model that the view expects:
#model MoneyDrainPlug.Models.ExpenseModel
And the model the controller expects to recieve after a post:
[Authorize]
[HttpPost]
public ActionResult Create(ExpenseModel expense)
{
if (ModelState.IsValid)
{
UserProfile user = db.UserProfiles.Single(u => u.UserName == User.Identity.Name)
_expense = new Expense() { Name = expense.Name, Limit = expense.Limit, UserProfile = user };
user.Expenses.Add(_expense);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(expense);
}

Categories