Getting Data from Multiple Partial Views in MVC 4 - c#

I am trying to make a view which will have Partial Views being generated dynamically. Each Partial view is having a TextBox in which users put the name of a merchant.
Below is my view code:
#model BrightMediaTest.merchant
#{
ViewBag.Title = "AddMerchant";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
<h2>AddMerchant</h2>
#Ajax.ActionLink("Add More", "AddMerchantPartial", "MerchantDB", new AjaxOptions { UpdateTargetId = "ajax-partial-view-box", InsertionMode = InsertionMode.InsertAfter })
#using (Html.BeginForm("AddMerchant", "MerchantDB", FormMethod.Post, new { }))
{
<ul style ="list-style: none;">
<li>
#Html.LabelFor(m => m.merchantName)
</li>
<li>
#Html.TextBoxFor(m => m.merchantName)
</li>
</ul>
<div id="ajax-partial-view-box">
</div>
<input type="submit" value="Add" />
}
As you can see there is a ActionLink in the Code "Add More", which actually adds partial view in the div "ajax-partial-view-box".
So, what I want to achieve is that when I submit the form. I want to take the text of all the TextBoxes which are added in the div "ajax-partial-view-box" dynamically by clicking the link "Add More".
There is no id associated with the TextBoxes in the partial views. Any help or suggestions is appreciated.
Here is my partial view code:
#model BrightMediaTest.merchant
<ul style="list-style: none;">
<li>
#Html.LabelFor(m => m.merchantName)
</li>
<li>
#Html.TextBoxFor(m => m.merchantName)
</li>
</ul>
So, I am trying to add all those merchants names in the database when the form is submitted. I have an Action "AddMerchant" which i will use to add all those merchant name in the database.
My merchant ViewModel is as follows:
namespace BrightMediaTest
{
using System;
using System.Collections.Generic;
public partial class merchant
{
public merchant()
{
this.stores = new HashSet<store>();
}
public long merchantID { get; set; }
public string merchantName { get; set; }
public virtual ICollection<store> stores { get; set; }
}
This code was generated using Entity Framework.
Thanks

Ok, so I recreated a sample of your issue and it boils down to the fact that you can't post a list without specifying an index in the name attribute. See: MVC Form not able to post List of objects
At the end of the day, before the user clicks submit you will have n merchantName inputs. On your controller post action you will have something like this:
[HttpPost]
public ActionResult AddMerchant(List<merchant> merchant)
{
//post logic
}
In your current setup, the parameter will be null, BUT your form values in your HttpContext.Request will not be, they will look, something like this:
{merchantName=test1&merchantName=test2}
You need to specify where those inputs are supposed to bind to, right now they are lost sheep in a huge pasture. That is done by specifying an index of the object that these inputs belong to.
Such as: merchant[i].merchantName where i is some index.
Once the partials are added they need to have an object class name and an index prepended to the merchantName value. Your html, before the user clicks add, would look something like this:
<ul style="list-style: none;">
<li>
<label for="merchantName">merchantName</label>
</li>
<li>
<input id="merchantName" name="merchant[0].merchantName" value="" type="text">
</li>
</ul>
<div id="ajax-partial-view-box">
<ul style="list-style: none;">
<li>
<label for="merchantName">merchantName</label>
</li>
<li>
<input id="merchantName" class="valid" aria-invalid="false" name="merchant[1].merchantName" value="" type="text">
</li>
</ul>
<ul style="list-style: none;">
<li>
<label for="merchantName">merchantName</label>
</li>
<li>
<input id="merchantName" class="valid" aria-invalid="false" name="merchant[2].merchantName" value="" type="text">
</li>
</ul></div>
<input value="Add" type="submit">
Then you form data will look something like:
{merchant%5b0%5d.merchantName=test1&merchant%5b1%5d.merchantName=test2&merchant%5b2%5d.merchantName=test3}
And your action parameter will have 3 values in it with the correct data.
Now, how you do this is another matter. There are a number of ways to handle it through Razor or Javascript

What you want to do is lose the AjaxHelper. You'll need to take more control over your merchant parital insertion.
Add More
Wire this up with jQuery. Use index to track your merchants added to the form so you can uniquely identity them later.
var index = 1; // start with 1 because your form already has a static one
$(".add-merchant").on("click", function(event) {
event.preventDefault();
$.ajax({
url: $(this).attr("href"),
method: "GET",
data: { index: index }
})
.done(function(partialViewResult) {
$("#ajax-partial-view-box").append(partialViewResult);
});
});
Modify your action
public ActionResult AddMerchantPartial(int index)
{
ViewBag.Index = index;
return View(new merchant());
}
Now in your partial you need to include the index and manually write the input elements.
#model merchant
#{
var index = ViewBag.Index as int;
var id = Model.merchantName + "_" + index + "_"; // merchantName_i_
var name = Model.merchantName + "[" + index + "]"; // merchantName[i]
}
<ul>
<li><label for="#id">#Model.merchantName</label></li>
<li><input id="#id" name="#name" type="text" value="#Model.merchantName" /></li>
</ul>
Normally, I refrain from using ViewBag. You might consider adding index to the view model.
Your main form becomes
#using (Html.BeginForm("AddMerchant", "MerchantDB", FormMethod.Post, new { }))
{
var id = Model.merchantName + "_0_";
var name = Model.merchantName + "[0]";
<ul style ="list-style: none;">
<li>
<label for="#id">#Model.merchantName</label>
</li>
<li>
<input id="#id" name="#name" type="text" value="" />
</li>
</ul>
<div id="ajax-partial-view-box"></div>
<input type="submit" value="Add" />
}
And your post method would be
[HttpPost]
public ActionResult AddMerchant(List<merchant> merchants)
{
...
}

