Generic View and Controller to avoid Duplicate Code in MVC - c#

I have lots of models with same basic structure in my MVC project. So, I created a master class like below.
public class MasterTemplate
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Description { get; set; }
public DateTime? UpdatedOn { get; set; }
public string UpdatedBy { get; set; }
}
And I created all my model classes like below.
public class Industry : MasterTemplate
{
}
public class Caste : MasterTemplate
{
}
public class Gender : MasterTemplate
{
}
public class Qualification : MasterTemplate
{
}
public class BloodGroup: MasterTemplate
{
}
There are many more like this. Following is my code for IndustryController.
public class IndustryController : Controller
{
private ApplicationDbContext _context { get; set; }
private string UserId { get; set; }
public IndustryController()
{
_context = new ApplicationDbContext();
UserId = System.Web.HttpContext.Current.User.Identity.GetUserId();
}
public ActionResult Index(int id = 0)
{
Industry data = new Industry();
if (id > 0)
data = _context.Industries.SingleOrDefault(c => c.Id == id);
if (data == null)
data = new Industry();
return View("Industry", data);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(Industry data)
{
if (!ModelState.IsValid)
return View("Industry", data);
var record = _context.Industries.Where(c => c.Description.Trim().ToLower() == data.Description.Trim().ToLower() && c.Id != data.Id);
if (record.Count() > 0)
{
ModelState.AddModelError("Duplicate Industry", "Industry already exist");
return View("Industry", data);
}
Industry cm = new Industry();
if (data.Id >= 1)
{
cm = _context.Industries.SingleOrDefault(c => c.Id == data.Id);
cm.Description = data.Description;
cm.UpdatedOn = DateTime.Now;
cm.UpdatedBy = UserId;
}
else
{
cm = data;
_context.Industries.Add(cm);
}
_context.SaveChanges();
return RedirectToAction("Index", new { id = 0 });
}
And following is my code for IndustryView
#model Industry
#{
ViewBag.Title = "Industries";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h3>Industries Management</h3>
<div class="row">
<div class="col-md-4">
#using (#Html.BeginForm("Save", "Industry"))
{
#Html.ValidationSummary("Please correct the following")
#Html.HiddenFor(m => m.Id)
<div class="form-group">
<div>
#Html.LabelFor(m => m.Description)
#Html.TextBoxFor(m => m.Description, new { #class = "form-control", autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Description)
</div>
</div>
#Html.AntiForgeryToken()
<button type="submit" class="btn btn-primary btn-sm">Save</button>
}
</div>
<div class="col-md-8">
<table class="table table-sm" id="mydata">
<thead>
<tr>
<th>
Industries
</th>
<th>
</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
#section scripts
{
#Scripts.Render("~/bundles/jqueryval")
<script>
$(document).ready(function () {
$("#mydata").DataTable({
ajax: {
url: "/api/get/industries",
dataSrc: ""
},
columns: [
{
data: "description"
},
{
data: "id",
render: function (data) {
var url = '#Url.Action("Index", "Industry", new { id = "__data__" })';
return 'Edit';
}
}
]
});
});
</script>
}
Now my problem is, code for controller and views for all the models in my project is almost similar. It is as above. So, I wanted to generalize them and create a single controller and view which can be used for all my other models. I am new to generics, tried the following code, but still not able to figure out the way forward. It is so confusing for me.
public interface IMaster
{
int Id { get; set; }
string Description { get; set; }
}
public class GenericController : Controller
{
private ApplicationDbContext _context { get; set; }
private string UserId { get; set; }
public GenericController()
{
_context = new ApplicationDbContext();
UserId = System.Web.HttpContext.Current.User.Identity.GetUserId();
}
public ActionResult Index(int id = 0)
{
IMaster data = null;
if (id > 0)
data = _context.Industries.SingleOrDefault(c => c.Id == id);
if (data == null)
data = new Industry();
return View("Generic", data);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(IMaster data)
{
if (!ModelState.IsValid)
return View("Generic", data);
var record = _context.Industries.Where(c => c.Description.Trim().ToLower() == data.Description.Trim().ToLower() && c.Id != data.Id);
if (record.Count() > 0)
{
ModelState.AddModelError("Duplicate Industry", "Industry already exist");
return View("Generic", data);
}
Industry cm = new Industry();
if (data.Id >= 1)
{
cm = _context.Industries.SingleOrDefault(c => c.Id == data.Id);
cm.Description = data.Description;
cm.UpdatedOn = DateTime.Now;
cm.UpdatedBy = UserId;
}
else
{
cm.Id = data.Id;
cm.Description = data.Description;
_context.Industries.Add(cm);
}
_context.SaveChanges();
return RedirectToAction("Index", new { id = 0 });
}
}
Can somebody guide me in right direction, need to create a generic controller and view for all the similar models in my project.

I have not run it, but I am pretty confident, that this should do the trick! Actually the only truly generic part is the controller. The other stuff is just the usual polymorphism. And thank you for the inspiration. It was fun thinking about such a solution. Maybe I'll build something similar in the future.
A word of caution: You will bind the name of your controllers to the name of each Model. Just be aware of this! There is a naming schema that must be kept or you break it.public class [ModelName]Controller : MasterController<ModelName>{}The ajax endpoints will end with the value of [PluralName](Read on in the View to know, what I mean.)
You will need an additional Property in the MasterTemplate. Ideally make it abstract, so you won't forget to implement it in the derived classes. This is for the Plural Name in the View's header and the ajax call in the View.
public abstract class MasterTemplate
{
[Key]
public int Id { get; set; }
public abstract string PluralName {get;}
[Required]
[StringLength(255)]
public string Description { get; set; }
public DateTime? UpdatedOn { get; set; }
public string UpdatedBy { get; set; }
}
Industry will then look like this
public class Industry: MasterTemplate
{
public override string PluralName => "Industries"
}
Make a truly generic Controller and derive all other Controllers from it like
public class IndustryController : MasterController<Industry>
{
//done everthing else is in the master :)
}
And here the generic MasterController<T>.
public class MasterController<T> : Controller where T : MasterTemplate, new()
{
private ApplicationDbContext _context { get; set; }
private string UserId { get; set; }
public MasterController()
{
_context = new ApplicationDbContext();
UserId = System.Web.HttpContext.Current.User.Identity.GetUserId();
}
public ActionResult Index(int id = 0)
{
T data = (id > 0)
? data = _context.Set<T>().SingleOrDefault(c => c.Id == id) ?? new T()
: new T();
return View("View", data);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(T data)
{
if (!ModelState.IsValid)
return View("View", data);
var record = _context.Set<T>().Where(c => c.Description.Trim().ToLowerInvariant() == data.Description.Trim().ToLowerInvariant() && c.Id != data.Id);
if (record.Count() > 0)
{
ModelState.AddModelError($"Duplicate {typeof(T).Name}", $"{typeof(T).Name} already exist");
return View("View", data);
}
if (data.Id >= 1)
{
T cm = _context.Set<T>().SingleOrDefault(c => c.Id == data.Id);
cm.Description = data.Description;
cm.UpdatedOn = DateTime.Now;
cm.UpdatedBy = UserId;
}
else
{
_context.Set<T>().Add(data);
}
_context.SaveChanges();
return RedirectToAction("Index", new { id = 0 });
}
Name the View "View" (or just the same, as you call it in the MasterController) and place it in the Shared Folder, for every controller to find it there.
#model MasterTemplate
#{
string name = Model.GetType().Name;
ViewBag.Title = name;
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h3>#Model.PluralName Management</h3>
<div class="row">
<div class="col-md-4">
#using (#Html.BeginForm("Save", name))
{
#Html.ValidationSummary("Please correct the following")
#Html.HiddenFor(m => m.Id)
<div class="form-group">
<div>
#Html.LabelFor(m => m.Description)
#Html.TextBoxFor(m => m.Description, new { #class = "form-control", autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Description, $"{name} is required.", new { #class = "text-danger" })
</div>
</div>
#Html.AntiForgeryToken()
<button type="submit" class="btn btn-primary btn-sm">Save</button>
}
</div>
<div class="col-md-8">
<table class="table table-sm" id="mydata">
<thead>
<tr>
<th>
#(name)
</th>
<th>
</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
#section scripts
{
#Scripts.Render("~/bundles/jqueryval")
<script>
$(document).ready(function () {
$("#mydata").DataTable({
ajax: {
url: "/api/get/#(Model.PluralName)",
dataSrc: ""
},
columns: [
{
data: "description"
},
{
data: "id",
render: function (data) {
var url = '#Url.Action("Index", "#(name)", new { id = "__data__" })';
return 'Edit';
}
}
]
});
});
</script>
}

Related

Asp.Net Mvc hierarchical categories self-related with One-To-Many

I'm trying to create category tree. With this purpose i created some classes. I done somethings let me show you what i did and whats gone wrong.
I created a self-referenced model like below.
public class Category
{
public int Id { get; set; }
public string Title { get; set; }
public bool Published { get; set; }
public int? ParentId { get; set; }
public virtual Category RootCategory { get; set; }
public virtual ICollection<Category> SubCategories { get; set; }
}
and i mapped it like below
public class CategoryMap : EntityTypeConfiguration<Category>
{
public CategoryMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Title)
.IsRequired()
.HasMaxLength(150);
this.Property(t => t.Published)
.IsRequired();
// Table & Column Mappings
this.ToTable("Category");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Title).HasColumnName("Title");
this.Property(t => t.Published).HasColumnName("Published");
HasOptional(x => x.RootCategory).WithMany(x => x.SubCategories).HasForeignKey(x => x.ParentId).WillCascadeOnDelete(false);
}
}
To Create and Edit Category i created a ViewModel like below
public class CreateEditCategoryViewModel
{
[HiddenInput]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public bool Published { get; set; }
private int? _selectedCategory;
public int? SelectedCategory
{
get { return _selectedCategory; }
set { _selectedCategory = value; }
}
}
Here is my Controller Create action
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateEditCategoryViewModel viewModel)
{
if (ModelState.IsValid)
{
Category category = new Category();
if (viewModel.SelectedCategory > 0)
{
category.ParentId = viewModel.SelectedCategory;
}
category.Title = viewModel.Title;
category.Published = viewModel.Published;
db.Categories.Add(category);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.SelectedCategory = new SelectList(db.Categories, "Id", "Title", viewModel.SelectedCategory);
return View(viewModel);
}
Here is my Create view,
#model BiSahne.Areas.Administrator.Models.CreateEditCategoryViewModel
<div id="inputs" class="box g8">
<h2 class="box-ttl">Category Ekle</h2>
<div class="box-body form">
#Html.LabelFor(model => model.Title, new { #class = "label input g4" })
#Html.TextBoxFor(model => model.Title, new { #class = "g12" })
#Html.ValidationMessageFor(model => model.Title)
<span class="label g4">Published</span>
#Html.CheckBoxFor(model => model.Published, new { #class = "g1" })
<button type="submit" class="green btn-m flt-r g3">Kaydet</button>
</div>
</div>
<div class="box coll g8" style="height: 500px">
<div class="box-body form">
<span class="label g16">Categories</span>
<hr />
<select size="20" title="Select Category" name="SelectedCategory" class="form-control select" data-live-search="true">
#foreach (BiSahne.Models.Content.Category item in ViewBag.SelectedCategory)
{
if (item.RootCategory == null) //top level
{
<option value="#item.Id">#item.Title</option>
foreach (var item2 in item.SubCategories)
{
<option value="#item2.Id">---#item2.Title</option>
if (item2.SubCategories.Count > 0)
{
foreach (var item3 in item2.SubCategories)
{
<option value="#item3.Id">------#item3.Title</option>
if (item3.SubCategories.Count > 0)
{
foreach (var item4 in item3.SubCategories)
{
<option value="#item4.Id">------#item4.Title</option>
if (item4.SubCategories.Count > 0)
{
foreach (var item5 in item4.SubCategories)
{
<option value="#item5.Id">------#item5.Title</option>
}
}
}
}
}
}
}
}
}
</select>
</div>
</div>
}
This is my Edit action method.
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Category category = db.Categories.Find(id);
if (category == null)
{
return HttpNotFound();
}
CreateEditCategoryViewModel viewModel = new CreateEditCategoryViewModel()
{
Id = category.Id,
Title = category.Title,
Published = category.Published,
};
ViewBag.SelectedCategory = new SelectList(db.Categories, "Id", "Title", viewModel.SelectedCategory);
return View(viewModel);
}
Edit.cshtml view same as Create.cshtml view above.
I can create category and i can assign a parent for it. But when i'm trying to edit the category -and also when Create Action returns view due to ModelState is not valid- i'm getting error like below
An exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in System.Core.dll but was not handled in user code. Additional information: Cannot convert type 'System.Web.Mvc.SelectListItem' to 'BiSahne.Models.Content.Category'
Pointed to this line in edit.cshtml
#foreach (BiSahne.Models.Content.Category item in ViewBag.SelectedCategory)
{
How can i fix that problem? I have to list categories and select the selected category.

Using partial view updates asp.net ajax

Trying to display only one comment from the text box in the partial view.
To get some data you need the Session Controller:
private ConferenceContext context = new ConferenceContext();
//
// GET: /Session/
public ActionResult Index()
{
ConferenceContext context = new ConferenceContext();
List<Session> sessions = context.Sessions.ToList();
return View(sessions);
}
//
// GET: /Session/Details/5
public ActionResult Details(int id = 0)
{
Session session = context.Sessions.Find(id);
if (session == null)
{
return HttpNotFound();
}
return View(session);
}
Details View in the Session Folder:
#model Conference.Models.Session
<h3>
#Model.Title
</h3>
<div>
#Model.Abstract
</div>
#Html.Action("_GetForSession", "Comment", new { SessionID = Model.SessionID })
Then the CommentController, which is using the partial view _GetForSession to display the text from the Text Box:
ConferenceContext context = new ConferenceContext();
public PartialViewResult _GetForSession(Int32 sessionID)
{
ViewBag.SessionID = sessionID;
List<Comment> comments = context.Comments.Where(c => c.SessionID == sessionID).ToList();
return PartialView("_GetForSession", comments);
}
[ChildActionOnly()]
public PartialViewResult _CommentForm(Int32 sessionID)
{
Comment comment = new Comment() { SessionID = sessionID };
return PartialView("_CommentForm", comment);
}
[ValidateAntiForgeryToken()]
public PartialViewResult _Submit(Comment comment)
{
context.Comments.Add(comment);
context.SaveChanges();
List<Comment> comments = context.Comments.Where(c => c.SessionID == comment.SessionID).ToList();
ViewBag.SessionID = comment.SessionID;
return PartialView("_GetForSession", comments);
}
Here is the _GetForSession View from the Comment folder:
#model IEnumerable<Conference.Models.Comment>
<div id="comments">
<ul>
#foreach (var comment in Model)
{
<li>#comment.Content</li>
}
</ul>
#using(Ajax.BeginForm("_Submit", "Comment", new AjaxOptions() { UpdateTargetId = "comments" }))
{
#Html.AntiForgeryToken();
#Html.Action("_CommentForm", new { SessionID = ViewBag.SessionID })
}
</div>
The _GetForSession gets its data from the _CommentForm in the Comment folder:
#model Conference.Models.Comment
#Html.HiddenFor(m => m.SessionID)
<div>
#Html.LabelFor(m => m.Content)
#Html.EditorFor(m => m.Content)
</div>
<button type="submit">Submit Comment</button>
Now the main Context would be coming from ConferenceContext in the Models:
public class ConferenceContext : DbContext
{
public DbSet<Session> Sessions { get; set; }
public DbSet<Speaker> Speakers { get; set; }
public DbSet<Comment> Comments { get; set; }
}
And the Context itself from the ConferenceContextInitializer:
public class ConferenceContextInitializer : DropCreateDatabaseAlways<ConferenceContext>
{
protected override void Seed(ConferenceContext context)
{
context.Sessions.Add(
new Session()
{
Title = "Partial View",
Abstract = "View Within the Main",
Speaker = context.Speakers.Add(new Speaker()
{
Name = "John Smith",
EmailAddress = "johnsmith#nowhere.com"
})
});
context.SaveChanges();
}
}
So, my question is can it be possible to display only one comment in the partial view not two?

Why updateModel() dont update my model! in MVC ASP.NET for the nested IList<Object>?

Models :
public class Category
{
[Key]
public int Categoryid { get; set; }
[Required , StringLength(50)]
public string Categoryname { get; set; }
public virtual IList<SubCategory> SubCategories { get; set; }
}
public class SubCategory
{
[Key]
public int SubCategoryid { get; set; }
[Required, StringLength(50)]
public string SubCategoryname { get; set; }
public int Categoryid { get; set; }
public SubCategory( string name)
{
SubCategoryname = name;
}
public SubCategory()
{
}
}
Controller
[HttpPost]
public ActionResult Edit(Category CatModifications, int id, string txtSub, string SubCreate, string SaveAll)
{
Category Cat = context.Categories.Single(model => model.Categoryid == id);
UpdateModel(Cat, new string[] { "Categoryname" });
if (ModelState.IsValid)
{
// Create Sub
if (SubCreate != null)
{
if (txtSub != "")
{
context.SubCategories.Add(new SubCategory(txtSub) { Categoryid = Cat.Categoryid });
context.SaveChanges();
}
return RedirectToAction("Edit");
}
if (SaveAll != null)
{
// Edit Sub/Cat
for (int i = 0; i < Cat.SubCategories.Count; i++)
{
UpdateModel(Cat, new string[] { "SubCategories["+i+"].SubCategoryname" });<---------- Here my Model dont update with my subcat
}
context.Entry(Cat).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("index");
}
}
return View();
}
view
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<div>
#Html.LabelFor(model => model.Categoryname)<br/>
#Html.EditorFor(model => model.Categoryname)
</div>
<div>
#if (Model != null && Model.SubCategories != null)
{
#Html.LabelFor(model => model.SubCategories)<br/>
for (int i = 0; i < Model.SubCategories.Count; i++)
{
<input type="text" name="SubCategories[#i].SubCategoryname" value="#Model.SubCategories[#i].SubCategoryname"/>
//#Html.EditorFor(model => model.SubCategories[i].SubCategoryname)
#Html.ActionLink("Delete", "DeleteSub", "Categories", new {SubId = Model.SubCategories[i].SubCategoryid}, null)<br/>
}
<input type="text" value="" name="txtSub"/>
<input type="submit" value="Create SubCategory" name="SubCreate"/>
}
</div>
<div>
<input type="submit" value="Save" name="SaveAll" />
</div>
}
<div class="Back-Button">
#Html.ActionLink("Back to List", "Index")
</div>
-
so! my not nested list proprieties (categoryname) updating well but not my nested list one (subcategories[i]subcategoryname) ... any one know why or if i did somting wrong here ? ty a lot ...
Initialize public virtual IList<SubCategory> SubCategories { get; set; } in constructor
<input name="SubCategories[#i].SubCategoryname" value="#Model.SubCategories[#i].SubCategoryname"/>
ok the way im binding the data is workiing .... the proof :
the Request.Form.allkeys.tolist = Categoryname,SubCategories[0].SubCategoryname,txtSub,SaveAll
with all the good value in it ....
it just when im doing the updatemodel that not working .....
i got an category with the changes (CatModifications) witout id in it ...
and i go a cat witout the changes (Cat ) but with teh id in it
so i want to update "Cat" with the change of CatModifications
UpdateModel(Cat, new string[] { "SubCategories["+i+"].SubCategoryname" , "Categoryname" })
Categoryname work but not SubCategories["+i+"].SubCategoryname... and i know the value are in it .... so why it not updating it .....

