I'm new to MVC3 and I can't figure out how to use checkboxes in MVC.
I have a bunch of text in my view like
text1
text2
text3
text4
text5
submitbutton
This text is not related to any model its just plain text. I would like to place a checkbox for each item and a link it to the controller so that when a user selects some of the checkbox values and clicks on the submit button my controller picks up which items have been selected.
I tried using #html.checkbox("text"+ index) and tried the controller to be
[HttpPost]
public ActionResult controller(List<string> list)
{
}
But that doesn't pick up the list of selected items. Can you tell me what i'm doing wrong or another way to do it?
What i would do in this situation is to make those items to be a property of my ViewModel.
public class MyViewModel
{
public bool text1 { set;get}
public bool text2 { set;get;}
public bool SomeMeaningFullName { set;get;}
// Other properties for the view
}
and in my Get Action method i will return this ViewModel to my View
public ActionResult Edit()
{
MyViewModel objVM=new MyViewModel();
return View(objVM);
}
and in my View
#model MyViewModel
#using (Html.BeginForm("Edit","yourcontroller"))
{
#Html.LabelFor(Model.text1)
#Html.CheckBoxFor(Model.text1)
#Html.LabelFor(Model.text2)
#Html.CheckBoxFor(Model.text2)
<input type="submit" value="Save" />
}
Now this property value will be available in your post action method
[HttpPost]
public ActionResult Edit(MyViewModel objVM)
{
//Here you can access the properties of objVM and do whatever
}
Create a ViewModel with all of your values. Populate the ViewModel and send it to the view. When something is checked, you'll know what's what on the post.
public class MyModelViewModel
{
public List<CheckBoxes> CheckBoxList {get; set;}
// etc
}
public class CheckBoxes
{
public string Text {get; set;}
public bool Checked {get; set;}
}
[HttpPost]
public ActionResult controller(MyModelViewModel model)
{
foreach(var item in model.CheckBoxList)
{
if(item.Checked)
{
// do something with item.Text
}
}
}
Basically ViewModels are your friend. You want to have a separate ViewModel for each View, and it's what gets passed back and forth between the Controller and the View. You can then do your data parsing either in the controller, or (preferably) in a service layer.
Additional Reference:
Should ViewModels be used in every single View using MVC?
Related
The app is designed to allow the user to enter a an IP address for a local machine and and it will then return the HDD information for that machine. It starts out with a default value already in the TextAreaFor box and performs the query for that value. This part works with no problem. But when a user tries to enter in their own value and hit the Refresh button, it keeps coming up with the error Object reference not set to an instance of an object.
I'm not sure why this is happening. It seems to me that clicking the button submits a POST action, which should kick off the second method in the controller. The current model is then passed to the controller with the values in the TextAreaFor attached and the mainCode() method is run on the new values.
Edit: According to What is a NullReferenceException, and how do I fix it? I am pretty sure that I am returning an empty model from my controller. I just don't see how. The form field should be sending the controller everything contained in TextAreaFor so the model should not be empty.
Edit2: I did some testing and the model is getting returned alright, but the values from TextAreaFor are not. When the mainCode() tries to do some logic to startDrives.startingDrives, it can't because that variable is empty for some reason.
Model:
namespace RelengAdmin.Models
{
public class DriveInfo
{
public class DriveHolder
{
public string startingDrives {get; set;}
}
public DriveHolder startDrives = new DriveHolder();
public void mainCode()
{
/****Code to return the HDD size omitted****/
}
}
}
View:
#using (Html.BeginForm())
{
<input type="submit" value="Refresh" />
#Html.TextAreaFor(model => model.startDrives.startingDrives, new {#class = "HDDTextBox"})
}
Controller:
namespace RelengAdmin.Controllers
{
public class HDDCheckerController : Controller
{
[HttpGet]
public ActionResult Index()
{
DriveInfo myDrive = new DriveInfo();
myDrive.startDrives.startingDrives = "148.136.148.53"
myDrive.mainCode();
return View(myDrive);
}
[HttpPost]
public ActionResult Index(DriveInfo model)
{
model.mainCode();
return View(model);
}
}
}
The issue is that your model's startDrives property is not actually declared as a property with getters and setters, so the model binder won't bind to it. I was able to duplicate the issue locally, and solve it by declaring the startDrives as a property and initializing it in the constructor.
public class DriveInfo
{
public class DriveHolder
{
public string startingDrives { get; set; }
}
public DriveHolder startDrives { get; set; }
public DriveInfo()
{
startDrives = new DriveHolder();
}
public void mainCode()
{
/****Code to return the HDD size omitted****/
}
}
Your question is a bit unclear of where the model is actually null.. but I would assume that when you hit your button, it goes to the correct action, but there is nothing in model because you haven't passed any specific values..
so try this:
CSHTML
#using (Html.BeginForm())
{
<input type="submit" value="Refresh" />
#Html.TextArea("startingDrive", "148.136.148.53", new {#class = "HDDTextBox"})
}
Controller
[HttpPost]
public ActionResult Index(string startingDrive)
{
DriveInfo searchThisDrive = new DriveInfo();
searchThisDrive.startDrives.startingDrives = startingDrive;
searchThisDrive.mainCode();
return View(searchThisDrive);
}
Let me know if this helps!
Passing ViewModel to #Html.Partial
Have two ViewModels
public class RegisterVM
{
... some properties
public AddressVM AddressInformation { get; set; } //viewmodel
}
public class AddressVM {
public string Street1 { get; set; }
public string Street2 { get; set; }
public string PostalCode { get; set; }
}
When loading main view using VM:
#model ViewModels.RegisterVM
All field load. But When I add Partial View and pass viewmodel
#Html.Partial("_AddressDetails", Model.AddressInformation)
It fails
Error: Exception Details: System.NullReferenceException: Object reference not set to an instance of an object. Why does it fail?
The partial View _AddressDetails is expecting a
#model ViewModels.AddressVM
Update
Based on changes from Prashant,
When submitting the information The Address information is NULL.
In The controller:
[HttpPost]
public ActionResult Register(RegisterVM vm){
...
//when viewing vm.AddressInformation.Street1 is null. and there is a value
//Is there a different way of retrieving the values from partial view?
}
Thanks for reading.
The error is generated because property AddressInformation is null, and you need to initailize it in a parameterless constructor or in the controller before you pass it to the view, for example
public class RegisterVM
{
public RegisterVM() // default constructor
{
AddressInformation = new AddressVM();
}
public AddressVM AddressInformation { get; set; }
....
}
However you usage means that the controls generated will be
<input name="Street1" .../>
whereas they need to be
<input name="AddressInformation.Street1" .../>
in order to bind to your model. You can either make your partial an EditorTemplate (/Views/Shared/EditorTemplates/AddressVM.cshtml) and use in the main view as
#Html.EditorFor(m => m.AddressInformation)
or pass the prefix to the partial as additional ViewData
#Html.Partial("_AddressDetails", Model.AddressInformation, new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "AddressInformation" }})
This is working for me. You just need to instantiate your VM, attach it and send it to the view.
Page Action
public ActionResult Page(){
RegisterVM vm = new RegisterVM();
vm.AddressInformation = new AddressVM();
return View(vm);
}
Page.cshtml
#model Project.Web.Models.RegisterVM
<!-- loading partial view -->
#Html.Partial("_AddressDetails",Model.AddressInformation)
Partial View File
<input type="text" name="name" value=" " />
I am not having more info about code but as per mention details, can you try this
public ActionResult Register(){ return View(register); }
i know you may tried this but try to assigned explict value. as this is basic MVC implementation. if it not work out then you need to provide more code details.
hope this help.
in Register get Method must instatiate your viewModel because in view, call other partial with viewModel members(proprties);
public ActionResult Register(){
RegisterVM vm = new RegisterVM();
return View(vm);
}
I have a view which is bound with a ViewModel which contains multiple viewmodels.
Now, the parent view contains views(rendered by #html.partial) each view bound with its corresponding viewmodel and has its own form action.
My Question:
I could view the data correctly, but i can't submit each subview alone, so how can post each submodel alone?
Also, when there would be modelstate errors how can i refer to the correct subview?
Any idea would be appreciated.
Extra info:
The code sample shows what i did exactly:
ViewModel:
public class ViewModelParent
{
public ViewModelChild1 ViewModelC1 {get; set;}
public ViewModelChild2 ViewModelC2 {get; set;}
public ViewModelChild3 ViewModelC3 {get; set;}
}
Controller:
public ActionResult GetParent()
{
return view(new ViewModelParent());
}
Views:
GetParent.cshtml (contains views for each submodel).
#model Models.ViewModelParent
#Html.Partial("~/Views/Children/GetC1.cshtml", Model.ViewModelC1)
#Html.Partial("~/Views/Children/GetC2.cshtml", Model.ViewModelC2)
#Html.Partial("~/Views/Children/GetC3.cshtml", Model.ViewModelC3)
Children views:
GetC1.cshtml
#model ViewModelChild1
<form action="#Url.Action("GetC1", "Child"" method="POST" class="smart-form" id="frm_child1">
#Html.AntiForgeryToken()
#Html.ValidationSummary()
#* controls here*#
</form>
The same applies for the rest children views GetC2.cshtml & GetC3.cshtml
I've done something similar in the past.
I'd recommend this as a possible approach (assuming you want to stick with full page postbacks instead of going the ajax route).
Use your existing Parent ViewModel class (with child Models)
public class ViewModelParent
{
public ViewModelChild1 ViewModelC1 {get; set;}
public ViewModelChild2 ViewModelC2 {get; set;}
public ViewModelChild3 ViewModelC3 {get; set;}
}
Have the partial views each use the Parent Model
#model Models.ViewModelParent
#Html.Partial("~/Views/Children/GetC1.cshtml", Model)
#Html.Partial("~/Views/Children/GetC2.cshtml", Model)
#Html.Partial("~/Views/Children/GetC3.cshtml", Model)
The Child views each have the parent Model, but only contain form elements for the Child Model of that view. If you want a validation summary in every partial view you have to get a bit creative - I'll explain later...
eg: GetC1.cshtml
#model ViewModelParent
#using(Html.BeginForm("GetParent", "ParentControllerName", null, FormMethod.Post, new {#class="smart-form" id="frm_child1"}))
{
#Html.AntiForgeryToken()
#Html.ValidationSummaryForGroup(ViewBag.ChildType, "Child1") #* I'll explain this later *#
#* controls here - eg... *#
#Html.TextBoxFor(m => m.ViewModelChild1.Property1)
}
Then your controller can simply farm out the child methods if the form is valid (or return if not)
Eg:
public class ParentControllerNameController : Controller
{
public ActionResult GetParent()
{
return View(new ViewModelParent());
}
[HttpPost]
public ActionResult GetParent(ViewModelParent model)
{
if (ModelState.IsValid)
{
if (model.ViewModelC1 != null)
{
return GetC1(model.ViewModelC1);
}
else if (model.ViewModelC2 != null)
{
return GetC2(model.ViewModelC2)
}
else if (model.ViewModelC3 != null)
{
return GetC3(model.ViewModelC3)
}
} else {
// invalid!
if (model.ViewModelC1 != null)
{
ViewBag.ChildType = "Child1";
}
else if (model.ViewModelC2 != null)
{
ViewBag.ChildType = "Child2";
}
else if (model.ViewModelC3 != null)
{
ViewBag.ChildType = "Child3";
}
// needed to prevent null reference errors
if (model.ViewModelC1 == null) model.ViewModelC1 = new ViewModelChild1();
if (model.ViewModelC2 == null) model.ViewModelC2 = new ViewModelChild2();
if (model.ViewModelC3 == null) model.ViewModelC3 = new ViewModelChild3();
}
return View(model);
}
}
The above else-if statements will work, because each child view only contains properties for that child model - hence the other child viewmodels are null.
Note I used a new Html Helper extension above that I created that wraps the Validation Summary so you can display errors specific to the child model. A simple display/not display is insufficient because you'd lose client side validation errors being shown otherwise.
Of course this is only necessary when you have a validation summary in every partial view. If there's just one validation summary then you can stick with a simple #Html.ValidationSummary()
namespace System.Web.Mvc.Html
{
public static class ValidationSummaryForGroupExtensions
{
public static MvcHtmlString ValidationSummaryForGroup(this HtmlHelper html, string testValue, string expectedValue)
{
return ValidationSummaryForGroup(html, testValue, expectedValue, false);
}
/// <summary>
/// Displays a validation summary which shows serverside errors only if the specified testvalue and value are equal. Client side validation will work as normal.
/// <para>The purpose of this is to allow multiple valiation summaries (for multiple forms) on a single page.</para>
/// </summary>
/// <param name="testValue">Value to test (could be a value in viewbag)</param>
/// <param name="expectedValue">Value to expect if the server side errors are to be displayed.</param>
/// <returns></returns>
public static MvcHtmlString ValidationSummaryForGroup(this HtmlHelper html, string testValue, string expectedValue, bool excludePropertyErrors)
{
if (testValue != null && testValue.ToLower() == expectedValue.ToLower())
return html.ValidationSummary(excludePropertyErrors);
return new MvcHtmlString("<div class=\"validation-summary-valid\" data-valmsg-summary=\"true\"><ul><li style=\"display:none\"></li></ul></div>");
}
}
}
Of course you could do partial postbacks using ajax - in which case the child views could be directly for the child models, and each child form postback directly to the relevant method in your controller.
This is simple, in the handler of the post method of the controller, call the name of the parameter after the property name in the view model.
So in you view model you have:
public class ViewModelParent
{
public ViewModelChild1 viewModelC1 {get; set;}
public ViewModelChild2 viewModelC2 {get; set;}
public ViewModelChild3 viewModelC3 {get; set;}
}
In the post handler of your controller you will need something like:
<HttpPost()>
Function GetC1(viewModelC1 As ViewModelChild1 ) As ActionResult
in the html all the properties will be names like 'viewModelC1.nameofsomething' and this helps the model binder map the properties up. The above is VB.net but you should get the idea.
hope that helps
Andy
I am developing an application. I have created a view and a controller. The view has a button, on the click of which I am supposed to do database operations. I have put the database operations in the model, I am creating the object of model in the controller. On clicking the button the action is handled by a method in the controller, and the object of the model is created to get the records from the database. I would like to know if there is any way to display this data in the view.Is the approach correct or the view is supposed to interact with model directly to get the data.
Following is the code in controller that gets invoked on the button click
public ActionResult getRecord()
{
DataModel f_DM = new DataModel();
DataTable f_DT = f_DM.getRecord();
return View();
}
DataModel is the model class with simply a method "getRecord".
Any help will be highly appreciated.
I would like to add that i am using vs2010 and mvc4
Regards
you should write the logic of retrieving data in your controller. Store all your data in view model and pass it to the view.
for eg.
Model
namespace Mvc4App.Models
{
public class Product
{
public string Name { get; set; }
}
public class ProductViewModel
{
public Product Product { get; set; }
public string SalesPerson { get; set; }
}
}
Controller
public class ProductController : Controller
{
public ActionResult Info()
{
ProductViewModel ProductViewModel = new ProductViewModel
{
Product = new Product { Name = "Toy" },
SalesPerson = "Homer Simpson"
};
return View(ProductViewModel);
}
}
View
#model Mvc4App.Models.ProductViewModel
#{ ViewBag.Title = "Info"; }
<h2>Product: #Model.Product.Name</h2>
<p>Sold by: #Model.SalesPerson</p>
This is the best known practice to pass data from controller to the view.
you may use other techniques also like,
1. ViewData
2. ViewBag
3. TempData
4. View Model Object
5. Strongly-typed View Model Object
Yes, it's possible, but actually now very logical way to to this.
Lets follow your way. You have some View were you have a button, that will trigger this action.
For ex:
public ActionResult Index()
{
return View();
}
Inside view you can have a Ajax link, that will trigget your getRecord method:
<div id="GetDataDiv"></div>
<div>
#Ajax.ActionLink("Get Record", "getRecord", "ControllerName", null, new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = "GetDataDiv" })
</div>
In the getRecord method you should have:
public ActionResult getRecord()
{
DataModel f_DM = new DataModel();
DataTable f_DT = f_DM.getRecord();
return PartialView(f_DT);
}
And in View it should be:
#model DataTable
#Model.PropertyOne #Model.PropertyTwo
It should works for you.
Actually same exaple here: http://www.dotnetpools.com/Article/ArticleDetiail/?articleId=151
My scenario is much complicated so i simplified it with the example below. The main problem is binding collection properties of a model, specifying the path of the property like Html.TextBox("List[0].Name") and not the Html.TextBoxFor(t => t.List[0].Name). So, for the current view i will only know some of the metadata of the model so i will have to construct it this way. Here is the scenario :
Model
public class ModelTest
{
public int Id {get;set;}
public List<Foo> Collection {get;set;}
}
public class Foo
{
public string Value1 {get;set;}
public string Value2 {get;set;}
}
Controller
public class TestController: Controller
{
[HttpGet]
public ActionResult Test()
{
var model = new ModelTest()
{
Id = 455,
Collection = new List<Foo>()
{
new Foo(){ Value1 = "sagasga", Value2 = "Beul"},
new Foo(){ Value1 = "dgdsgds", Value2 = "fhfhd" }
}
};
return View(model);
}
[HttpPost]
public ActionResult Test( ModelTest model)
{
//....
return View();
}
View:
#using (Html.BeginForm())
{
#Html.TextBox("Id")
#Html.TextBox("Collection[0].Value1")
#Html.TextBox("Collection[0].Value2")
<input type="submit" value="Add" />
}
For the code above i get empty textboxes for the collection values. However, when the page is submited i get the model built correct in the Post method.
Many thanks,
Alex
This is the way to name you input fields when you wanna post a collection to your controller. However, you have to specify the initial value yourself. Your code is currently just creating textbox with the name property set to Collection[0].Value1. You still need to specify the input this way,
#Html.TextBox("Collection[0].Value1", Model.Collection.FirstOrDefault().Value1)
#Html.TextBox("Collection[0].Value2", Model.Collection.FirstOrDefault().Value2)