Related

C# ASP.NET MVC 5 - Type 'System.Web.Mvc.MvcHtmlString' is not enumerable

Here is the context :
This is my first ASP.NET MVC web application.
On the categories list, I click on a category to see all infos about it in a new view.
From this view, I display its name, its description and the number of products in this category.
Below that, I want to display all products in this category.
I gave a try to the Html.Action() method.
But when I want to iterate through the foreach statement, Visual Studio tells me that Type 'System.Web.Mvc.MvcHtmlString' is not enumerable.
Here is my view (details about a category):
#model UlysseCMS.Models.category
#{
ViewBag.Title = "Details about "+ Model.category_name + " category";
var nbProducts = Html.Action("GetNumberOfProductByCategory", "Product", Model.category_id);
var productsList = Html.Action("GetListOfProductsByCategory", "Product", Model.category_id);
}
<div>
<span class="leader">
<span class="mif-chevron-thin-right fg-teal"></span>
<span>#Html.ActionLink("Categories", "Index")</span>
<span class="mif-chevron-thin-right fg-teal"></span>
<span>#Model.category_name</span>
<span class="mif-chevron-right fg-teal"></span>
<span>details</span>
</span>
</div>
<br />
<hr class="bg-teal" />
<br />
<div class="margin30">
<div class="flex-grid">
<div class="row cells7">
<div class="cell colspan3 offset1 sub-header">
Category name :
</div>
<div class="cell colspan4">
#Model.category_name
</div>
</div> <br/>
<div class="row cells7">
<div class="cell colspan3 offset1 sub-header">
Category description :
</div> <br/>
<div class="cell colspan4">
#Model.category_description
</div>
</div> <br/>
<div class="row cells7">
<div class="cell colspan3 offset1 sub-header">
Number of products in this category :
</div>
<div class="cell colspan4">
#nbProducts
</div>
</div>
</div>
</div>
#foreach (var item in productsList)
{
}
Here is my GetListOfProductsByCategory() method from the ProductController :
public IEnumerable<product> GetListOfProductsByCategory(int id)
{
return db.product.Where(x => x.product_category_id == id);
}
I still continue to find a solution with IEnumerable casting or something.
Thanks,
Hellcat.
The result of #Html.Action is a View.
When you have an action inside a controller, this action usually return a view, and that rendered view is result of Html.Action ,for displaying it you need to use an # before html.
You should write your code as below:
public ActionResult GetListOfProductsByCategory(int id)
{
return View(db.product.Where(x => x.product_category_id == id));
}
Then right click on View and from menu select Add View, then create a partial view.
inside partial view you can enumerate your list and create the rendered out put.
Finally wherever you want to show the rendered list, just call:
#Html.Action("GetListOfProductsByCategory",id)
When creating view, you need to select your model and view type as list so on top of your view you would have:
#model IEnumerable<product>
Then you must use you foreach as below:
#foreach (var item in Model)
{
}
Okay I modified my Partial View (GetListOfProductsByCategory) like that :
#model IEnumerable<UlysseCMS.Models.product>
#foreach (var item in Model)
{
<a href="#Url.Action("Details", new { id = item.product_id })">
<div class="tile tile-wide bg-white block-shadow margin30" data-role="tile">
<div class="tile-content slide-up-2">
<div class="slide fg-darkTeal">
<span class="sub-leader" style="padding-left: 5px;">#Html.DisplayFor(modelItem => item.product_name)</span>
</div>
</div>
</div>
</a>
}
And in my Category Details View I display the products like this :
#Html.Action("GetListOfProductsByCategory", "Product", Model.category_id)
It's now okay hanks to Mehrdad Babaki's answer.