Understanding Enumerables and ViewModels

I am trying to learn ASP.NET MVC (C#), and am currently tackling ViewModels. I understand the purpose of ViewModels, and I can get data through to the screen using them, but am having difficulties with understanding their relationship with - another new element for me - interfaces.
I want to achieve the following View:
You can see I have a simple initial insert form for adding a new staff member, with a dropdown for salutations. Following this, there is a second form for editing where I iterate through available staff members, placing their values into relevant input fields, where the salutation dropdown defaults to their relative salutation.
I have two Domain Models / tables, Prm_Staff and Prm_Salutation, which I am accessing (wrong word, I think) via the ViewModel Staff_VM:
public class Staff_VM
{
public int StaffID { get; set; }
public int SalutationID { get; set; }
public string FName { get; set; }
public string LName { get; set; }
public bool Active { get; set; }
public List<Prm_Salutation> AvailableSalutations { get; set; }
}
public class StaffMembers
{
public Staff_VM StaffVm;
public IEnumerable<Staff_VM> ListStaffVms;
}
In my controller:
var activeSalts = (from a in db.Prm_Salutations
where a.Active == true
orderby a.Desc ascending
select a).ToList();
var model = new StaffMembers
{
ListStaffVms = (from a in db.Prm_Staffs
where a.Active == true
orderby a.LName ascending
select new Staff_VM
{
StaffID = a.Prm_StaffID,
SalutationID = a.SalutationID,
FName = a.FName,
LName = a.LName,
Active = a.Active,
AvailableSalutations = activeSalts
}),
StaffVm = new Staff_VM()
{
AvailableSalutations = activeSalts
},
};
return View("StaffMembers", model);
In the View, I refer to that model #model November.ViewModels.StaffMembers:
#*Record New Staff Member*#
<tr>
<td>
#Html.DropDownListFor(
model => model.StaffVm.SalutationID,
Model.StaffVm.AvailableSalutations.Select(option => new SelectListItem
{
Text = option.Desc.ToString(),
Value = option.Prm_SalutationID.ToString()
}
),
"Choose...")
</td>
<td>#Html.EditorFor(model => model.StaffVm.FName)</td>
<td>#Html.EditorFor(model => model.StaffVm.LName)</td>
<td>#Html.EditorFor(model => model.StaffVm.Active)</td>
</tr>
#*Update Existing Staff Members*#
#foreach (var staff in Model.ListStaffVms)
{
<tr>
<td>#Html.HiddenFor(model => staff.StaffID)#Html.ValueFor(model => staff.StaffID) </td>
<td>
#Html.DropDownListFor(
model => staff.SalutationID, staff.AvailableSalutations.Select(option => new SelectListItem
{
Selected = (option.Prm_SalutationID == staff.SalutationID),
Text = option.Desc.ToString(),
Value = option.Prm_SalutationID.ToString()
}
),
"Choose...")
</td>
<td>#Html.EditorFor(model => staff.FName)</td>
<td>#Html.EditorFor(model => staff.LName)</td>
<td>#Html.EditorFor(model => staff.Active)</td>
<td>Delete</td>
</tr>
}
ActionResult:
public ActionResult UpdateStaff(StaffMembers list)
{
if (ModelState.IsValid)
{
foreach (var staffVm in list.ListStaffVms)
{
Prm_Staff staff = db.Prm_Staffs.Find(staffVm.StaffID);
staff.SalutationID = staffVm.SalutationID;
staff.FName = staffVm.FName;
staff.LName = staffVm.LName;
staff.Active = staffVm.Active;
}
db.SaveChanges();
ViewBag.rtrn = "Successfully Updated.";
return RedirectToAction("Parameters", new { param = "Staff Members", rtrn = ViewBag.rtrn });
}
else
{
ViewBag.rtrn = "Failed ! Please try again.";
return RedirectToAction("Parameters", new { param = "Staff Members", rtrn = ViewBag.rtrn });
}
}
EDIT: Updated to show most recent changes
I think you should consider change your ViewModel. Also do something like below:
ViewModel
public class Staff_VM
{
public int ID { get; set; }
public int SalutationID { get; set; }
public string FName { get; set; }
public string LName { get; set; }
public bool Active { get; set; }
}
public class MyViewModel
{
public Staff_VM StaffVm { get; set; }
public List<Staff_VM> ListStaffVms { get; set; }
public List<Prm_Salutation> AvailableSalutations { get; set; }
}
Add_Update_Staff Action
[HttpGet]
public ActionResult Add_Update_Staff()
{
var model = new MyViewModel
{
ListStaffVms = (from a in db.Prm_Staffs
where a.Active == true
orderby a.LName ascending
select new Staff_VM
{
ID = a.Id,
SalutationID = a.SalutationID,
FName = a.FName,
LName = a.LName,
Active = a.Active
}),
AvailableSalutations = (from p in db.Prm_Salutations
where a.Active == true
orderby p.Desc ascending
select p).ToList()
};
return View(model);
}
Update Staff Post
[HttpPost]
public ActionResult Add_Update_Staff(MyViewModel model, string buttonType)
{
if (buttonType == "Insert")
{
if (ModelState.IsValid)
{
//save a new staff info
return RedirectToAction("Index", "Home");
}
}
if (buttonType == "Update")
{
foreach (var staffVm in model.ListStaffVms)
{
// update each record here
}
return RedirectToAction("Index", "Home");
}
model.AvailableSalutations = (from p in db.Prm_Salutations
orderby p.Desc ascending
select p).ToList();
return View(model);
}
View
You may need to add validation for insert and update staff info
#using (Html.BeginForm("Add_Update_Staff", "Staff"))
{
<tr>
<td>
#Html.DropDownListFor(
model => model.StaffVm.SalutationID, Model.AvailableSalutations.Select(option => new SelectListItem
{
Text = option.Desc.ToString(),
Value = option.Prm_SalutationID.ToString()
}
), "Choose...")
</td>
<td>#Html.EditorFor(model => model.StaffVm.FName)</td>
<td>#Html.EditorFor(model => model.StaffVm.LName)</td>
<td>#Html.EditorFor(model => model.StaffVm.Active)</td>
</tr>
<input type="submit" value="Insert" name="buttonType" />
for (int i = 0; i < Model.ListStaffVms.Count(); i++)
{
<tr>
<td>#Html.HiddenFor(m => m.ListStaffVms[i].ID)#Html.ValueFor(m => m.ListStaffVms[i].ID) </td>
<td>
#Html.DropDownListFor(
m => m.ListStaffVms[i].SalutationID, Model.AvailableSalutations.Select(option => new SelectListItem
{
Selected = (option.Prm_SalutationID == Model.ListStaffVms[i].SalutationID),
Text = option.Desc.ToString(),
Value = option.Prm_SalutationID.ToString()
}), "Choose...")
</td>
<td>#Html.EditorFor(model => model.ListStaffVms[i].FName)</td>
<td>#Html.EditorFor(model => model.ListStaffVms[i].LName)</td>
<td>#Html.EditorFor(model => model.ListStaffVms[i].Active)</td>
<td>Delete</td>
<hr />
</tr>
}
<input type="submit" value="Update" name="buttonType" />
}

