ASP.NET MVC 3 WITH RAZOR : How to pass selected checkbox' ids in a Partial view to controller action? - asp.net-mvc-3

I have a partialview [_SearchProduct] within the main view, let's say [product] view. The Partialview has a number of checkboxes segregated into different sections like search by company,search by product etc. with one [search] button.
A User can select multiple checkboxes. When user clicks [search] button I need to pass ids of all selected checkbox to controller action and re-render the page again considering the user's selection . Please guide me how to pass selected checkbox ids to my controller action.
My partial view is something like below:
<fieldset>
<legend>By Company</legend>
<table style="border-style: none;">
<tr>
#{
int i = 0;
foreach (var item in Model.CompanyName)
{
i = i + 1;
<td style="border-style: none;text-decoration:none;" >
#Html.CheckBox("chkCompany",new {id="chkCompany_" + Model.CompanyId.Tostring()}) #Model.CompanyName
</td>
if (i == 5)
{
#:</tr><tr>
i = 0;
}
}
}
</tr>
</table>
</fieldset>
<fieldset>
<legend>By Product</legend>
<table style="border-style: none;">
<tr>
#{
i = 0;
foreach (var item in Model.Product)
{
i = i + 1;
<td style="border-style: none;text-decoration:none;" >
#Html.CheckBox("chkProduct",new {id="chkProduct_" + Model.CompanyId.Tostring()}) #Model.ProductName
</td>
if (i == 10)
{
#:</tr><tr>
i = 0;
}
}
}
</tr>
</table>
</fieldset>
checkboxes are dynamic
Checkbox id represent the primarykey of respective table based on which i do filtering.
Please guide me>>

So it sounds like you have a structure containing names (of companies/products), and ids.
I would create a View Model structure that looked like
public class PartialViewModel //Make sure this is included in your main view model
{
public List<ObjectsToInclude> Companies { get; set; }
public List<ObjectsToInclude> Products { get; set; }
}
public class ObjectsToInclude //Give this a better name
{
public string Name { get; set; }
public string Id { get; set; }
public bool Include { get; set; }
}
Then in order to bind them you could do
for (int i =0; i<Model.Companies.Count(); i++)
{
<td style="border-style: none;text-decoration:none;" >
#Html.HiddenFor(m => m.Companies[i].Id)
#Html.CheckBoxFor(m => m.Companies[i].Include) #Model.Companies[i].Name
</td>
if (i == 5)
{
#:</tr><tr>
i = 0;
}
}
Then provided your post takes a parameter of PartialViewModel (or some MainViewModel where that contains an instance of PartialViewModel), you'll have lists of companies and products binded. You can loop through the list, and take the respective ids of anything checked to be included.
Edit: If you wanted a single comma separated array to be posted, it would be possible by by creating an onclick event for your checkboxes, and then setting a value of a hidden input every time a checkbox is clicked. But then your code would only work with JavaScript enabled. If you need a comma separated string, you can create it server side with the view model I suggested.
string companyIds = String.Join(",", model.Companies
.Where(company => company.Include)
.Select(company => company.Id));

http://dotnetnsqlcorner.blogspot.in/2012/09/multiple-checkboxes-in-mvc-3-and-post.html

Related

Updating only the partial view contained in a mvc 3 page?