MVC 5 Viewmodel binding works but post back is partial filled

I have a parameterless Index for the HttpGet which works. But when I post it the HttpPost version of Index is invoked and the viewmodel object is passed in, but there is only the value of the dropdown in it. The rest is null (products, title)
[HttpPost]
public ActionResult Index(ProductsViewModel pvm)
{
// breakpoint on line 36, shows that pvm.Title is null and Products too.
return View(pvm);
}
My compilable and running example can be downloaded from my OneDrive http://1drv.ms/1zSsMkr
My view:
#model KleinKloteProductOverzicht.Models.ProductsViewModel
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect"})
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#foreach (var item in Model.Products)
{
#Html.DisplayFor(i => item.Name);
}
</div>
}
}
If I have this view model:
public class ViewModel
{
public string Name {get;set;}
public string SelectedLocation {get;set;}
public IEnumerable<SelectListItem> Locations {get;set;}
}
And your actions look like this:
public ActionResult MyForm()
{
var vm = new ViewModel
{
Locations = context.Locations.ToList() // Some database call
}
return View(vm);
}
[HttpPost]
public ActionResult MyForm(ViewModel vm)
{
vm.Locations // this is null
}
It is null because the model binder can't find a form control that is setting its data.
The <form> must set some data in the view for the model binder to pick it up.
<form>
Name: <input type="text" id="name" />
</form>
This will set the Name property on the view model, because the model bind can see the id of the form control and uses that to know what to bind to.
So in terms of your view, you need to make sure you wrap any content that you want to post back to the server with #using(Html.BeginForm())
Anyway this is my guess.
Well, you seem to be confused as to how [HttpPost] and form tags interact with eachother.
You see, when .NET MVC binds your parameters in your controller actions, it tries to derive that data from the request. For [HttpGet] it does this by looking at the query string.
For [HttpPost] calls, it also looks at the Request.Form. This variable is populated with the values of all input fields that were inside the form you submitted.
Now, this is your view:
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect" })
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#foreach (var item in Model.Products)
{
#Html.DisplayFor(i => item.Name);
}
</div>
}
}
You only have one select tag (generated by Dropdownlistfor) but no other inputs. That's why .NET MVC cannot infer any other data for your view model.
If you change your view to this:
#model KleinKloteProductOverzicht.Models.ProductsViewModel
#using (Html.BeginForm("Index", "Products"))
{
<h2>#Html.DisplayFor(m => m.Title)</h2>
<input type="submit" value="post dit" /><br/>
<div class="row">
<div class="col-lg-2 col-md-2">
#Html.DropDownListFor(x => x.CurrentSort, EnumHelper.GetSelectList(typeof(SortOptions)), new { #class = "multiselect" })
</div>
</div>
if (Model.Products.Count() > 0)
{
<div class="row">
#for (var i = 0; i < Model.Products.Count; i++)
{
#Html.DisplayFor(model => model.Products[i].Name)
#Html.HiddenFor(model => model.Products[i].ID)
}
</div>
}
}
You'll see I've added a hidden input (<input type="hidden">) for the product id. Note that the product name still will be null.
I would suggest you follow a tutorial on .NET MVC and read up on some of the concepts behind it, because the very fact that you ask this question reveals that you have much to learn.
Best of luck!
P.S. One last tip: #Html.Blablabla writes directly to your view. You usually don't need that ";" at the end, because it will be inside your generated html.
Your property is not associated with a "postable" control, therefore it will not be submitted along with the form data. If your really want to get the value in your Title property, just set it as a hidden input.
#Html.HiddenFor(m => m.Title)
A label will not be posted when submitting a form but an input will. This is exactly what HiddenFor does; it creates a hidden input element which will be picked up by the form submit.