Model (complex type) won't submit to action

I have some problem posting a form with 'complex type' model:
I have a Model:
public class CircleEditViewModel
{
[Key]
public int CircleId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
public bool IsSystem { get; set; }
public class UserInCircle
{
public UserInCircle(User user)
{
this.UserId = user.UserId;
FullName = user.FullName;
}
public int UserId { get; set; }
public byte[] Picture { get; set; }
public string FullName { get; set; }
public bool isInCircle { get; set; }
}
public List<UserInCircle> Users { get; set; }
}
My first problem was that at post event, my Users where null.. so i followed a few posts on here (like MVC- Model Binding on a Complex Type) to use a for instead of a foreach,but since i did so, my form won't post anymore:
View:
#model Wims.Website.ViewModels.CircleEditViewModel
<script type="text/javascript">
$(document).ready(function () {
$.validator.unobtrusive.parse('form');
});
</script>
#using (Ajax.BeginForm(Html.ViewContext.RouteData.Values["Action"].ToString(), null, new AjaxOptions { HttpMethod = "POST", OnSuccess = "SaveDone(data)" }, new { id = "editform" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Circle</legend>
#Html.Label(DateTime.Now.ToString());
<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>
</fieldset>
if (Model.Users != null)
{
for (int i = 0; i < Model.Users.Count; i++)
{
<div class="userDetail">
<div>
<div>
#Html.DisplayFor(model => Model.Users[i].isInCircle);
</div>
<div class="iconDiv">
#Html.Image("~/Content/Images/defaultUser.jpg", Model.Users[i].FullName, null);
</div>
<div>
#Html.TextBoxFor(model => Model.Users[i].FullName)
#Html.HiddenFor(model => Model.Users[i].UserId)
</div>
</div>
</div>
<div style="clear: both"></div>
}
}
#Html.GenerateSecureDataControls(model => model.CircleId)
<input type="submit" value="Save" />
}
My view is rendered as a partial loaded thru ajax (not sure it makes any difference here).
Any idea why it won't post? If i remove all the '[]' like 'Users[0].FullName' to Users0.FullName i will post, but of course it won't be bound.
Thanks for your help
Edit just in case needed: Action:
[HttpPost]
public ActionResult Edit(CircleEditViewModel circleData, FormCollection collection)
{
if (ModelState.IsValid)
{
using (var logic = new CircleLogic())
{
Circle circle = logic.GetCircleById(circleData.CircleId, WebMatrix.WebData.WebSecurity.CurrentUserId);
if (circle == null)
{
return HttpNotFound();
}
else
{
circle.Name = circleData.Name;
logic.UpdateCircle(circle, GetSelectedUser(collection));
}
return PartialView("_CircleAndUsers", GetData(logic, circle.CircleId));
}
}
return this.Json(new { success = false, viewdata = RenderRazorViewToString("_CircleAndUsers", circleData) });
}
Pablo Romeo was right, i added a default ctor and it worked.

Categories

Resources