I have a MVC 3 page that returns a list of user responses with a partial view called "memo" (which displays/add memos) for each response. When I add a memo to a response, it should update the db and the list of memos for that response. It should be partial page update via ajax, which effects only the partial view "memo".
The view Response.chtml that contains "memo":
#using (Html.BeginForm("Response", "User", FormMethod.Post, new { id = "UserResponse" }))
{
.... code removed ....
#foreach (var response in Model)
{
<div class="qna"><input type="text" id=#response.responseId value="#response.ResponseText" />
<div>#Html.Partial("_memo", response.responseId)</div>
}
.....
The partial page "_memo.chtml":
<div>add memo</div>
<ul id="memos">
#foreach (var memo in Model) {
<li>#memo.Text</li>
}
</ul>
<form method="post" id="memoForm"
action="#Url.Action("AddMemo")">
#Html.TextArea("Memo", new { rows = 5, cols = 50 })
<br />
<input type="submit" value="Add" />
</form>
Controller for view User/Response:
[HttpGet]
public ActionResult Response(id)
{
.....
return View(responses);
I just started with the code above, need help filling the blanks.
If I pass the response Id to the partial view, how do I pull the list of memos for that response? Will it involve ajax? (instead of ..Partial("_memo", response.memos))
How do I update the partial view via ajax call. What is ajax call (sample code) on the client side and how would the controller look? When the ajax call is successful, how do I update the list memos div="memos" to reflect the new memo?
Will the form action from Response conflict with form action of the partial view Memo?
Answers to Questions:
You shouldn't pass the responseId to the partial, you should pass the memo collection from your response object and make your partial view strongly typed to that collection.
See full code example below.
You don't need the form in the partial since you're making a simple ajax call to add the new memo. See full code example below.
This is a modified example from a project I am currently working on:
There is a bit of code to follow, so here goes:
This is my model. There are several sections on a career planning form, one of which is a section to select and update competencies. The SelectCompetencies model has a collection of competencies within it. The user will have the ability to add competencies. When they do, it will be added to the database and will update the list of competencies in the partial.
public class CareerPlanningFormViewModel
{
// code removed ...
public SelectCompetenciesModel SelectCompetencies { get; set; }
// code removed ...
}
public class SelectCompetenciesModel
{
public int CareerPlanningFormID { get; set; }
public IList<CompetencyModel> Competencies { get; set; }
public byte MaximumCompetenciesAllowed { get; set; }
}
public class CompetencyModel
{
public int CompetencyID { get; set; }
public int? CompetencyOptionID { get; set; }
public string ActionPlan { get; set; }
public IDictionary<int, string> CompetencyOptions { get; set; }
}
The main view of the career planning form: /Views/CPF/CareerPlanningForm.cshtml
#model MyNamespace.Models.CareerPlanningForm.CareerPlanningFormViewModel
<link rel="stylesheet" href="#Url.Content("~/Content/CreateCPF.css")" />
#using (Html.BeginForm())
{
// other sections loaded here...
// code removed for brevity...
#Html.Partial("SelectCompetencies", Model.SelectCompetencies)
// other sections loaded here...
// code removed for brevity...
}
The SelectCompetencies partial: /Views/CPF/SelectCompetencies.cshtml
The user will fill in the new action plan text and click the add competency button.
That will post via ajax to CPFController/NewCompetencyTemplate
#model MyNamespace.Models.CareerPlanningForm.SelectCompetenciesModel
#Html.HiddenFor(m => m.CareerPlanningFormID)
<h3>Select Competencies</h3>
<p class="guidance">
Select up to #Model.MaximumCompetenciesAllowed competencies to focus on improving.
</p>
<table id="CompetenciesTable">
<thead>
<tr>
<th>Competency</th>
<th>Action Plan:</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Competencies.Count(); i++)
{
#Html.EditorFor(m => m.Competencies[i])
}
</tbody>
<tfoot id="CompetenciesTableFooter" class="#(Model.Competencies.Count() < Model.MaximumCompetenciesAllowed ? "" : "hidden")">
<tr>
<td colspan="2">
#Html.TextArea("NewActionPlanText")
#Html.Button(ButtonType.Button, "Add Another Competency", "add", new { id = "AddCompetencyButton" })
</td>
</tr>
</tfoot>
</table>
#section script
{
<script>
jQuery(document).ready(function ($) {
var competenciesTableBody = $('#CompetenciesTable tbody'),
competenciesTableFooter = $('#CompetenciesTableFooter'),
addCompetencyButton = $('#AddCompetencyButton'),
newCompetencyTemplateUrl = '#Url.Content("~/CPF/NewCompetencyTemplate")',
count = competenciesTableBody.find('tr').length,
newActionPlanText = $('#NewActionPlanText'),
careerPlanningFormID = $('#CareerPlanningFormID');
addCompetencyButton.click(function () {
$.ajax({
url: newCompetencyTemplateUrl(),
type: 'POST',
data: {
careerPlanningFormID: careerPlanningFormID,
actionPlan: newActionPlanText,
itemCount: count
},
dataType: 'html',
success: function (data) {
var elements = $(data);
// other code removed here...
competenciesTableBody.append(elements);
// other code removed here...
}
});
});
});
</script>
}
Views/CPF/EditorTemplates/CompetencyModel.cshtml
#model MyNamespace.Models.CareerPlanningForm.CompetencyModel
<tr class="competency">
<td>
#Html.DropDownListFor(m => m.CompetencyOptionID, new SelectList(Model.CompetencyOptions, "Key", "Value"), "Select competency...")
</td>
<td>
#Html.TextAreaFor(m => m.ActionPlan, new { #class = "competencyActionPlan" })
#Html.HiddenFor(m => m.CompetencyID)
</td>
</tr>
The controller containing the action to add the new competency: /Controllers/CPFController.cs
This will call the CareerPlanningFormService to add the new competency and will return a partial view for NewCompetencyTemplate that will render out the new competency
public class CPFController : Controller
{
private readonly ICareerPlanningFormService careerPlanningFormService;
public CPFController(ICareerPlanningFormService careerPlanningFormService)
{
this.careerPlanningFormService = careerPlanningFormService;
}
[HttpPost]
public PartialViewResult NewCompetencyTemplate(int careerPlanningFormID, int itemCount, string newActionPlanText)
{
var count = itemCount + 1;
// Even though we're only rendering a single item template, we use a list
// to trick MVC into generating fields with correctly indexed name attributes
// i.e. Competencies[1].ActionPlan
var model = new SelectCompetenciesModel
{
Competencies = Enumerable.Repeat<CompetencyModel>(null, count).ToList()
};
model.Competencies[count - 1] = this.careerPlanningFormService.BuildNewCompetencyModel(careerPlanningFormID, newActionPlanText);
return this.PartialView(model);
}
}
My service class: CareerPlanningFormService.cs
This handles the business logic and makes the calls to the repository to add the item to the database and returns a new CompetencyModel
public class CareerPlanningFormService : ICareerPlanningFormService
{
private readonly IMyRenamedRepository repository;
private readonly IPrincipal currentUser;
public CareerPlanningFormService(
IMyRenamedRepository repository,
IPrincipal currentUser)
{
this.repository = repository;
this.currentUser = currentUser;
}
public CompetencyModel BuildNewCompetencyModel(int careerPlanningFormID, string newActionPlanText)
{
var competency = new Competency
{
CareerPlanningFormID = careerPlanningFormID,
CompetencyOptionID = null,
ActionPlan = newActionPlanText
};
this.repository.Add(competency);
this.repository.Commit();
return new CompetencyModel
{
CompetencyID = competency.CompetencyID,
CompetencyOptionID = competency.CompetencyOptionID,
ActionPlan = competency.ActionPlan,
CompetencyOptions = this.GetCompetencyOptionsForCareerPlanningFormID(careerPlanningFormID)
};
}
}
Now, the partial for NewCompetencyTemplate: Views/CPF/NewCompetencyTemplate.cshtml
This is very simple, it simply renders the same editor template as above, for the last competency in the collection (which we just added)
#model MyNamespace.Models.CareerPlanningForm.SelectCompetenciesViewModel
#Html.EditorFor(m => m.Competencies[Model.Competencies.Count() - 1])
When the ajax call succeeds, it will receive this partial back from the controller action method it called. It then takes the partial and appends it to the competencies table body
// snippet from ajax call above
competenciesTableBody.append(elements);
I hope this helps. Let me know if you have any additional questions.
While you're correct that you can do it just by returning a partial view containing the updated content, you may also consider using jQuery's load method.
Look here, in particular at the "loading page fragments" section. Basically you can just get the original page again and jQuery will "extract" the content you want as long as it can be targetted by a selector (such as a div id).
Note, this solution is not suitable in all cases as there will be redundant markup in the response from the server because you will be discarding the rest of the page content and just using the updated part.