How can I query a view partial and render it in a modal?

I have a view partial in a strongly typed controller. Is it possible to render the would-be contents of that view partial on mouseclick?
Example:
Active View
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<div id="modalView"></div>
<script>
$(document).ready(function () {
$('.open-popup-link').magnificPopup({
key: 'my-popup',
type: 'inline',
inline: {
// Define markup. Class names should match key names.
markup: '<div class="white-popup"><div class="mfp-close"></div>awesome</div>'
}
},
{
callbacks: {
open: function(){
}
}
});
$('.open-popup-link').on('mfpOpen', function(e /*, params */) {
var linkText = // how to I grab this? (e.g. 1, 2, 3, or 4)
$.ajax({
// call view partial withlinktext as parameter
//on success
// var inlineContent = viewPartialContent
// On error
// var inlineCOntent = 'Uh oh, something went wrong'
});
});
});
</script>
View Partial
#model *******.Models.Reservation
<div class="container">
<div class="section-heading">
<h2 class="red">Confirm Your Reservation</h2><br />
</div>
<div class="section-content">
<div class="row">
<h3 class="black text-center">Are you sure you want to reserve space <span class="dark-red">#Model.SpaceNumber</span></h3>
<h4 class="black text-center">for <span class="dark-red">#Model.Game.Description</span> on <span class="dark-red">#Model.Game.Date.ToShortDateString()</span>?</h4>
</div>
<div class="row">
<div class="hero-buttons text-center">
No
<form action="/api/Reservations" method="post" id="confirmationForm">
#Html.Hidden("eRaiderUserName", #Model.eRaiderUserName)
#Html.Hidden("SpaceNumber", #Model.SpaceNumber)
<input type="submit" value="Yes" class="btn btn-red btn-lg white">
</form>
</div>
</div>
</div>
</div>
Method for viewpartial in controller
public ActionResult Confirm(int spaceNumber)
{
var reservation = new Reservation { SpaceNumber=spaceNumber, UserName=AppSettings.CurrentUserName, Game=db.Games.FirstOrDefault(g => g.ID == AppSettings.CurrentGameID) };
return View(reservation);
}
Does this make sense, and can I make it work?
You need to do two things:
change your Confirm method, so that it returns PartialView(reservation) instead of View(reservation)
You need to use AJAX, for example jQuery ajax, to get the HTML, and render it in your page
$.ajax({url = 'the url for your Confirm action',
type = 'GET',
dataType='html',
data = params}).done(function(html) {
// use the jQuery and the html to inject it wherever you need in your page
});
NOTE: params is a jQuery object which contains the data you need to pass like spaceNumber in this case, i.e.
var params = { spaceNumber: 'spaceNumberValue' }

DropDown List error : "There is no ViewData item of type 'IEnumerable<SelectListItem>'

