Repopulating selectlist for dropdownlist on postback when default selected ASP.NET MVC - asp.net-mvc

New to ASP.NET MVC and I've come across this before, however I feel I've gone about fixing it in an incorrect way. I was hoping someone could point me in the direction of the correct way of doing it.
I have a page which the user selects a dropdown value and clicks next. Now, if they don't select an item and select the default value ("Select..."), there is a validation error. The controller seems to lose information about the dropdownlist even though the model is returned to the view on a "postback." So, say I don't basically repeat code in my [HTTPPost] from my [HTTPGet]. I get the error:
There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'SheetIndex'.
on
#Html.DropDownListFor(model => model.SheetIndex, Model.SheetsDropdown, "Select...", new { #class = "form-control" })
So unless I repeat code, I get that error when no selection is made. What am I doing wrong?
ViewModel:
public class SelectSheetViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
[Required]
public string SheetIndex { get; set; }
public string SheetName { get; set; }
public int? ChainId { get; set; }
public int? SheetId { get; set; }
public int? FileId { get; set; }
public IEnumerable<SelectListItem> SheetsDropdown { get; set; }
public HeaderViewModel Header { get; set; }
}
Controller:
[HttpGet]
public ActionResult SelectSheet(int? chainId, int? sheetId, int? fileId)
{
if (sheetId == null || fileId == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var fileDetails = db.FileDetails.Find(fileId);
SelectSheetViewModel selectSheet = new SelectSheetViewModel()
{
Id = fileDetails.FileId,
Name = fileDetails.Name,
ChainId = chainId,
SheetId = sheetId,
FileId = fileId
};
string fileName = fileDetails.UniqueName + fileDetails.Extension;
string relativeFileLocation = "~/uploads/" + fileName;
string absoluteFileLocation = HttpContext.Server.MapPath(relativeFileLocation);
if (System.IO.File.Exists(absoluteFileLocation))
{
DSDBuilder builder = new DSDBuilder();
selectSheet.SheetsDropdown = builder.GetSheets(absoluteFileLocation); // Where I get my selectlist
}
else
{
ModelState.AddModelError("SheetDropdown", "Excel workbook does not exist.");
}
selectSheet.Header = BuildHeaderViewModel(chainId, sheetId);
return View(selectSheet);
}
[HttpPost]
public ActionResult SelectSheet(SelectSheetViewModel selectSheet)
{
if (ModelState.IsValid)
{
FileDetail fileDetails = db.FileDetails.Find(selectSheet.FileId);
string[] sheetIndexAndName = selectSheet.SheetIndex.Split(':');
fileDetails.SheetIndex = Convert.ToInt32(sheetIndexAndName[0]);
fileDetails.SheetName = sheetIndexAndName[1];
db.SaveChanges();
return RedirectToAction("Build", "Sheets", new
{
ChainId = selectSheet.ChainId,
SheetId = selectSheet.SheetId,
FileId = selectSheet.FileId
});
}
// Probably not a good method vvv
var fileDetailsPostBack = db.FileDetails.Find(selectSheet.FileId);
string fileName = fileDetailsPostBack.UniqueName + fileDetailsPostBack.Extension;
string relativeFileLocation = "~/uploads/" + fileName;
string absoluteFileLocation = HttpContext.Server.MapPath(relativeFileLocation);
if (System.IO.File.Exists(absoluteFileLocation))
{
DSDBuilder builder = new DSDBuilder();
selectSheet.SheetsDropdown = builder.GetSheets(absoluteFileLocation);
}
else
{
ModelState.AddModelError("SheetDropdown", "Excel workbook does not exist.");
}
// Probably not a good method ^^^
return View(selectSheet);
}
View:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ChainId)
#Html.HiddenFor(model => model.SheetId)
#Html.HiddenFor(model => model.FileId)
#Html.HiddenFor(model => model.Header.ChainName)
#Html.HiddenFor(model => model.Header.SheetName)
#Html.HiddenFor(model => model.Header.SheetDescription)
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(model => model.SheetIndex, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SheetIndex, Model.SheetsDropdown, "Select...", new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SheetIndex, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Next" class="btn btn-default" />
</div>
</div>
</div>
}
I also would not like to repeat that code because it goes to a function which uses Excel.Interop and is kind of resource heavy. Let me know if anyone has a better solution. I'm always trying to improve my code and do things the "correct" way.