Print a value which is later known in the view in Razor

Is there in Razor a way to print some HTML on the page
while the value later known in the view?
For example: I would like to print the sum of an expensive calculation, and that sum should be before the items in the HTML. But with the example below, it will always print 0.
I would like to calculate the sum only once.
I would like to solve this in the view, not in a csharp helper or on the client side (css, javascript etc)
#{
var sum = 0;
}
<table>
#* I would like to print here the sum which is known after the foreach loop*#
<tr><td>total: #sum</td></tr>
#foreach (var item in items)
{
<tr>
#{
var x= item.expensiveCalculation();
sum+= x;
}
//print item with x
<td>#x</td>
</tr>
}
</table>
edit: It is very important that the expensiveCalculation() is only calculated once for each item!
Your model is not adapted to the requirements of the view. Full stop.
So when your model is not adapted to the requirements of your view you go ahead and define a view model so that all the expensive operations are done inside the controller.
So:
public class ItemViewModel
{
public decimal Price { get; set; }
public string Name { get; set; }
}
Now your view becomes strongly typed to the view model and there are no longer expensive operations there:
#model IEnumerable<ItemViewModel>
<table>
<tr>
<td>
total: #Model.Sum(item => item.Price)
</td>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#item.Name - #item.Price<td>
</tr>
}
</table>
and now inside your controller action prepare this view model and pass it to the view:
public class SomeAction()
{
IEnumerable<Item> items = ... go and fetch your domain models from wherever you are fetching them
// now let's build the view model
var model = new MyViewModel
{
Items = items.Select(x => new ItemViewModel
{
Name = x.Name,
Price = x.expensiveCalculation()
})
};
// and we are obviously passing the view model to the view, not the domain model
return View(model);
}
As you can see we are doing the expensive operations for each element inside the controller in order to bind it to the corresponding property of the view model (Price) but we are no longer paying this expensive operations price inside the view as we are simply summing over the view model pre-calculated properties.
And next time when you encounter a problem in ASP.NET MVC don't forget that view models are the solution to your problem.

