ASP.NET Tag helpers and Bootstrap Dropdown. How to use together? - c#

Faced such peculiar problem. For a better understanding, I will try to describe in more detail.
I have two metod in ArticleController:
[HttpGet]
public async Task<IActionResult> Create()
{
var sources = await _sourceServices.GetSourceNameAndId();
var listSources = new List<SourceNameAndIdModel>();
foreach (var source in sources)
{
listSources.Add(_mapper.Map<SourceNameAndIdModel>(source));
}
var viewModel = new ArticleCreateViewModel()
{
SourceNameAndIdModels = listSources
};
return View(viewModel);
}
[HttpPost]
public async Task<IActionResult> Create(ArticleCreateViewModel viewModel)
{
await _articleService.CreateArticle(_mapper.Map<ArticleDTO>(viewModel));
return RedirectToAction("Index", "Article");
}
As you can see, in the Get-method, I get the names and ids of all Sources from the database via _sourceService in the form of IEnumerable :
public class SourceNameAndIdDTO
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Next, I enumerate them in a foreach loop and add each SourceNameAndIdDTO object to the List listSources I created before:
public class SourceNameAndIdModel
{
public string Name { get; set; }
public Guid Id { get; set; }
}
Next, I create an instance of the ArticleCreateViewModel model, which I will use further in the View:
public class ArticleCreateViewModel
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Title { get; set; }
public string Description { get; set; }
public string Body { get; set; }
public Guid SourceId { get; set; }
public DateTime CreationDate { get; set; }
public List<SourceNameAndIdModel> SourceNameAndIdModels { get; set; }
}
And I assign to the field public List SourceNameAndIdModels { get; set; } List listSources values:
var viewModel = new ArticleCreateViewModel()
{
SourceNameAndIdModels = listSources
};
You can see this in the controller code I posted above. Next, I send the viewModel to the View.
Code of my View:
#model ArticleCreateViewModel
<div class="container">
<h2>Edit article</h2>
<div class="row gx-5">
<form asp-controller="Article" asp-action="Create" asp-antiforgery="true" method="post">
<div>
<input type="hidden" asp-for="Id" />
<div class="mb-3">
<label class="col-sm-2 col-form-label" asp-for="Title"></label>
<input class="form-control" type="text" asp-for="Title">
</div>
<div class="mb-3">
<label class="col-sm-2 col-form-label" asp-for="Description"></label>
<textarea class="form-control" asp-for="Description"></textarea>
</div>
<div class="mb-3">
<label class="col-sm-2 col-form-label" asp-for="Body"></label>
<textarea class="form-control" asp-for="Body"></textarea>
</div>
<div class="dropdown">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Source
</a>
<ul class="dropdown-menu">
#foreach (var item in #Model.SourceNameAndIdModels)
{
<li><select class="dropdown-item" asp-for="SourceId" asp-items="#item.Id">#item.Name</select></li>
}
</ul>
</div>
<div class="mb-3">
<label class="col-sm-2 col-form-label" asp-for="CreationDate"></label>
<input type="datetime-local" class="form-control" asp-for="CreationDate">
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
And finally, in this place of the code, in which I want to set the Source of the article I am creating, I have an error:
enter image description here
Can you please tell me how in my case to make friends with my code with dropdown on my request? What am I doing wrong?

Using asp-items in this way is incorrect.
The Select Tag Helper asp-items specifies the option elements
Details and example:
https://learn.microsoft.com/...netcore-6.0#the-select-tag-helper

Related

Create Parent and Multiple Children in same view in ASP.NET MVC with C#