Related

how i can send multivalue to create action

i have a doctor i want add doctor subspecialty to the doctor from sub specialties table many to many relationship
i need to add subspecialties from multiselect list but my controller only add first selection , i want my create controller take all passed subspecialties and create it
my model
public partial class DoctorSubSpecialty
{
public int Id { get; set; }
public Nullable<int> DoctorId { get; set; }
public Nullable<int> SubSpecialtyId { get; set; }
public virtual DoctorProfile DoctorProfile { get; set; }
public virtual SubSpecialty SubSpecialty { get; set; }
}
}
create get action
public ActionResult Create()
{
ViewBag.DoctorId = new SelectList(db.DoctorProfiles, "Id", "FullName");
ViewBag.SubSpecialtyId = new MultiSelectList(db.SubSpecialties, "id", "Name");
return View();
}
create post action
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "Id,DoctorId,SubSpecialtyId")] DoctorSubSpecialty doctorSubSpecialty)
{
DoctorSubSpecialty doctorSub = db.DoctorSubSpecialties.Where(d => d.DoctorId == doctorSubSpecialty.DoctorId & d.SubSpecialtyId == doctorSubSpecialty.SubSpecialtyId).FirstOrDefault();
if (doctorSub == null) {
db.DoctorSubSpecialties.Add(doctorSubSpecialty);
await db.SaveChangesAsync();
}
my view
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>DoctorSubSpecialty</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.DoctorId, "DoctorId", htmlAttributes: new { #class = "control-label col-md-2", #id = "DoctorID" })
<div class="col-md-10">
#Html.DropDownList("DoctorId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.DoctorId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SubSpecialtyId, "SubSpecialtyId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("SubSpecialtyId",(MultiSelectList)ViewBag.SubSpecialtyId, htmlAttributes: new { #multiple = "multiple", #class = "form-control" })
#Html.ValidationMessageFor(model => model.SubSpecialtyId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Create a ViewModel specific to your usecase that can actually transport more than one Id.
I.e. you will need an int[] to bind the selection to.
A ViewModel also helps you to get rid of all this ViewBag and [Bind] nonsense.
public class CreateDoctorSubSpecialtyViewModel {
// These are the selected values to be posted back
public int DoctorId { get; set; }
public int[] SubSpecialtyIds { get; set; }
// These are the possible values for the dropdowns
public IEnumerable<SelectListItem> DoctorProfiles { get; set; }
public IEnumerable<SelectListItem> SubSpecialties { get; set; }
}
GET action - initialize the ViewModel and pass it to the View:
[HttpGet]
public ActionResult Create() {
var doctorProfiles = db.DoctorProfiles.Select(d =>
new SelectListItem {
Text = d.FullName,
Value = d.Id
}
).ToArray();
var subSpecialties = db.SubSpecialties.Select(s =>
new SelectListItem {
Text = s.Name,
Value = s.id
}
).ToArray();
var viewModel = new CreateDoctorSubSpecialtyViewModel {
DoctorProfiles = doctorProfiles,
SubSpecialties = subSpecialties
};
return View("Create", viewModel);
}
View "Create.cshtml" (styling removed for clarity) - tell MVC which ViewModel we want to use with #model:
#model CreateDoctorSubSpecialtyViewModel
#using (Html.BeginForm("Create", "YourControllerName", FormMethod.Post)) {
#Html.DropDownListFor(m => m.DoctorId, Model.DoctorProfiles)
#Html.DropDownListFor(m => m.SubSpecialtyIds, Model.SubSpecialties, new { multiple = "multiple" })
<input type="submit" />
}
POST action - use Linq Contains to test against multiple submitted SubSpecialtyIds:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(CreateDoctorSubSpecialtyViewModel postData) {
DoctorSubSpecialty[] allSelectedSubSpecialities = db.DoctorSubSpecialties
.Where(d => d.DoctorId == postData.DoctorId
&& postData.SubSpecialtyIds.Contains(d.SubSpecialtyId))
.ToArray();
// ...
}
EDIT #Html.DropDownListFor requires an IEnumerable<SelectListItem> as second parameter.

MVC MultipleModel DropdownlistFor SaveChanges()

HI i Have a MultipleModel View with CompanyName and EmployeeRange. CompanyName contains names of companies with a Relationship link to EmployeeRange (int) Field.
The Employee Range is basically
0-9
10-19
20-49
I can create and SaveChanges for a new Company Name Field.
Please help With Saving Selected value from the EmployeeRange DropDownListFor to DB.
Here is the Code
//MultipleModel.cs
public partial class MultipleModel
{
public MultipleModel()
{
CompanyEntities = new company();
EmployeeEntities = new Employee();
}
public company CompanyEntities { get; set; }
public Employee EmployeeEntities { get; set; }
}
//CompanyController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RegisterCompany(MultipleModel model)
{
if (ModelState.IsValid)
{
//GET VARIBALE FROM DB
var addComanyName = db.companies.Add(model.CompanyEntities);
//GET VARIABLLE
addComanyName.COMPANY_NAME = model.CompanyEntities.COMPANY_NAME;
//ADD VARIBALE TO DB
db.companies.Add(addComanyName);
db.SaveChanges();
return RedirectToAction("index");
}
return View();
}
//RegisterCompany.cshtml
#model EISystem.Models.MultipleModel
#Html.DropDownListFor(m => m.CompanyEntities, new SelectList(ViewBag.products, "Employees_Range_ID", "Employees_Range"), "Select Number of Employees")
?? How Do i view the DropDownListFor so that selected Value can be POST to Controller and later be saved to DB?
You should use another class which is ViewModel for your MultipleModel class.
Like:
public class MultipleModelViewModel
{
public int SelectedProductId { get; set; }
public List<Products> ProductList{ get; set; }
public string Name { get; set; }
}
In a View
#model MultipleModelViewModel
#using (Html.BeginForm("RegisterCompany", "Company", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.SelectedProductId , htmlAttributes: new { #class = "form-control" })
#Html.DropDownListFor(model => model.SelectedProductId, new SelectList(Model.ProductList, "Id", "Name"), "Select Product", new { #class = "form-control " })
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "form-control " })
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
</div>
<button type="Submit" class="btn btn-success " id="Save-btn">
Save
</button>
}
Controller:
//Get
public ActionResult RegisterCompany()
{
var model = new MultipleModelViewModel (){
Name = model.Name,
Products = db.Products.Select(x=>new {Id=x.Id, Name=x.Name).ToList()
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RegisterCompany(MultipleModelViewModel model)
{
if (ModelState.IsValid)
{
var company = new Company(){
Name = model.Name,
Product = db.Products.Find(model.SelectedProductId)
};
db.companies.Add(company);
db.SaveChanges();
return RedirectToAction("index");
}
return View();
}

ViewModel Contents are Null after Posting Form to Controller

So the ViewModel has 2 sets of data.
The CurrentDetails and UpdatedDetails. Both are instances of the same class which carries strings and whatnot inside etc.
This method has worked with all other views and models I've attempted with, but for THIS one instance, when the form is posted to the controller, its contents (CurrentDetails and UpdatedDetails) are both found to be null.
I've tried changing the parameter name from model to test and to other arbitrary things, but to no avail.
The one thing that worked (but is not a solution to me) is NOT having instances of the class inside the ViewModel, and just having the data there (but I don't see why I should be forced to do things this way.
Here's the controller:
[HttpPost]
public ActionResult FloristProfile(MerchantFloristProfileViewModel test)
{
if (!ModelState.IsValid)
return View(test);
using (var db = new ApplicationDbContext())
{
Florist florist = db.Florists.Find(MerchantBase.FloristID);
if (Request.Form["editSubmit"] != null)
{
florist.Name = test.UpdatedDetails.Name;
florist.Website = test.UpdatedDetails.Website;
db.SaveChanges();
return RedirectToAction("FloristProfile");
}
else if (Request.Form["photoSubmit"] != null)
{
if (test.CurrentDetails.File.ContentLength > 0)
{
CloudBlobContainer container = FlowerStorage.GetCloudBlobContainer();
string blobName = String.Format("florist_{0}.jpg", Guid.NewGuid().ToString());
CloudBlockBlob photoBlob = container.GetBlockBlobReference(blobName);
photoBlob.UploadFromStream(test.CurrentDetails.File.InputStream);
florist.LogoPath = blobName;
florist.isRendering = true;
db.SaveChanges();
return RedirectToAction("FloristProfile");
}
}
}
return Content("Invalid Request");
}
View:
#using (Html.BeginForm("FloristProfile", "Merchant", FormMethod.Post, new { #class = "form-horizontal" }))
{
#Html.ValidationSummary(false, "", new { #class = "text-danger" })
#Html.HiddenFor(x => x.CurrentDetails.FloristID)
#Html.HiddenFor(x => x.CurrentDetails.Name)
#Html.HiddenFor(x => x.CurrentDetails.StaffCount)
#Html.HiddenFor(x => x.CurrentDetails.StoreCount)
#Html.HiddenFor(x => x.CurrentDetails.Website)
<div class="form-group">
#Html.LabelFor(x => x.UpdatedDetails.Name, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#Html.TextBoxFor(x => x.UpdatedDetails.Name, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(x => x.UpdatedDetails.Website, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#Html.TextBoxFor(x => x.UpdatedDetails.Website, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" name="editSubmit" class="btn btn-success">Save</button>
</div>
</div>
}
ViewModel:
public class MerchantFloristProfileViewModel
{
public class FloristProfileDetails
{
public int FloristID { get; set; }
[Required(ErrorMessage = "Please Enter a Name")]
public string Name { get; set; }
[DataType(DataType.Url)]
[Required(ErrorMessage = "Please Enter a Website")]
public string Website { get; set; }
public int StoreCount { get; set; }
public int StaffCount { get; set; }
// For Picture Upload
public HttpPostedFileBase File { get; set; }
}
public FloristProfileDetails CurrentDetails;
public FloristProfileDetails UpdatedDetails;
}
Both CurrentDetails and UpdatedDetails in your MerchantFloristProfileViewModel model are fields, not properties (no getter/setter) so the DefaultModelBinder cannnot set the values. Change them to
public FloristProfileDetails CurrentDetails { get; set; }
public FloristProfileDetails UpdatedDetails { get; set; }
But you should not be sending all that extra data to the view, then sending it all back again unchanged. Apart from the extra overhead, any malicious user could alter the values in the hidden fields causing your app to fail. Just get the original from the repository again if you need it in the POST method

Categories/Subcategories in asp.net mvc

We are making a marketplace like https://www.etsy.com/. And we have a problem in categorising the listings. We want to categories the item in the Listing in 3 levels, f.ex it has to be categories in this order:
Category 1
Sub Category 1.1
Sub Category 1.1.1
One of the important thing is that when you choose a category, f.ex. Electronics, then in the subcategory you can only see stuff like pc, smartphone, tv etc.
This is what we have now
public class Listing
{
public int ListingId { get; set; }
public String Name { get; set; }
public int Subcategory2Id { get; set; }
public virtual Subcategory2 Subcategory2 { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public String CategoryName { get; set; }
public virtual ICollection<Subcategory1> Subcategory1s { get; set; }
}
public class Subcategory1
{
public int Subcategory1Id { get; set; }
public String Subcategory1Name { get; set; }
public int CategoryId { get; set; }
public virtual Category Categories { get; set; }
public virtual ICollection<Subcategory2> Subcategory2s { get; set; }
}
public class Subcategory2
{
public int Subcategory2Id { get; set; }
public String Subcategory2Name { get; set; }
public int Subcategory1Id { get; set; }
public virtual Subcategory1 Subcategory1s { get; set; }
public virtual ICollection<Listing> Listings { get; set; }
}
and in the IdentityModels-ApplicationDbContext we have
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Listing> Listings { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Subcategory1> Subcategory1s { get; set; }
public DbSet<Subcategory2> Subcategory2s { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
The thing is we are not sure this is he right way to do it, and we dont know how to proceed, the meaning is that when you create a listing you have to have 3 drop down list, where you choose the respective categorys. So first you choose your category, and then you are able to select the subcategory 1 etc...
You should absolutely not have multiple category/subcategory entities. A category can have a parent and it can have children, but they're all "categories".
public class Category
{
public int Id { get; set; }
public int? ParentId { get; set; }
public virtual Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
}
ParentId is nullable, because top-level categories have no parent.
Entity Framework tends to get confused by self-referencing relationships, so you might need a little fluent config to help it out:
public class Category
{
// properties
public class Mapping : EntityTypeConfiguration<Category>
{
public class Mapping()
{
HasOptional(m => m.Parent).WithMany(m => m.Children);
}
}
}
Then, in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Category.Mapping());
}
With all that in place, when you're in your "Electronics" category, you'd show the subcategories simply by iterating over it's Children property.
UPDATE
If you need the full hierarchy rather than just one level at a time, you have a couple of options. First, you can just include multiple levels when querying:
db.Categories.Include("Children.Children");
That's not highly efficient, though, and I definitely would not recommend delving much deeper than tertiary children. However, that's all you're asking for, so this is still a workable method.
Second, you can create a stored procedure to walk the hierarchical structure for you. It's a little more complex, but with a combination of WITH and UNION ALL, you can create a flat representation of the hierarchy and then recursively use LINQ's GroupBy to work it back into a hierarchical structure.
There's a final potential third option in #Hackerman's recommendation of using HIERARCHYID, but unfortunately, to do that, you must completely remove Category from your EF context, which also means removing any direct relationships to it, as well. To relate a product to a category, you could only store the id (not as a foreign key), and then use that id to manually lookup the category in a second step. Unfortunately, while this solution makes dealing the the hierarchy easier, it makes doing everything else more difficult. Either way, it's up to you, though.
This seems to be a correct solution.
You can also use only one class (one DB table etc.) for all categories. Your "Category" class/table must then contain the reference of the parent category (nullable). That allows to make generic treatments for all categories.
For example, when the user create an item, you can display a dropdown list for the main category. If the user selects a category which contains other category, an other dropdownlist is displayed with the child categories, etc...
I giving here a example for category and subcategory with image upload.
public class ProductController : Controller
{
ApplicationDbContext db = new ApplicationDbContext();
// GET: Product
public ActionResult Index()
{
return View();
}
public ActionResult insert(int? id)
{
ViewBag.categoryList = db.Product.Where(x => x.CategoryId == 0).Select(x => new SelectListItem { Text = x.name, Value = x.Id.ToString() }).ToList();
var product = db.Product.Where(x => x.Id == id).Select(x => x).FirstOrDefault();
if (product == null) { product = new Product(); product.CategoryId = 0; }
return View(product);
}
[HttpPost]
public ActionResult insert(Product model)
{
if (Request.Files.Count > 0)
if (Request.Files["fileupload"].ContentLength > 0)
{
var fileupload = Request.Files[0];
var fileName = Path.GetFileName(fileupload.FileName);
model.Imagename = fileName;
model.ImageUrl = DateTime.Now.Ticks.ToString() + "." + fileName.Split('.')[1];
string baseurl = Server.MapPath("/") + "Images/" + model.ImageUrl;
fileupload.SaveAs(baseurl);
}
if (model.Id > 0)
{
var productEntity = db.Product.Where(x => x.Id == model.Id).Select(x => x).FirstOrDefault();
if (model.Imagename != null)
productEntity.Imagename = model.Imagename;
if (model.ImageUrl != null)
productEntity.ImageUrl = model.ImageUrl;
productEntity.name = model.name;
productEntity.CategoryId = model.CategoryId;
}
else
{
db.Product.Add(model);
}
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult ProductList()
{
var product = db.Product.Where(x => x.Id > 0).Select(x => x).ToList();
return View(product);
}
public ActionResult getsubcategory(int id)
{
var list = db.Product.Where(x => x.CategoryId == id)
.Select(x => new SelectListItem { Text = x.name, Value = x.Id.ToString() }).ToList();
return Json(list, JsonRequestBehavior.AllowGet);
}
}
This upper controller for insert update record.
Below html code :
#model WebApplication1.Models.Product
#{
ViewBag.Title = "insert";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>insert</h2>
#using (Html.BeginForm("insert","product", FormMethod.Post,new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Product</h4>
<hr />
#Html.HiddenFor(x=>x.Id)
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<label class="control-label col-md-2">SubCategory</label>
<div class="col-md-10">
#Html.DropDownList("SubCategory", new SelectList(ViewBag.categoryList, "Value", "Text", Model.CategoryId), "-Select-", new { #onchange = "categoryselect()", htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CategoryId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CategoryId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CategoryId, new SelectList(ViewBag.categoryList, "Value", "Text", Model.CategoryId),"-Select-", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CategoryId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Imagename, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input id="Imagename" name="fileupload" type="file" class = "form-control" />
#*#Html.(model => model.Imagename, new { htmlAttributes = new { #class = "form-control" } })*#
#Html.ValidationMessageFor(model => model.Imagename, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
<script>
function categoryselect () {
var d = $("#SubCategory option:selected").val();
$.ajax({
url: "/product/getsubcategory?id="+d
, type: "get"
, success: function (data) {
// alert(data)
$("#CategoryId").html('<option value="">-select- </option>');
for(var i=0;i<data.length;i++)
$("#CategoryId").append('<option value="' + data[i].Value + '">' + data[i].Text + '</option>')
}
})
}
</script>
model:
namespace WebApplication1.Models
{
public class Product
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int CategoryId { get; set; }
public string name { get; set; }
public string ImageUrl { get; set; }
public string Imagename { get; set; }
}
public class Category
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int PrentId { get; set; }
public string name { get; set; }
}
}
Index Page:
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<div id="productList">
</div>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$(document).ready(function () {
$.ajax({
url:"/product/productlist"
, type: "GET"
,success:function(data)
{
$("#productList").html(data)
}
})
})
</script>
List Page:
#model IEnumerable<WebApplication1.Models.Product>
<p>
#Html.ActionLink("Create New", "Insert")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CategoryId)
</th>
<th>
#Html.DisplayNameFor(model => model.name)
</th>
<th>
#Html.DisplayNameFor(model => model.ImageUrl)
</th>
<th>
#Html.DisplayNameFor(model => model.Imagename)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.CategoryId)
</td>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.ImageUrl)
</td>
<td>
#Html.DisplayFor(modelItem => item.Imagename)
</td>
<td>
#Html.ActionLink("Edit", "insert", new { id=item.Id })
</td>
</tr>
}
</table>

Better Way To Edit A View Model in MVC

I actually came up with a working example of how to display (GET) and edit (POST) a view model consisting of three models in MVC. However, my MVC skills are limited and I was looking for suggestions on "the right way" I should be doing this.
The part in question is that I'm sending the form fields back individually rather than to a view model, which is something I couldn't figure out how to do.
Here are my models
Name
public partial class Name
{
public Name()
{
this.Addresses = new HashSet<Address>();
this.Emails = new HashSet<Email>();
}
public int ID { get; set; }
public string FIRST_NAME { get; set; }
public string LAST_NAME { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
public virtual ICollection<Email> Emails { get; set; }
}
Address
public partial class Address
{
public int ADDRESS_ID { get; set; }
public int NameID { get; set; }
public string ADDRESS_1 { get; set; }
public string CITY { get; set; }
public string STATE { get; set; }
public string ZIP { get; set; }
public virtual Name Name { get; set; }
}
Email
public partial class Email
{
public int EMAIL_ID { get; set; }
public int NameID { get; set; }
public string EMAIL { get; set; }
public virtual Name Name { get; set; }
}
My View Model made up of all the fields from the three models.
public class ContactFormViewModel
{
public int? ID { get; set; }
public string FIRST_NAME { get; set; }
public string LAST_NAME { get; set; }
public string ADDRESS_1 { get; set; }
public string CITY { get; set; }
public string STATE { get; set; }
public string ZIP { get; set; }
public string EMAIL { get; set; }
}
The GET method of the Edit page (in the Controller)
// GET: Names/Edit/5
//The GET method takes the id from the URL and passes it into the query to return data for the specific record
public ActionResult Edit(int id)
{
//This query is an outer join of the Name, Address and Email models/tables
var query = from n in db.Names
join a in db.Addresses
on n.ID equals a.NameID into na
from a in na.DefaultIfEmpty()
join e in db.Emails
on n.ID equals e.NameID into ne
from e in ne.DefaultIfEmpty()
where n.ID == id
//Creates a new instance of the view model, populated with the query data
select new ContactFormViewModel
{
ID = id,
FIRST_NAME = n.FIRST_NAME,
LAST_NAME = n.LAST_NAME,
ADDRESS_1 = a.ADDRESS_1,
CITY = a.CITY,
STATE = a.STATE,
ZIP = a.ZIP,
EMAIL = e.EMAIL
};
//Returns the query to the view
return View(query);
}
The POST method of the Edit page (in the Controller)
// POST: Names/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
//The POST method takes the individual form field data and passes it to queries that update all three models separately
public ActionResult Edit(int id, string FIRST_NAME, string LAST_NAME, string ADDRESS_1, string CITY, string STATE, string ZIP, string EMAIL)
{
if (ModelState.IsValid)
{
//Query the database for the row to be updated.
var queryN =
from n in db.Names
where n.ID == id
select n;
var queryA =
from a in db.Addresses
where a.NameID == id
select a;
var queryE =
from e in db.Emails
where e.NameID == id
select e;
//Assign the form field data to the fields in the model
foreach (Name n in queryN)
{
n.FIRST_NAME = FIRST_NAME;
n.LAST_NAME = LAST_NAME;
}
//If there are no address records, insert
if (!queryA.Any())
{
//New instance of Address
var address = new Address
{
NameID = id,
ADDRESS_1 = ADDRESS_1,
CITY = CITY,
STATE = STATE,
ZIP = ZIP
};
db.Addresses.Add(address);
}
//Else, if there are address records, then update
else
{
foreach (Address a in queryA)
{
a.ADDRESS_1 = ADDRESS_1;
a.CITY = CITY;
a.STATE = STATE;
a.ZIP = ZIP;
}
}
//If there are no email records, insert
if (!queryE.Any())
{
//New instance of Email
var email = new Email
{
NameID = id,
EMAIL = EMAIL
};
db.Emails.Add(email);
}
//Else, if there are email records, then update
else
{
foreach (Email e in queryE)
{
e.EMAIL = EMAIL;
}
}
//// Submit the changes to the database.
try
{
db.SaveChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
// Provide for exceptions.
}
}
return RedirectToAction("Index");
}
The View
#model IQueryable<MDTestApplication.ViewModel.ContactFormViewModel>
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Name</h4>
<hr />
#*Uses foreach loop to get all field data from the view model*#
#foreach (var item in Model)
{
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(modelItem => item.ID)
<div class="form-group">
#Html.LabelFor(modelItem => item.FIRST_NAME, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#*Using razor syntax to output the value*#
#*Using form field 'Name' attribute for posting back to the controller*#
<input type="text" name="FIRST_NAME" value="#item.FIRST_NAME" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.LAST_NAME, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="LAST_NAME" value="#item.LAST_NAME" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.ADDRESS_1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="ADDRESS_1" value="#item.ADDRESS_1" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.CITY, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="CITY" value="#item.CITY" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.STATE, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="STATE" value="#item.STATE" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.ZIP, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="ZIP" value="#item.ZIP" class="form-control" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(modelItem => item.EMAIL, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="text" name="EMAIL" value="#item.EMAIL" class="form-control" />
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
UPDATE
Here's additional insert and update code I added to Alex's answer below. You would use this same setup for address and email.
foreach (var address in model.Addresses)
{
var addressToUpdate = name.Addresses.FirstOrDefault(a => AddressId== address.AddressId);
if (addressToUpdate != default(Address))
{
// preform an update
addressToUpdate.AddressId = address.AddressId;
addressToUpdate.City = address.City;
addressToUpdate.State = address.State;
addressToUpdate.Zip = address.Zip;
}
else
{
//perform an insert
var newAddress = new Address
{
NameID = model.ID,
Address1 = address.Address1,
City = address.City,
State = address.State,
Zip = address.Zip
};
db.Addresses.Add(newAddress);
}
}
First of all let me start with the naming convention.
This:
public int ADDRESS_ID { get; set; }
public int NameID { get; set; }
Is BAD You have no naming convention at all, some properties are PascalCase, others capital case with underscores. I strongly advise you to install some tool that will enforce you to apply set of style and consistency rules(for example StyleCop). In general it is very common to use PascalCase for properties.
Once you apply it your models will look like:
public partial class Address
{
public int AddressId { get; set; }
public int NameId { get; set; }
public string Address1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public virtual Name Name { get; set; }
}
Second thing:
If I understand correct you are trying to edit a data for one user:
His(or hers) first and last name, list of addresses and a lit of emails. If I am right both your View and ViewModel are wrong. Your ViewModel could look as following:
public class ContactFormViewModel
{
public int NameId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IList<Address> Addresses { get; set; }
public IList<Emails> { get; set; }
}
Controller(UPDATED):
// GET: Names/Edit/5
//The GET method takes the id from the URL and passes it into the query to
//return data for the specific record
public ActionResult Edit(int id)
{
//You don't need the joins since you have navigation properies!
var name = db.Names.FirstOrDefault(n => n.ID == id);
ContactFormViewModel model;
if(name == default(Name))
{
model = new ContactFormViewModel{
Addresses = new List<Address>(),
Emails = new List<Email>()
};
}
else {
model = new ContactFormViewModel
{
NameId = name.NameId ,
FirstName = name.FirstName,
LastName = name.LastName ,
Addresses = name.Addresses.ToList(),
Emails = name.Emails.ToList(),
};
}
if(!model.Addresses.Any())
{
model.Addresses.Add(new Address());
}
if(!model.Emails.Any())
{
model.Emails.Add(new Email());
}
//Returns the query to the view
return View(model);
}
View:
#model MDTestApplication.ViewModel.ContactFormViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Name</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.NameId)
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FirstName, new { #class = "FirstName" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LastName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => item.LastName, new { #class = "FirstName" })
</div>
</div>
#for (int i = 0; i < Model.Addresses.Count; i++)
{
#Html.HiddenFor(model => model.Addresses[i].AddressId)
<div class="form-group">
#Html.LabelFor(model => model.Addresses[i].Address1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Addresses[i].Address1, new { #class = "FirstName" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Addresses[i].City, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Addresses[i].City, new { #class = "FirstName" })
</div>
</div>
/// Continue here with all the address properties
}
#for (int i = 0; i < Model.Emails.Count; i++)
{
#Html.HiddenFor(model => model.Emails[i].EmailId)
<div class="form-group">
#Html.LabelFor(model => model.Emails[i].Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Emails[i].Email, new { #class = "FirstName" })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Edit Action in Controller:
public ActionResult Edit(ContactFormViewModel model)
{
if (ModelState.IsValid)
{
//Query the database for the row to be updated.
var name = db.Names.FirstOrDefault( n => n.NameId == model.NameId);
if(name != default(Name))
{
name.FirstName = model.FirstName;
name.LastName = model.LastName;
bool hasAddresses = name.Addresses.Any();
foreach(var address in model.Addresses)
{
var addressToUpdate = name.Addresses.FirstOrDefault(a => a.AddressId == address.AddressId);
if(addressToUpdate != default(Address))
{
// preform an update
}
else
{
//perform an insert
}
}
foreach(var email in model.Emails)
{
var emailToUpdate = name.Emails.FirstOrDefault(a => a.EmailId == email.EmailId);
if(emailToUpdate != default(Email))
{
// preform an update
}
else
{
//perform an insert
}
}
db.SaveChanges();
}
}
}
What you are attempting to do is submit a variable length list, which is not something asp mvc can handle on its own. Let me explain:
You have a form, and inside the form is a foreach loop that creates editable fields for multiple records. ASP MVC can handle updating a single record just fine with its form helpers, however when you have multiple records that need updating, ASP MVC doesn't automatically create an indexing number for each record, which makes it impossible for it to keep track of which property belongs to which record. When you submit the form, MVC doesn't create this indexing for you, so you need to find alternative solutions.
I highly suggest you look at this blog and incorporate the helper that is provided to achieve your form submission:
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Resources