Passing part of a viewmodel to a controller - c#

I have a Customer index page that takes a CustomerIndexViewModel to populate the page with a list of customers, the time the request took, and many other pieces of information.
I have a CustomerSearchArgsModel inside the CustomerIndexViewModel.
public class CustomerIndexViewModel : BaseIndexViewModel
{
public IEnumerable<Customer> Customers{ get; set; }
public double RequestTime { get; set; }
public CustomerSearchArgsModel CustomerSearchArgsModel { get; set; }
public OtherTypes OtherType {get;set;}
}
public class CustomerSearchArgsModel
{
public string CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
On my Customer Index page I want to have something like -
#model CustomerIndexViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("Index","Customer",FormMethod.Post, new { id="searchSubmit"}))
{
#Html.LabelFor(model => model.CustomerSearchArgsModel.ConsumerID)
#Html.EditorFor(model => model.CustomerSearchArgsModel.ConsumerID)
#Html.LabelFor(model => model.CustomerSearchArgsModel.LastName)
#Html.EditorFor(model => model.CustomerSearchArgsModel.LastName)
#Html.LabelFor(model => model.CustomerSearchArgsModel.FirstName)
#Html.EditorFor(model => model.CustomerSearchArgsModel.FirstName)
<input type="submit" value="Search" />
}
I want to return the typed in values to the Index (POST) method on the Customer controller in a CustomerSearchArgsModel.
But I don't know how to return a Model that is different from the one defined at the top of the page.

You can put your "searchSubmit" form inside a partial view. Then pass the model.CustomerSearchArgsModel to the partial view. Be sure that model.CustomerSearchArgsModel is not null; otherwise, you will get an exception.
Index Page
#Html.Partial("_search", model.CustomerSearchArgsModel)
_search Partial View
#model CustomerSearchArgsModel
#using (Html.BeginForm("Index","Customer",FormMethod.Post, new { id="searchSubmit"}))
{
#Html.LabelFor(model => model.ConsumerID)
#Html.EditorFor(model => model.ConsumerID)
#Html.LabelFor(model => model.LastName)
#Html.EditorFor(model => model.LastName)
#Html.LabelFor(model => model.FirstName)
#Html.EditorFor(model => model.FirstName)
<input type="submit" value="Search" />
}
The problem with this approach is you will get a 0 value displayed in your textbox for ConsumerID. To solve this problem you can use Html.Action instead of Html.Partial.
Hope this helps.

Related

c# MVC ViewModel is not passing to controller

I've got a model:
public class PersonViewModel
{
public int Id { get; set; }
[MaxLength(25)]
public string Firstname { get; set; }
[MaxLength(50)]
public string Surname { get; set; }
}
And a ViewModel to contain one instance and a list of the above:
public class PeopleSearchViewModel
{
public PersonViewModel Person { get; set; }
public List<PersonViewModel> People { get; set; }
}
Then my View:
#model PeopleSearchViewModel
#using (Html.BeginForm("Search", "Home", FormMethod.Post))
{
#Html.LabelFor(m => m.Person.Firstname)
#Html.HiddenFor(m => m.Person.Firstname)
#Html.TextBoxFor(m => m.Person.Firstname)
#Html.ValidationMessageFor(m => m.Person.Firstname)
<input type="submit" value="Search" id="Whatever"/>
}
And finally the controller:
[HttpPost]
public ActionResult Search(PeopleSearchViewModel theModelIsntPassing)
{
}
The model is not being passed to the controller on form submission?
Or maybe it is, but the individual properties aren't populated.
The ActionResult Search method is definitely being called, just theModelIsntPassing has no values in its nested property
Not sure why you are having both HiddenFor and TextBoxFor for FirstName.
Please try comment/remove #Html.HiddenFor(m => m.Person.Firstname) statement in your view
#model PeopleSearchViewModel
#using (Html.BeginForm("Search", "Home", FormMethod.Post))
{
#Html.LabelFor(m => m.Person.Firstname)
#*#Html.HiddenFor(m => m.Person.Firstname)*#
#Html.TextBoxFor(m => m.Person.Firstname)
#Html.ValidationMessageFor(m => m.Person.Firstname)
<input type="submit" value="Search" id="Whatever"/>
}

Entry saved to two tables has one to many relationship in mvc 4 code first

I simplified my problem as i could ,I have two classes Employee ,Departments
I supposed to make a one to many relationship between them
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string salary { get; set; }
public virtual Department depatments { get; set; }
}
//////////////////////
public class Department
{
public Department()
{
this.employees = new HashSet<Employee>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> employees { get; set; }
}
and that is my context class
public class myEntity :DbContext
{
public DbSet<Employee> employees {get;set;}
public DbSet<Department> Departments {get;set;}
}
well, I have created a Create ActionResult in order to be able to add new record to Employee and filled a selectlist with Department table/Class
And sent it to view by viewbag
[HttpGet]
public ActionResult Create()
{
ViewBag.xx = new SelectList(mycontext.Departments, "Id", "Name");
return View();
}
[HttpPost]
public ActionResult Create(Employee emp)
{
mycontext.employees.Add(emp);
mycontext.SaveChanges();
return RedirectToAction("Index", "Employee");
}
And there is the view
**#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Employee</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.salary)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.salary)
#Html.ValidationMessageFor(model => model.salary)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.depatments)
</div>
<div class="editor-field">
#Html.DropDownListFor(model =>model.depatments.Id,#ViewBag.xx as SelectList)
#Html.ValidationMessageFor(model => model.salary)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
I ran it, every thing is ok and database created.
the view dropdownlist render ID And Name as it should.
Normally ,Every Employee record should have Department ID as a Foreign Key.
The problem is When i add new record to Employee it add a new record in Department where it should pick just ID and add it to Employee
Note: I filled the Department first With no problem
If you use a different context to save then it should work without this issue. Your Departments entity is still being tracked in the first context and this is the reason for the issue.

How to POST a form containing Editor Templates for an ICollection?

I'm working with ASP.NET MVC 4 and I have the following models:
public class MultiLanguageText
{
[Key] public int Id { get; set; }
[Required] public string Name { get; set; }
public virtual ICollection<LocalizedText> LocalizedTexts { get; set; }
}
public class LocalizedText
{
[Key] public int Id { get; set; }
[ForeignKey("Language")] public int LanguageId { get; set; }
public virtual Language Language { get; set; }
[ForeignKey("MultiLanguageText")] public int MultiLanguageTextId { get; set; }
public virtual MultiLanguageText MultiLanguageText { get; set; }
public string Text { get; set; }
}
I'm trying to create an Edit view for MultiLanguageText which will also contain an editor for every item in the LocalizedTexts property. The form goes like this:
#using (Html.BeginForm("Edit", "MultiLanguageText", FormMethod.Post)) {
#Html.ValidationSummary(true)
<fieldset>
<legend>MultiLanguageText</legend>
#Html.HiddenFor(model => model.Id)
<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>
#Html.EditorFor(model => model.LocalizedTexts)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
The editor template I've created (in the file ~/Views/Shared/EditorTemplates/LocalizedText.cshtml) is the following:
<fieldset>
<legend>LocalizedText</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.LanguageId)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LanguageId)
#Html.ValidationMessageFor(model => model.LanguageId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MultiLanguageTextId)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MultiLanguageTextId)
#Html.ValidationMessageFor(model => model.MultiLanguageTextId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Text)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Text)
#Html.ValidationMessageFor(model => model.Text)
</div>
</fieldset>
I'm testing with some pre-existing values and all the data is shown properly in the parent and in the child editors, when the page is shown. However, when I submit the form, all changes done inside any of the child editors are ignored.
My HttpPost method is this:
[HttpPost]
public ActionResult Edit(MultiLanguageText multilanguagetext)
{
if (ModelState.IsValid)
{
db.Entry(multilanguagetext).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(multilanguagetext);
}
My question is: How do I make it so that all values are posted, from both the parent model and its children model editors? (I've seen that other similar questions exist, but I've tried their solutions and they didn't seem to fix my problem.)
I'm testing your question. I think it's done and all values posted. But I think you need provide more information to fix your problem
After some trying, this is the solution that I found to work:
[HttpPost]
public ActionResult Edit(MultiLanguageText multilanguagetext)
{
if (ModelState.IsValid)
{
db.Entry(multilanguagetext).State = EntityState.Modified;
foreach (var local in multilanguagetext.LocalizedTexts) db.Entry(local).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(multilanguagetext);
}

No parameterless constructor defined for this object- Unable to Solve

I am new to C# and MVC so please forgive any naiveness/stupidity. I have seen multiple posts dealing with the error I have and I have tried all the solutions but they dont seem to work in my case. Please guide me to the right way to solve this problem.
Background:
I have 2 models tblPrograms and tblReports.
I am trying to build a simple form where the user can create a new tblReport. Part of the process of creating a new tblReport is to select one tblProgram from a list.
Code Overview:
Controller:
// GET: /Reports/
public ViewResult New()
{
ProgramListModel progList = new ProgramListModel();
tblReports2 report = new tblReports2();
ExistingReportViewModel ervm = new ExistingReportViewModel(report,progList);
return View(ervm);
}
[HttpPost]
public ActionResult GenerateSQL(ExistingReportViewModel evrm)
{
evrm.Report.ProgramName = evrm.progList.selectedProgram;
return View(evrm);
}
View Model:
public class ExistingReportViewModel
{
public tblReports2 Report { get; set; }
public ProgramListModel progList { get; set; }
//public ExistingReportViewModel() { }
public ExistingReportViewModel(tblReports2 report, ProgramListModel progList)
{
this.Report = report;
this.progList = progList;
}
}
Models:
public class tblReports2
{
[Key]
public string ProgramName { get; set; }
public string ReportDesc { get; set; }
....
}
public class ProgramListModel
{
public SelectList progList;
public string selectedProgram;
public ProgramListModel() {
ReportHelper helper = new ReportHelper();
this.progList = helper.getProgramList();
}
}
Helper Method:
public SelectList getProgramList()
{
var programs = from p in this.DB.tblProgramsSet
select p;
programs = programs.Where(s => s.ProgramType.Equals("RPT"));
SelectList list = new SelectList(programs, "ProgramName", "ProgramDescription");
return list;
}
View:
#model ReportsSetup.Models.ExistingReportViewModel
#{
ViewBag.Title = "New";
}
New
#using (Html.BeginForm("GenerateSQL", "Reports"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>ExistingReportViewModel</legend>
#Html.DropDownListFor(model=>model.progList.selectedProgram, Model.progList.progList)
<div class="editor-label">
#Html.LabelFor(model => model.Report.ReportDesc)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Report.ReportDesc)
#Html.ValidationMessageFor(model => model.Report.ReportDesc)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Report.Formerly)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Report.Formerly)
#Html.ValidationMessageFor(model => model.Report.Formerly)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Report.ExtendedDesc)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Report.ExtendedDesc)
#Html.ValidationMessageFor(model => model.Report.ExtendedDesc)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Report.ReportCode)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Report.ReportCode)
#Html.ValidationMessageFor(model => model.Report.ReportCode)
</div>
...
<input type="submit" value="Create" />
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Error:
![Exception Details: System.MissingMethodException: No parameterless constructor defined for this object.][1]
http://i.stack.imgur.com/GHJpG.jpg
Attempted Solutions:
Refactored the code to use IEnumerable <tblPrograms> instead of
SelectList in ProgramListModel and converting to
IEnumerable in View using a function similar to
sample below:
public static IEnumerable<SelectListItem> ToSelectListItems(
this IEnumerable<Album> albums, int selectedId)
{
return
albums.OrderBy(album => album.Name)
.Select(album =>
new SelectListItem
{
Selected = (album.ID == selectedId),
Text = album.Name,
Value = album.ID.ToString()
});
}
Refactored the code to use IEnumerable<SelectListItem> instead of
SelectList in ProgramListModel
Many Thanks in Advance!!
No, you can't do that:
public ActionResult GenerateSQL(ExistingReportViewModel evrm)
if your ExistingReportViewModel doesn't have a parameterless constructor. The default model binder doesn't know how to instantiate this class if it doesn't have a default constructor.
So you have 2 possibilities:
Define a parametrless constructor to this class:
public class ExistingReportViewModel
{
public ExistingReportViewModel()
{ }
... some other stuff of course
}
Write a custom model binder
Definitely go for 1. unless you need something very special. If you need something very special then please define it precisely, so that we can help you with 2.

Pass a model to a partial view?

this is my partial:
#model RazorSharpBlog.Models.MarkdownTextAreaModel
<div class="wmd-panel">
<div id="wmd-button-bar-#Model.Name"></div>
#Html.TextAreaFor(m => m.Name, new { #id = "wmd-input-" + #Model.Name, #class = "wmd-input" })
</div>
<div class="wmd-panel-separator"></div>
<div id="wmd-preview-#Model.Name" class="wmd-panel wmd-preview"></div>
<div class="wmd-panel-separator"></div>
I'm trying to include it like this in my View:
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.Title)
#Html.TextBoxFor(m => m.Title)
#Html.Partial("MarkdownTextArea", new { Name = "content" })
<input type="submit" value="Post" />
}
these are the model classes:
public class MarkdownTextAreaModel
{
[Required]
public string Name { get; set; }
}
public class BlogContentModel
{
[Required]
[Display(Name = "Post Title")]
public string Title { get; set; }
[Required]
[DataType(DataType.MultilineText)]
[Display(Name = "Post Content")]
public string Content { get; set; }
}
What am I doing wrong, how should I do this in order to make my partial reusable?
Your partial expects an instance of the MarkdownTextAreaModel class. So do so, instead of passing an anonymous object which would throw anyways:
#Html.Partial("MarkdownTextArea", new MarkdownTextAreaModel { Name = "content" })
Now this being said a far better solution would be to adapt your view model, so that it contains a reference to MarkdownTextAreaModel and use editor templates instead of partials in your views, just like so:
public class BlogContentModel
{
[Required]
[Display(Name = "Post Title")]
public string Title { get; set; }
[Required]
[DataType(DataType.MultilineText)]
[Display(Name = "Post Content")]
public string Content { get; set; }
public MarkdownTextAreaModel MarkDown { get; set; }
}
then of course readapt the controller serving this view so that it populates the MarkDown of your view model:
public ActionResult Foo()
{
BlogContentModel model = .... fetch this model from somewhere (a repository?)
model.MarkDown = new MarkdownTextAreaModel
{
Name = "contect"
};
return View(model);
}
and then inside your main view simply:
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.Title)
#Html.TextBoxFor(m => m.Title)
#Html.EditorFor(x => x.MarkDown)
<input type="submit" value="Post" />
}
and then in order to follow standard conventions move your partial to ~/Views/YourControllerName/EditorTemplates/MarkdownTextAreaModel.cshtml and now everything will magically come into place as it should.
#using (Html.BeginForm()) {
#Html.LabelFor(m => m.Title) #Html.TextBoxFor(m => m.Title)
#Html.Partial("MarkdownTextArea", new MarkdownTextAreaModel { Name = "content" })
<input type="submit" value="Post" />
}

Categories

Resources