viewmodel return null on HttpPost

i have created a view like below.
<%for (var i = 0; i < Model.customerCDTO.Count();i++)
{%>
<tr>
<td style="text-align: center; width: 10%;" class="table-content-middle">
<%if (Model.customerCDTO[i].Active == true)
{%>
<%=Html.CheckBoxFor(m=>m.customerCDTO[i].Active)%>
<%}
else
{ %>
<%=Html.CheckBoxFor(m => m.customerCDTO[i].Active)%>
<%} %>
</td>
<td class="table-content-middle" align="center" style="width: 80%;">
<%=Html.DisplayTextFor(m=>m.customerCDTO[i].Name)%>
</td>
</tr>
<%} %>
the problem with the above view is that it is giving value for null for customerCDTO[i].Name
below is my view model
public class CustomerVM
{
public List<CustomerCDTO> customerCDTO { get; set; }
}
In the above view model I have created a List property. The CustomerCDTO class definition is as follows.
public class CustomerCDTO
{
public string Name { get; set; }
public bool Active { get; set; }
public bool? IsChecked { get; set; }
}
please help.
thanks
It's giving you a null value because your form does not contain any <input> elements corresponding to the Name property (which you can immediately spot if you View Source). The reason for this is that DisplayTextFor is not supposed to create such elements in the first place.
An almost always bad idea would be to also call Html.HiddenFor, which does create input elements:
<td class="table-content-middle" align="center" style="width: 80%;">
<%=Html.DisplayFor(m=>m.customerCDTO[i].Name)%>
<%=Html.HiddenFor(m=>m.customerCDTO[i].Name)%>
</td>
This would solve your problem, but then the user could simply edit the values with a tool like Firebug and change the value of Name, which you obviously do not want since otherwise you 'd have presented an <input> element for that. (Note: I also changed DisplayTextFor to DisplayFor, which in general is the more correct way to do things).
But what to do then? Well, there's no magic solution here. If the form doesn't contain Name then it will be null, but if it does contain Name (even hidden) then the user can change it at will. If that's a problem, the only option is to modify your backend code to populate Name from your data store.
Could you make sure that your list is populated with your intended data after running your query against database. In fact, it seems like your data is not populated properly.

MVC3 collection model binding with EditorFor