Trying to change my view to PartialView, that I use it with tabs System, I got this error : There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'REGION_ID'. at line 30 of _CreateCit.cshtml file :
#model pfebs0.Models.CITOYEN
<h2>Demandeur Info</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
// some code removed (HTML tag)
<div class="form-group">
<label for="REGION_ID" >Region</label>
#Html.DropDownList("REGION_ID", String.Empty)
#Html.ValidationMessageFor(model => model.REGION_ID)
</div>
// some other input tag
<br />
<button type="submit" class="btn btn-default">Valider</button>
<button type="reset" class="btn btn-default">Annuler</button>
</div>
}
And this my index.cshtml file`:
#{
ViewBag.Title = "Creation D'un nouveau Demande";
}
<ul class="nav nav-tabs" id="myTab">
<li class="active">Add user tab</li>
<li>add request tab</li>
</ul>
<div class="tab-content">
<div id="tab1" class="tab-pane active">
#Html.Partial("_CreateCit", ViewData)
</div>
<div id="tab2" class="tab-pane">
#Html.Partial("_CreateDM")
</div>
</div>
#section Scripts {
// script Section removed
}
her's my Controller code :
public ActionResult CreateCit()
{
ViewData["REGION_ID"] = new SelectList(db.REGION, "REGION_ID", "NOM");
return PartialView("_CreateCit");
}
//
// POST: /Citoyen/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateCit(CITOYEN citoyen)
{
if (ModelState.IsValid)
{
db.CITOYEN.Add(citoyen);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.REGION_ID = new SelectList(db.REGION, "REGION_ID", "NOM", citoyen.REGION_ID);
return PartialView(citoyen);
}
The same code work with standard view, I just changed return View to return PartialView(Viewname) and I edited the cshtml file.
I think I missed something !!
The #Html.Partial just renders the partial view and does not call the controller action. It just takes the partial view code, without reaching the controller (set a break point in the action and test this). The ViewData will not contain the data that you think it will. You need to use #Html.Action:
<div id="tab1" class="tab-pane active">
#Html.Action("CreateCit")
</div>
<div id="tab2" class="tab-pane">
#Html.Action("CreateDM")
</div>
Alternatively, try explicitly adding the data in the ViewData object, before calling #Html.Partial:
<div id="tab1" class="tab-pane active">
#{
ViewData["REGION_ID_DDL"] = ViewData["REGION_ID"]
}
#Html.Partial("_CreateCit", ViewData)
</div>
Edit:
To address your comment about the success message: there are several things you can do to display a success message. My personal favorite is to create a ResultModel:
public class ResultModel
{
public string Message { get; set; }
public ResultType TypeOfResult { get; set; }
}
public Enum TypeOfResult
{
Error,
Success
}
Create a partial view which accepts a ResultModel and displays the message:
#model YourNamespace.Models.ResultModel
<p>Sort du resultat: #Model.TypeOfResult</p>
<p>Message: #Model.Message</p>
You would then just call it like this:
if (ModelState.IsValid)
{
db.CITOYEN.Add(citoyen);
db.SaveChanges();
ResultModel resultModel = new ResultModel();
resultModel.TypeOfResult = TypeOfResult.Success;
//my french is a bit rusty but the result from Google Translate sounds good enough
resultModel.Message = "Citoyen ajouté avec succès.";
return PartialView("_ResultSummary", resultModel);
}
Of course, this can be used to trace out errors as well. You could add a try-catch around the db.SaveChanges(); call and set the success type/message in the try block and the error type/message in the catch block.
Another advantage of this method is that the ResultModel object can also be sent as a JSON string, in case you want to display a JavaScript "alert" or even a custom pop-up.

Pass javascript value to controller C#

I have created a search function on my website that allows the user to select what table they wnat to search for. The select list is within a dropdown rendered within a dropdown menu using bootstrap. The issue I'm facing is that when the form is run the value of the selected item from the dropdown menu that is passed into the controller is "" instead of the value that was selected in the dropdown menu.
Any help would be grateful.
** search function**
<div class="col-lg-6">
#{using (Html.BeginForm("Results", "Searchv2", FormMethod.Get))
{
<div class="input-group">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">Action <span class="caret"></span></button>
<ul class="dropdown-menu">
#foreach (var searchType in new[] { "User", "Restaurant", "Cuisine" })
{
<li>#searchType</li>
}
</ul>
<input type="hidden" name="SearchBy" />
</div>
<input type="text" class="form-control" placeholder="Search" name="SearchString" id="SearchString">
</div>
}}
</div>
jquery that finds the value
<script>
$(".dropdown-menu").click(function () {
$("input[name='SearchBy']").val($(this).html());
});
</script>
you have to write click event on anchor tag, as you have anchor tag inside <li> like this:
<ul class="dropdown-menu">
<li>
<a>SearchText</a>
</li>
</ul>
$(".dropdown-menu li a").click(function () {
$("input[name='SearchBy']").val($(this).html());
});
I believe you want to set the SearchBy value by getting the value of the selected item:
<script>
$(".dropdown-menu").click(function () {
$("input[name='SearchBy']").val($(this).val());
});
</script>
if you want to get the value of text in between the <a></a> tags, try use .text() instead of html()
First you are selecting the ul not the a inside its li and second you should use jquery text function:
<script>
$('.dropdown-menu li a').click(function (e) {
e.preventDefault();
$('input[name="SearchBy"]').val($(this).text());
});
</script>

Categories