I have been racking the web trying to find an easy solution (or at least a regular walk through) of what I would think would be a common activity, but I have been unable to find any. I have the following classes:
public class Attribute
{
...
public int Id { get; set; }
[Required]
public string AttributeName { get; set; }
public string Description { get; set; }
public bool UserCanChangeValue { get; set; }
public ICollection<AttributeValue> Values { get; set; }
}
class AttributeValue
{
public int Id { get; set; }
[ForeignKey("AssociatedAttribute")]
public int AssociatedAttributeId { get; set; }
public virtual Attribute AssociatedAttribute { get; set; }
[Required]
public string Value { get; set; }
public bool PromptChange { get; set; } = true;
public bool ShowonInvoice { get; set; } = false;
}
Then in a standard controller:
...
// GET: AttributesController/Create
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Description,Values")] Attribute newAttribute)
....
What I am confused on is how to create this where an attribute has to have 1 or more values that need to be created at the same time, and dynamically an attribute can have a variable number. Ideally I would add rows to the form for each attribute value to create.
Some scaffolded details:
...
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="AttributeName" class="control-label"></label>
<input asp-for="AttributeName" class="form-control" />
<span asp-validation-for="AttributeName" 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="AttributeName" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="UserCanChangeValue" /> #Html.DisplayNameFor(model => model.UserCanChangeValue)
</label>
</div>
<div>
<table>
<thead>
<tr>
<th>Value</th>
<th>Prompt Change</th>
<th>Show on Invoice</th>
</tr>
</thead>
<tbody>
INSERT DYNAMIC NEW VALUE FORM?
</tbody>
</table>
</div>
Firstly, you set PromptChange and ShowonInvoice default value, if you make checkbox/radio input unchecked, they will pass the default value(PromptChange= true and ShowonInvoice=false) to backend. But actually, I prefer do not set default value which can control true/false by checked or unchecked. Not sure which way is what you want, just modify by yourself.
Then your class AttributeValue need public access modifier.
Here is a simple working demo you could follow:
Model:
namespace MvcProj5_0.Models
{
public class Attribute
{
public int Id { get; set; }
[Required]
public string AttributeName { get; set; }
public string Description { get; set; }
public bool UserCanChangeValue { get; set; }
public ICollection<AttributeValue> Values { get; set; }
}
public class AttributeValue
{
public int Id { get; set; } //add a primary key
[ForeignKey("AssociatedAttribute")]
public int AssociatedAttributeId { get; set; }
public virtual Attribute AssociatedAttribute { get; set; }
[Required]
public string Value { get; set; }
public bool PromptChange { get; set; }
public bool ShowonInvoice { get; set; }
}
}
View(Create.cshtml):
#model MvcProj5_0.Models.Attribute
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="AttributeName" class="control-label"></label>
<input asp-for="AttributeName" class="form-control" />
<span asp-validation-for="AttributeName" 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="AttributeName" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="UserCanChangeValue" /> #Html.DisplayNameFor(model => model.UserCanChangeValue)
</label>
</div>
<button type="button" onclick="AddRow()">Add row</button> //add button
<div>
<table>
<thead>
<tr>
<th>Value</th>
<th>Prompt Change</th>
<th>Show on Invoice</th>
</tr>
</thead>
<tbody id="AttributeValueList" data-count="0"> //add id and data-count
INSERT DYNAMIC NEW VALUE FORM?
</tbody>
</table>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
JS in Create.cshtml:
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script>
function AddRow()
{
var countVal = parseInt($('#AttributeValueList').attr('data-count'));
var html = '';
html += '<tr>';
html += '<td><input type="text" name="Values[' + countVal + '].Value" class="form-control"/></td>';
html += '<td><input type="checkbox" name="Values[' + countVal + '].PromptChange" value="true"/></td>';
html += '<td><input type="checkbox" name="Values[' + countVal + '].ShowonInvoice" value="true"/></td>';
html += '</tr>';
$('#AttributeValueList').append(html);
countVal += 1;
$('#AttributeValueList').attr('data-count', countVal);
}
</script>
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("Id,AttributeName,Description,UserCanChangeValue,Values")] MvcProj5_0.Models.Attribute attribute)
{
if (ModelState.IsValid)
{
_context.Add(attribute);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(attribute);
}

How to populate dropdown list from nested Models? (Asp.net Core MVC)

I have a nested model and I use one model named (BootstrapTool) to make the view strongly typed.
I use ViewBag to populate a dropdown list with the nested Model named (BootstrapCategory). But my problem is when I select from it always returns validation null exception.
BootstrapTool Model
public class BootstrapTool
{
public Guid Id { get; set; }
[MaxLength(100,ErrorMessage ="Max 100")]
[Required]
public string tool { get; set; }
[MaxLength(300, ErrorMessage = "Max 300")]
[Required]
public string descreption { get; set; }
[Required]
public string html { get; set; }
[Required] // here where nisting the other Model BootstrapCategory
public BootstrapCategory category { get; set; }
}
BootstrapCategory Model
public class BootstrapCategory
{
[Key]
public Guid Id { get; set; }
[MaxLength(20)]
[Required]
public string Category { get; set; }
}
The Controller
public IActionResult Create()
{
List<BootstrapCategory> bootstrapCategories = _context.bootstrapCategories.ToList();
ViewBag.bpCategories = new SelectList(bootstrapCategories, "Id", "Category");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(BootstrapTool bootstrapTool)
{
if (ModelState.IsValid)
{
bootstrapTool.Id = Guid.NewGuid();
_context.Add(bootstrapTool);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(bootstrapTool);
}
Here where the dropdown list is to display BootstrapCategory
The View
#model BootstrapTool
<div class="row">
<div class="col-10 offset-1">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="gap-2 d-flex justify-content-between">
<div class="form-group w-50">
<label asp-for="tool" class="control-label"></label>
<input asp-for="tool" class="form-control" />
<span asp-validation-for="tool" class="text-danger"></span>
</div>
<div class="form-group w-50">
<label class="control-label">category</label>
<select asp-for="category.Id" class="form-control" asp-items="#ViewBag.bpCategories">
<option value="" disabled hidden selected>Select Section ...</option>
</select>
<span asp-validation-for="category" class="text-danger"></span>
#*<select asp-for="Id" asp-items="#Html.GetEnumSelectList<Enum>()" class="form-control">
<option value="" hidden disabled selected> -- please selecgt -- </option>
</select>*#
</div>
</div>
<div class="form-group">
<label asp-for="descreption" class="control-label"></label>
<textarea asp-for="descreption" class="form-control" rows="2"></textarea>
<span asp-validation-for="descreption" class="text-danger"></span>
</div>
<div class="form-group mb-2">
<label asp-for="html" class="control-label"></label>
<textarea asp-for="html" class="form-control" rows="5"></textarea>
<span asp-validation-for="html" class="text-danger"></span>
</div>
<div class="form-group mt-3">
<input type="submit" value="Create" class="btn btn-primary d-grid col-6 mx-auto" />
</div>
</form>
</div>
</div>
Add CategoryId
public class BootstrapTool
{
[Key]
public Guid Id { get; set; }
[Required]
public Guid? CategoryId { get; set; }
[ForeignKey(nameof(CategoryId))]
[InverseProperty("BootstrapTools")]
public BootstrapCategory Category { get; set; }
}
public class BootstrapCategory
{
[Key]
public Guid Id { get; set; }
[MaxLength(20)]
[Required]
public string Category { get; set; }
[InverseProperty(nameof(BootstrapTool.Category))]
public virtual ICollection<BootstrapTool> BootstrapTools { get; set; }
}
and fix the view
<select asp-for="CategoryId" class="form-control" asp-items="#ViewBag.bpCategories">
</select>
```

NullReferenceException: Object reference not set to an instance of an object when trying to create a cascading dropdown list in ASP.NET Core MVC [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 3 years ago.
I am trying to create a view model that allows the user to create a new product by typing in the products new name and then select which parent category it belongs to, which then filters the sub category and the user selects the appropriate sub-category. I am currently just trying to have both a drop down list of all parent categories and a drop down list of all subcategories, but I am getting a null refernce for the parent category in the view.
Models
public class Product
{
[Key]
public int ProductID { get; set; }
[Required]
[Display(Name = "Item Name")]
public string ProductName { get; set; }
public int ProductSubcategoryID { get; set; }
[Required]
public ProductSubcategory ProductSubcategory { get; set; }
}
public class ProductSubcategory
{
[Key]
public int ProductSubcategoryID { get; set; }
[Required]
public int ParentCategoryID { get; set; }
public ParentCategory ParentCategory { get; set; }
[Required]
[Display(Name = "Type")]
public string ProductSubcategoryDescription { get; set; }
}
public class ParentCategory
{
[Key]
public int ParentCategoryID { get; set; }
[Required]
public string ParentCategoryDescription { get; set; }
public IEnumerable<ProductSubcategory> ProductSubcategories { get; set; }
}
View Model
public class CreateProductViewModel
{
public IEnumerable<ParentCategory> ParentCategories{ get; set; }
public IEnumerable<ProductSubcategory> ProductSubcategories { get; set; }
public Product Product { get; set; }
}
Controllers
// GET: Products/Create
public IActionResult Create()
{
var parentCategories = _context.ParentCategories.ToList();
var productSubcategories = _context.ProductSubcategories.ToList();
var viewModel = new CreateProductViewModel
{
ParentCategories = parentCategories,
ProductSubcategories = productSubcategories
};
return View(viewModel);
}
// POST: Products/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]
public async Task<IActionResult> Create([Bind("ProductID,ProductName,VendorID,ProductSubcategoryID,ParentCategoryID,LocationID,QuantityPerUnit,UnitsInStock,UnitsInOrder,ReorderLevel,ProductComment,ProductMedia")]Product product)
{
_context.Add(product);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
view
#model PrototypeWithAuth.ViewModels.CreateProductViewModel
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/View.cshtml";
}
<h1>Create</h1>
<h4>Product</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="Product.ProductName" class="control-label"></label>
<input asp-for="Product.ProductName" class="form-control" />
<span asp-validation-for="Product.ProductName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ProductSubcategoryID" class="control-label"></label>
<!--<input asp-for="Product.ProductSubcategoryID" class="form-control" />-->
#Html.DropDownListFor(s => s.Product.ProductSubcategoryID,
new SelectList(Model.ProductSubcategories, "ProductSubcategoryID", "ProductSubcategoryDescription"),
"Select Subcategory", new { #class = "form-control" })
<span asp-validation-for="Product.ProductSubcategoryID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ProductSubcategory.ParentCategoryID" class="control-label"></label>
<!--<input asp-for="ProductSubcategoryID" class="form-control" />-->
#Html.DropDownListFor(c => c.Product.ProductSubcategory.ParentCategoryID,
new SelectList(Model.ParentCategories, "ParentCategoryID", "ProductSubcategoryDescription"),
"Select Category", new { #class = "form-control" })
<span asp-validation-for="Product.ProductSubcategory.ParentCategoryID" class="text-danger"></span>
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
When trying to add a new product I get a null reference for ParentCategory, but if I do not us a html helper tag for a dropdown than the form works. Thank you in advance
The SelectList is used like below
public SelectList(IEnumerable items, string dataValueField, string dataTextField)
In you case,ProductSubcategoryDescription is not the field of ParentCategory leading to the error.So change it to
#Html.DropDownListFor(c =>
c.Product.ProductSubcategory.ParentCategoryID,
new SelectList(Model.ParentCategories, "ParentCategoryID", "ParentCategoryDescription"),
"Select Category", new { #class = "form-control" })
create a new product by typing in the products new name and then select which parent category it belongs to, which then filters the sub category and the user selects the appropriate sub-category.
If you want to get the subcategories when you choose the parent category, you could use some JavaScript, for example:
View:
#model PrototypeWithAuth.ViewModels.CreateProductViewModel
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/View.cshtml";
}
<h1>Create</h1>
<h4>Product</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="Product.ProductName" class="control-label"></label>
<input asp-for="Product.ProductName" class="form-control" />
<span asp-validation-for="Product.ProductName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product.ProductSubcategory.ParentCategoryID" class="control-label"></label>
<!--<input asp-for="ProductSubcategoryID" class="form-control" />-->
#Html.DropDownListFor(c => c.Product.ProductSubcategory.ParentCategoryID,
new SelectList(Model.ParentCategories, "ParentCategoryID", "ParentCategoryDescription"),
"Select Category", new { #class = "form-control",#id="parentlist" })
<span asp-validation-for="Product.ProductSubcategory.ParentCategoryID" class="text-danger"></span>
</div>
<div>
<select name="Product.ProductSubcategoryID" id="sublist" class="form-control"></select>
</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");}
<script type="text/javascript">
//Insert default item "Select" in dropdownlist on load
$(document).ready(function () {
var items = "<option value='0'>Select</option>";
$("#sublist").html(items);
});
$("#parentlist").change(function () {
var parentCategoryId = $("#parentlist").val();
var url = "/Products/GetSubCategoryList";
$.getJSON(url, { ParentCategoryId: parentCategoryId }, function (data) {
var item = "";
$("#sublist").empty();
$.each(data, function (i, subCategory) {
item += '<option value="' + subCategory.productSubcategoryID + '">' + subCategory.productSubcategoryDescription + '</option>'
});
$("#sublist").html(item);
});
});
</script>
}
ProductsController:
[HttpGet]
public JsonResult GetSubCategoryList(int ParentCategoryId)
{
var subCategoryList = _context.ProductSubcategories.Where(c => c.ParentCategoryID == ParentCategoryId).ToList();
return Json(subCategoryList);
}

ASP.NET MVC View needs to display data from a certain model and at the same time take input data for another model

I have a View which uses a dynamic object to pull data from multiple models declared in the ViewModel. However, within the same model I am trying to take user input via a form. The data is correctly displayed for the 2 models which are also part of the dynamic object. But I am UNSUCCESSFUL getting the form input, because I keep getting the error that the dynamic object is not accessible.[And this is for the form only.]
Here is how the View looks like
#model dynamic
#using ActionAugerMVC.ViewModels
#addTagHelper "*,Microsoft.AspNetCore.Mvc.TagHelpers"
<div class="course__title">
#Model.item.PageTitle
</div>
<p class="course__desc">
#Html.Raw(Model.item.PageText)
</p>
<div class="event__price">
<h3>#Model.item.NoticeTitle</h3>
<p>#Model.item.NoticeNeedItem</p>
<button type="submit" class="btn btn-accent">
Get A Quote Now
</button>
</div>
<h3 class="course_desc__title">Other Services</h3>
<ul class="course_requirements__list multi-column">
#foreach (var link in Model.data)
{
<li><i class="ion-android-arrow-forward"></i> #Html.ActionLink((string)link.PageTitle, "Page", "Plumbing", new { id = link.ID, url = link.PageURL }) </li>
}
</ul>
<div class="sidebar__item">
<p class="subheading">Instant Quote Request</p>
<form class="register__form" role="form" asp-controller="Plumbing" asp-action="Page" method="post">
<div class="text-danger" asp-validation-summary="All"></div>
<div class="form-group">
<label class="sr-only">Full Name </label>
<input asp-for="#Model.quote.FullName" type="text" class="form-control" placeholder="Full name">
</div>
<div class="form-group">
<label class="sr-only">Your phone</label>
<input asp-for="#Model.quote.Phone" type="tel" class="form-control" placeholder="Your phone">
<span asp-validation-for="#Model.quote.Phone" class="text-danger"></span>
</div>
<div class="form-group">
<label class="sr-only">E-mail</label>
<input asp-for="#Model.quote.Email" type="email" class="form-control" placeholder="E-mail">
<span asp-validation-for="#Model.quote.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label class="sr-only">Your Message</label>
<input asp-for="#Model.quote.Message" type="text" class="form-control" placeholder="Your Message">
</div>
<input type="submit" value="Get a Quote Now" class="btn btn-accent btn-block">
</form>
</div> <!-- .sidebar__item -->
And here is how the controller looks like :-
public class PlumbingController : Controller
{
private readonly ActionAugerDataContext actionAugerDbContext;
private readonly UnitOfWork unitOfWork;
private readonly PlumbingPageViewModel plumbingViewModel;
public PlumbingController(ActionAugerDataContext context)
{
actionAugerDbContext = context;
unitOfWork = new UnitOfWork(actionAugerDbContext);
plumbingViewModel = new PlumbingPageViewModel(unitOfWork);
}
// GET: /<controller>/
public IActionResult Index()
{
var data = plumbingViewModel.PlumbingContent;
return View(data);
}
[HttpGet]
[Route("plumbing/calgary-{url}")]
public IActionResult Page(int ID, string url)
{
dynamic Page = new ExpandoObject();
Page.item = unitOfWork.ContentRepository.GetById(ID);
Page.data = plumbingViewModel.PlumbingContent;
Page.cities = plumbingViewModel.Cities;
// Page.quote = plumbingViewModel.Quote;
return View(Page);
}
[HttpPost]
public IActionResult Page(Quote quote)
{
return View();
}
}
Here is the View Model :-
public class PlumbingPageViewModel
{
public IEnumerable<Content> PlumbingContent { get; set; }
public IEnumerable<Cities> Cities { get; set; }
public Quote Quote { get; set; }
public PlumbingPageViewModel(UnitOfWork unitOfWork)
{
PlumbingContent = unitOfWork.ContentRepository
.GetAll()
.Where(d => d.Section == "Plumbing")
.Where(c => c.City == "Calgary");
Cities = unitOfWork.CitiesRepository
.GetAll()
.Where(c => c.HomeCity == "Calgary");
}
}
And here is the model class for the form.
public class Quote
{
public int ID { get; set; }
public string FullName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string City { get; set; }
public string Message { get; set; }
}
you can't you use dynamic model ( #model dynamic) for building your form with HtmlHelper
If you want Post form you should specific model.
Hope you this will you.

MVC 5 ASP.NET Entity Framework Collect Form Data through Range Input

So I am creating a survey monkey, a Survey Has 4 categories, and each category has 5 questions, the HomeController Index passes on the 20 questions from the Questions entity and each Questions must have a Range Slider with values from one to 5, the form values must be collected into a method in the controller, then accumulated into the QuestionResults table and processed, then they must be passed onto the CategoryResults and each Category, out of the 4, must have an accumulated score, they must then be compared to the CategoryFeedback(which is static) table and display the correct information. The code for my HomeController and it's index is as below.
The main issue that I am having, is that since I am passing on the form values through as a parameter of FormCollection, I am struggling to keep the relationship of a Question belonging to a Category and then passing these all onto the CategoryResult table for processing.
Any help will be greatly appreciated
Models:
Question
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace STRA.Models
{
public class Question
{
public int Id { get; set; }
public string Title { get; set; }
public string CreatedBy { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateModified { get; set; }
public virtual Category Category { get; set; }
public int CategoryId { get; set; }
public virtual ICollection<QuestionResult> QuestionResult { get; set; }
public virtual ICollection<QuestionFeedback> QuestionFeedback { get; set; }
}
}
QuestionResult
using IdentitySample.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace STRA.Models
{
public class QuestionResult
{
public int Id { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateModified { get; set; }
public int QuestionScore { get; set; }
//navigation properties
public virtual ApplicationUser User { get; set; }
public ICollection<CategoryResult> CategoryResult { get; set; }
public virtual Question Question { get; set; }
public int QuestionId { get; set; }
}
}
CategoryResult
using IdentitySample.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace STRA.Models
{
public class CategoryResult
{
public int Id { get; set; }
public int CategoryScore {get;set;}
//navigation properties
public virtual QuestionResult QuestionResult { get; set; }
public int QuestionResultId { get; set; }
}
}
Any help will be greatly appreciated.
HomeController Index
#model IEnumerable<STRA.Models.Question>
#{
//ViewBag.Question.Title = "Survey";
Layout = "~/Views/Shared/_QuizLayout.cshtml";
}
<div class="hr hr-18 hr-double dotted"></div>
<div class="widget-box">
<div class="widget-header widget-header-blue widget-header-flat">
<h4 class="widget-title lighter">STRA</h4>
#*<div class="widget-toolbar">
<label>
<small class="green">
<b>Validation</b>
</small>
<input id="skip-validation" type="checkbox" class="ace ace-switch ace-switch-4" />
<span class="lbl middle"></span>
</label>
</div>*#
</div>
#*"Create","Home"*#
#using (Html.BeginForm("Create", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="widget-body">
<div class="widget-main">
<!-- #section:plugins/fuelux.wizard -->
<div id="fuelux-wizard-container">
<div class="col-md-12 center">
<div class="easy-pie-chart percentage" data-percent="25" data-color="#2679b5">
#*<span class="percent">20</span>%*#
</div>
</div>
<div style="display:none;">
<!-- #section:plugins/fuelux.wizard.steps -->
<ul class="steps">
<li data-step="1" class="active">
<span class="step">1</span>
<span class="Question.Title">Validation states</span>
</li>
<li data-step="2">
<span class="step">2</span>
<span class="Question.Title">Alerts</span>
</li>
<li data-step="3">
<span class="step">3</span>
<span class="Question.Title">Payment Info</span>
</li>
<li data-step="4">
<span class="step">4</span>
<span class="Question.Title">Other Info</span>
</li>
</ul>
<!-- /section:plugins/fuelux.wizard.steps -->
</div>
<hr />
<!-- #section:plugins/fuelux.wizard.container -->
<div class="step-content pos-rel">
<div class="step-pane" data-step="1">
#{
foreach (var item in Model.Take(5))
{
<div class="col-md-12">
<h4>#Html.DisplayFor(modelItem => item.Title)</h4>
<div id="slider-eq">
<h5 class="pull-left">Strongly Disagree</h5>
<h5 class="pull-right">Strongly Agree</h5>
<span id="q_#Html.DisplayFor(modelItem => item.Id)" class="ui-slider-purple">3</span>
<input type="hidden" id="question_#item.Id" name="question_#item.Id" value="3" />
</div>
</div>
}
//Model.Skip(5);
}
</div>
<div class="step-pane" data-step="2">
#*<div class="center">
<h3 class="blue lighter">This is step 2</h3>
</div>*#
#{
foreach (var item in Model.Skip(5).Take(5))
{
<div class="col-md-12">
<h4>#Html.DisplayFor(modelItem => item.Title)</h4>
<div id="slider-eq">
<h5 class="pull-left">Strongly Disagree</h5>
<h5 class="pull-right">Strongly Agree</h5>
<span id="q_#Html.DisplayFor(modelItem => item.Id)" class="ui-slider-purple">3</span>
<input type="hidden" id="question_value" name="question_value" value="3" />
<input type="hidden" id="questonId" name="questonId" value="#item.Id" />
#*<span class="ui-slider-red">55</span>
*#></div>
</div>
}
}
</div>
<div class="step-pane" data-step="3">
#*<div class="center">
<h3 class="blue lighter">This is step 3</h3>
</div>*#
#{
foreach (var item in Model.Skip(10).Take(5))
{
<div class="col-md-12">
<h4>#Html.DisplayFor(modelItem => item.Title)</h4>
<div id="slider-eq">
<h5 class="pull-left">Strongly Disagree</h5>
<h5 class="pull-right">Strongly Agree</h5>
<span id="q_#Html.DisplayFor(modelItem => item.Id)" class="ui-slider-purple">3</span>
<input type="hidden" id="question_#item.Id" name="question_#item.Id" value="3" />
#*<span class="ui-slider-red">55</span>
*#
</div>
</div>
}
}
</div>
<div class="step-pane" data-step="4">
#*<div class="center">
<h3 class="blue lighter">This is step 4</h3>
</div>*#
#{
foreach (var item in Model.Skip(15).Take(5))
{
<div class="col-md-12">
<h4>#Html.DisplayFor(modelItem => item.Title)</h4>
<div id="slider-eq">
<h5 class="pull-left">Strongly Disagree</h5>
<h5 class="pull-right">Strongly Agree</h5>
<span id="q_#Html.DisplayFor(modelItem => item.Id)" class="ui-slider-purple">3</span>
<input type="hidden" id="question_#item.Id" name="question_#item.Id" value="3" />
#*<span class="ui-slider-red">55</span>
*#
</div>
</div>
}
}
</div>
</div>
<!-- /section:plugins/fuelux.wizard.container -->
</div>
<hr />
<div class="wizard-actions">
<!-- #section:plugins/fuelux.wizard.buttons -->
<button type="button" id="previous" class="btn btn-prev">
<i class="ace-icon fa fa-arrow-left"></i>
Prev
</button>
<button type="button" id="next" class="btn btn-success btn-next" #*data-last="Finish"*#>
Next
<i class="ace-icon fa fa-arrow-right icon-on-right"></i>
</button>
<button id="finish" class="btn btn-success" type="submit">
Submit
<i class="ace-icon fa fa-arrow-right icon-on-right"></i>
</button>
<!-- /section:plugins/fuelux.wizard.buttons -->
</div>
<!-- /section:plugins/fuelux.wizard -->
</div><!-- /.widget-main -->
</div>
<!-- /.widget-body -->
}
</div>
HomeController
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using IdentitySample.Models;
using STRA.Models;
using System.Collections.Generic;
using System.Diagnostics;
using System;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
namespace IdentitySample.Controllers
{
[Authorize]
public class HomeController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
//public ActionResult Index()
public ActionResult Index()
{
if (User.IsInRole("Admin"))
{
return RedirectToAction("Index", "Dashboard");
}
if (User.IsInRole("Sales Consultant"))
{
return RedirectToAction("Index", "Reports");
}
//loads all questions in
//var questions = db.Questions.Include(s => s.Survey).Include(c => c.Category);
var questions = db.Questions.Include(c => c.Category);
return View(questions.ToList());
//var questionResults = db.QuestionResults.Include(c => c.Question);
//return View(questionResults.ToList());
//viewmodel attempt
//var viewModel = new SurveyViewModels();
//viewModel.Questions = db.Questions
// .Include(i =>)
}
[HttpPost]
public ActionResult Index(FormCollection result)
{
List<QuestionResult> info = new List<QuestionResult>();
QuestionResult newResult = new QuestionResult();
var appUser = new ApplicationUserManager(new UserStore<ApplicationUser>(db));
ApplicationUser user = appUser.FindById(User.Identity.GetUserId());
foreach (var key in result.AllKeys.Where(k => k.Contains("question")).ToArray<string>())
{
string[] keys = key.Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
if (key.Count() > 1)
{
QuestionResult re = new QuestionResult();
re.QuestionScore = Convert.ToInt32(result[key]);
re.QuestionId = Convert.ToInt32(keys[1]);
re.User = user;
db.QuestionResults.Add(re);
}
}
db.SaveChanges();
Debug.WriteLine(result[0]);
return View();
}
}
}
jQuery UI Slider Plugin
$("#slider-eq > span").css({ width: '100%', 'float': 'left', margin: '15px' }).each(function () {
// read initial values from markup and remove that
var value = parseInt($(this).text(), 10);
$(this).empty().slider({
value: value,
min: 1,
max: 5,
range: "min",
animate: true,
change: function (event, ui) {
//alert(ui.value);
$(this).next("[id^=question_]").val(ui.value);
//$(this > ).slider("values", $this.data("index"), $this.val());
}
});
});
I would in your RazorTemplate add the question category as a prefix to the question_id or question_name which is what comes through via the FormCollection
<input type="hidden" id="#(item.category_id)_question_#item.Id" name="#(item.category_id)_question_#item.Id" value="3" />
Then in your controller action, just pull out the category_id from the start of the name, so you know what category you are currently iterating through.
Your view code is creating form controls which have no relationship to your models. Instead create view models representing what you need to display/edit in the view, use strongly typed helpers to give you 2-way model binding and post back your view model.
View models
public class SurveyVM
{
public List<CategoryVM> Categories { get; set; }
// add any other properties of Survey you need to display/edit (e.g. ID, Title etc)
}
public class CategoryVM
{
public List<QuestionVM> Questions { get; set; }
// add any other properties of Category you need to display/edit (e.g. ID, Title etc)
}
public class QuestionVM
{
public int Id { get; set; }
public string Title { get; set; }
public int Score { get; set; }
// Do not include properties such as DateCreated, User etc
}
Controller
public ActionResult Index()
{
SurveyVM model = new SurveyVM();
// populate the categories and for each category, populate the associated questions
return View(model);
}
[HttpPost]
public ActionResult Index(SurveyVM model)
{
// loop through each Category, and foreach category loop through each Question to build your `List<QuestionResult>`
}
View
#model yourAssembly.SurveyVM
#using (Html.BeginForm())
{
// add elements for properties of SurveyVM (ID, Title etc)
for(int i = 0; i < Model.Categories.Count; i++)
{
<div class="category">
// add elements for properties of each CategoryVM (ID, Title etc)
#for (int j = 0; j < Model.Categories[i].Questions.Count; j++)
{
<div class="question">
#Html.HiddenFor(m => m.Categories[i].Questions[j].Id)
#Html.DisplayFor(m => m.Categories[i].Questions[j].Title)
#Html.TextBoxFor(m => m.Categories[i].Questions[j].Score)
</div>
}
</div>
}
<input type="submit" .../>
}

Categories