Regarding this post and this other one.
Suppose I have the following:
public class Foo
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
public class BarViewModel
{
public string Baz { get; set; }
public IList<Foo> Foos { get; set; }
}
And I have a view that receive a BarViewModel:
#model BarViewModel
#Html.EditorFor(model => model.Baz)
<table>
#for(int i = 0 ; i < Model.Foos.Count ; i ++)
{
string name1 = "Foos[" + i.ToString() + "].Value1";
string name2 = "Foos[" + i.ToString() + "].Value2";
<tr>
<td>
<input type="text" name="#name1" value="#Model.Foos[i].Value1" />
</td>
<td>
<input type="text" name="#name2" value="#Model.Foos[i].Value2" />
</td>
</tr>
}
</table>
And in my controller I have a POST method that recive the BarViewModel.
Given the inputs names generated for Value1 and Value2 are "Foos[0].Value1" and "Foos[1].Value1" and so on, the collection on the BarViewModel, in the POST method, is automatically filled by the ModelBinder. Awesome.
The problem is, if I do it this way in my view :
#for(int i = 0 ; i < Model.Foos.Count ; i ++)
{
<tr>
<td>
#Html.EditorFor(model => model.Foos[i].Value1);
</td>
<td>
#Html.EditorFor(model => model.Foos[i].Value2);
</td>
</tr>
}
Then the names generated for the input are like "Foos__0__Value1", and that break the model binding. The Foos property of my BarViewModel, in my POST method, is now null
I am missing something?
Edit
If I use EditorFor on the collection itself:
#EditorFor(model => model.Foos)
The names are generated correctly. But that force me to build a ViewModel in /Views/Share to handle the type Foos, that will generate the row, wich I dont really want to do...
Edit 2
I will clarify my question here, I understand that it's a bit vague.
If I do :
#EditorFor(model => model.Foos)
The names of the inputs will have the form "Foos[0].Value1" and the model binding works just fine on posts.
But if I do :
#for(int i = 0 ; i < Model.Foos.Count ; i ++)
{
#EditorFor(model => Model.Foos[0].Value1)
}
The names takes the form "Foos__0__Value1" and the model binding does not works. On my post method, model.Foos will be null.
Is there a reason why the second syntax breaks the model binding?
I'm not entirely sure just exactly what your question is. However, this is how MVC works. EditorFor uses EditorTemplates, and you define an editor template for your type. It doesn't have to go in Share, it can go in whatever level you're using. For instance, you can have it at /Views/Home/EditorTemplates/Foos.cshtml, so long as you aren't using it anywhere other than your Home controller.

How can I retrieve the List in my controller?

My controller always gets "null" for the "adjModel" parameter.
How can I retrieve the values?
CONTROLLER
[HttpPost]
public ActionResult AdjustmentList(List<AdjustmentVM> adjModel)
{
// adjModel is null
}
VIEW
#model List<ExtFramework.ViewModels.BillingArea.AdjustmentVM>
<div class="no-fancybox">
#using (Html.BeginForm("AdjustmentList", "Deposit", new { depositId = ViewBag.depositId }))
{
<div>
<table id="adjustment">
<tr>
<th>Description</th>
<th>Montant</th>
</tr>
#foreach(var item in Model)
{
<tr>
<td>#Html.TextBoxFor(model => item.Description)</td>
<td>#Html.TextBoxFor(model => item.Amount)</td>
</tr>
}
</table>
<input type="submit" value="" class="save" />
</div>
}
</div>
MODEL
namespace ExtFramework.ViewModels.BillingArea
{
public class AdjustmentVM
{
public int AdjustmentId { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public int DepositId { get; set; }
}
}
This is where editor templates are useful. Instead of using a foreach loop to go through the list of view models, use #Html.EditorFor(m => m). Then, in a subfolder named EditorTemplates (an MVC naming convention) add a view with the name AdjustmentVM.cshtml. Again, this is another MVC naming convetion - using the name of the type being used. This file would look like:
#model AdjustmentVM
<tr>
<td>#Html.TextBoxFor(model => model.Description)</td>
<td>#Html.TextBoxFor(model => model.Amount)</td>
</tr>
The runtime will automatically loop over the items in the list and render the contents of the editor template, giving unique names for each form value, so that the default model binder can map these to the properties on the view model on postback.
You can customise the name of the editor template if you want, look a the UIHintAttribute class.
By default, when you want a collection, you need to make sure the names of the controls indicate they are from an array, etc. The default binder doesn't have this magic to my knowledge.

Resources