MVC model post with partial shows list as null - c#

I'm going round in circles now so if anyone can identify the issue here I would greatly appreciate it.
I have a partial I'm using to list out items and that works fine.
The post back to the controller works if the items passed back is just set as an ienumerable of the items but I need to pass back a model as it contains more information than just the list.
On doing this the list is empty each time and I cannot see why.
The partial:
#model RequestModel
#section Scripts {
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
<style>
.btn span.glyphicon {
opacity: 0;
}
.btn.active span.glyphicon {
opacity: 1;
}
</style>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
}
#for (var i = 0; i < Model.Requests.Count(); i++)
{
<div #(Model.Requests.Count == 3 ? "class=col-md-4" : Model.Requests.Count() == 2 ? "class=col-md-6" : "class=col-md-12")>
#Html.HiddenFor(m => Model.Requests[i].RequestID)
<table class="table table-responsive img-rounded">
<thead>
<tr class="alert-cascade">
<th colspan="2">
<div class="btn-group btn-group-sm pull-right" role="group" data-toggle="buttons">
<button class="btn btn-success" data-toggle="tooltip" title="Accept" id="acceptradio">
#Html.RadioButtonFor(m => Model.Requests[i].AcceptChecked, Model.Requests[i].AcceptChecked, new { #id = Model.Requests[i].RequestID, #style = "display:none" })
<span>Accept </span>
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
</button>
<button class="btn btn-warning" data-toggle="tooltip" title="Reject" id="rejectradio">
#Html.RadioButtonFor(m => Model.Requests[i].RejectChecked, Model.Requests[i].RejectChecked, new { #id = Model.Requests[i].RequestID, #style = "display:none" })
<span>Reject </span>
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
</button>
</div>
#Model.Requests[i].EmployeeDescription
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Request Type</strong></td>
<td class="text-right">#Model.Requests[i].RequestType</td>
</tr>
<tr>
<td><strong>Duration</strong></td>
<td class="text-right">#Model.Requests[i].DurationDescription</td>
</tr>
<tr>
<td><strong>Dates</strong></td>
<td class="text-right">#Model.Requests[i].DatesDescription</td>
</tr>
</tbody>
</table>
</div>
}
The view:
#model RequestPageModel
#{
ViewData["Title"] = "Requests";
ViewData["SubTitle"] = "Welcome to Cascade Mobile";
}
#{Layout = "~/Views/Shared/_MainLayout.cshtml";}
#section Scripts {
<script type="text/javascript">
//Submit count setter
$(document).ready(function () {
var accepted = 0;
var rejected = 0;
$("#acceptradio").click(function () {
console.log("ready 2!");
$("#acceptCount").text("4");
});
});
</script>
}
<div class="container">
<h3>#ViewBag.Warning</h3>
#*Existing requests*#
<h4><strong>Requests</strong> <span class="badge alert-cascade">#Model.Pager.TotalRecords</span></h4><br />
#using (Html.BeginForm("Index", "Request", new { #id = "requestsform" }))
{
<div class="row">
#Html.Partial("_MultiSelectPartial", Model.RequestModel)
</div>
<div>
<textarea class="span6" rows="3" placeholder="Comments.." required></textarea>
</div>
<input type="submit" value="submit"/>
}
<button id="submitbtn" class="btn btn-primary pull-right" type="button">
Accept
<span class="badge" id="acceptCount">0</span>
Reject
<span class="badge" id="rejectCount">0</span>
</button>
#Html.Partial("_Pager", Model.Pager)
</div>
The controller action:
[HttpPost]
public IActionResult Index(RequestModel requests)
{
ViewBag.Warning = "We have: ";
foreach (var request in requests.Requests)
{
ViewBag.Warning += request.RequestID + " : ** : ";
}
var requestModel = GetRequestPageModel(3,1);
//requestModel.Requests[0].AcceptChecked = true;
return View("~/Views/User/Requests.cshtml", requestModel);
}
The model:
using System;
using System.Collections.Generic;
namespace Mobile.Models
{
/// <summary>
/// Request data model for the requests page
/// </summary>
public class RequestModel
{
public List<Request> Requests;
public RequestModel(List<Request> requests)
{
Requests = requests;
}
public RequestModel()
{
Requests = new List<Request>();
}
}
}
Again, if the post method takes just a list of request items its fine, but I will need to pass more information and cannot get it to post the list as part of the model. Can anyone see whats wrong here?

The model in your partial is RequestModel and your loop is generating controls with
name="Requests[0].RequestID"
name="Requests[1].RequestID"
etc, but the model in your POST method should be RequestPageModel so the correct name attributes would need to be
name="RequestModel.Requests[0].RequestID"
name="RequestModel.Requests[1].RequestID"
which will post back to
[HttpPost]
public IActionResult Index(RequestPageModel model)
You need to change the model in the partial to #model RequestPageModel (and adjust the HtmlHelper methods accordingly) and in the main view, use #Html.Partial("_MultiSelectPartial", Model).
In addition, change the name of the parameter to (say) RequestModel model so there is no conflict with the equivalent property name (refer this answer for an explanation).
I would however recommend that you use custom EditorTemplate's for your types rather than a partial, so that you controls are correctly named (refer the 2nd part of this answer for an example)
Side notes:
Your radio buttons do not makes sense, since they have different
names so you can select both (and once selected, you cannot
un-select them)
Your have a <textarea> without a name attribute so it will not
submit a value.

Related

Showing partial-view, only after search has been submitted Asp.Net MVC

I am new to Asp.Net Mvc. I couldn't find a solution that worked for me here, if I am blind just redirect me.
I am trying to make a web-app where i can search through clients, without displaying the entire table of clients. Only after the user presses search, the search result should show as a partial view. I understand that using Ajax is the most popular way of handling something like this.
Any pointers on how to accomplish this?
My first thought was to just make a display: block/none script connected to the submit button but the page updates each time you search rendering this idea useless. That's why i could use some help with how to asynchronously update the web page with the search result.
HomeController:
using testForAutofill.Models;
//Search Functionality
[HttpPost]
public PartialViewResult Index(string searchTerm)
{
test_Db_Context db = test_Db_Context();
List<ViewNewOrderSum> orderSums;
if (string.IsNullOrEmpty(searchTerm))//Fix this.
{
orderSums = db.ViewNewOrderSum.ToList();
}
else
{
orderSums = db.ViewNewOrderSum.Where(x =>
x.ClientName.Equals(searchTerm)).ToList();
}
return PartialView(orderSums);
}
Index View:
#model IEnumerable<testForAutofill.Models.ViewNewOrderSum>
#using (Html.BeginForm())
{
<b>Kundenavn:</b>
#Html.TextBox("searchTerm", null, new { id = "txtSearch" })
<input type="submit" value="🔍 Search" class="btn btn-primary" id="btn-search" />
}
<div id="posts-wrapper"></div>
<div class="client-div" runat="server" style="max-width: 20rem;">
<div class="card-header">Header</div>
<div class="card-body" id="client-Card">
<h4 class="card-title">Client info</h4>
<table id="client-table">
<tr>
<th>
#Html.DisplayNameFor(model => model.ClientName)
</th>
</tr>
#foreach (var item in Model)
{
#Html.Partial("_OrderSum", item)
}
</table>
</div>
</div>
Partial View:
#model testForAutofill.Models.ViewNewOrderSum
<tr>
<td>
#Html.DisplayFor(modelItem => Model.ClientName)
</td>
</tr>
No need of using Ajax. You can submit search text in Form Post. Fetch your data and filter based on your searchTerm retun to View with model. If your model is not null or empty show table else do not display table.
Checkout the below code :
View :
#model List<testForAutofill.Models.ViewNewOrderSum>
#using (Html.BeginForm()) {
<b>Kundenavn:</b>
#Html.TextBox("searchTerm", null, new { id = "txtSearch" })
<input type="submit" value="🔍 Search" class="btn btn-primary" id="btn-search" />
}
#if (Model != null && Model.Count() > 0) {
<div class="client-div" runat="server" style="max-width: 20rem;">
<div class="card-header">Header</div>
<div class="card-body" id="client-Card">
<h4 class="card-title">Client info</h4>
<table id="client-table">
<tr>
<th>
ClientName
</th>
</tr>
#foreach (var item in Model) {
#Html.Partial("_OrderSum", item)
}
</table>
</div>
</div>
}
Controller :
public ActionResult Index()
{
//if you want to load all the clients by default
test_Db_Context db = test_Db_Context();
List<ViewNewOrderSum> orderSums;
orderSums = db.ViewNewOrderSum.ToList();
return View(orderSums);
}
[HttpPost]
public ActionResult Index(string searchTerm) {
test_Db_Context db = test_Db_Context();
List<ViewNewOrderSum> orderSums;
if (!string.IsNullOrEmpty(searchTerm))
{
orderSums = db.ViewNewOrderSum.Where(x =>
x.ClientName.Equals(searchTerm)).ToList();
}
return View(result);
}
My first thought was to just make a display: block/none script
connected to the submit button but the page updates each time you
search rendering this idea useless.
You can prevent the page from updating using something like the following (using jQuery):
<script type="text/javascript">
$('form').submit(function (evt) {
evt.preventDefault();
... your code
});
</script>
Then you can make your ajax POST call, get the data, unhide table headers and append the html results from your partial view.

How to direct a data-toggle modal to the controller?

I have a list of table for survey form and each one of them have a button/asp-action to view the answers of at least 3 competitors. but I need to select the competitors of a survey form using a modal. inside that modal, I should populate the body with a checkbox of competitors who answered that survey form. How can I push through to direct the data-toggle modal to the controller?
Here is my View:
#for (int i = 0; i < Model.SurveyNames.Count; i++)
{
<tr>
<td>
#Model.SurveyNames[i].SurveyName
</td>
<td>
#Model.SurveyNames[i].SurveyFor
</td>
<td>
#Model.SurveyNames[i].Description
</td>
<td>
#Model.SurveyNames[i].CreatedBy
</td>
<td>
#Model.SurveyNames[i].Status
</td>
<td>
<!-- Button trigger modal -->
<a asp-action="ViewCompetitors" asp-route-id="#Model.SurveyNames[i].Id" data-toggle="modal" data-target="#ChooseCompetitors">View Competitors</a>
</td>
</tr>
}
And this is my Controller, it should return the values to index modal:
public IActionResult Index(int? id)
{
var model = new CompetitorAnswerViewModel();
var SurveyList = _context.MainSurvey.ToList();
foreach (var zsurvey in SurveyList)
{
model.SurveyNames.Add(new survey { Id = zsurvey.Id, SurveyName = zsurvey.SurveyName, SurveyFor = zsurvey.SurveyFor,Description = zsurvey.Description,CreatedBy = zsurvey.CreatedBy,Status = zsurvey.Status}) ;
}
var competitorList = _context.SurveyCompetitor.ToList().Where(x => x.MainSurveyId == id);
foreach (var competitors in competitorList)
{
model.CompetitorNames.Add(new Competitor { CompetitorName = competitors.CompetitorName});
}
return View(model);
}
I should populate the body with a checkbox of competitors who answered that survey form. But it doesn't forward to the controller whenever I click "View Competitors".
I want the table data id to pass it to the contoller to filter the survey competitors and then return the filtered data to the modal in the same index
You could put the Competitors view in a partial view and render it to Index view using ajax.
1.Create the partial view in Shared folder /Views/Shared/_CompetitorsPartialView.cshtml
#model YourViewModel
<div class="col-md-12">
Your View
</div>
2. Return the filtered data to this partail view
public IActionResult ViewCompetitors(int id)
{
// filter logic
return PartialView("_CompetitorsPartialView",YourViewModel);
}
3.Use ajax in Index view.
Modify <a> to <button>:
<button id = "ViewCompetitors" onclick="viewCompetitor(#item.CustomerId)">View Competitors</button>
Modal and ajax:
<div class="modal fade" id="ChooseCompetitors" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Modal Header</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<div class="modal-body">
<div id="showresults"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
#section Scripts{
<script>
function viewCompetitor(id) {
$.ajax({
type: 'Get',
url: '/Home/ViewCompetitors/' + id,//your url
success: function (result) {
$("#ChooseCompetitors").modal();//open modal
$('#showresults').html(result);//populate view to modal
}
})
}
</script>
}

Selecting one object from IEnumerable by clicking on a button

I'm creating a tool for editing/disabling Active Directory user accounts. I will search for a username and it will come up with all matches ("jsm" will come up with a table with "John Smith", "James Smoth", and "Jack Smuth"). Beside the user names are buttons "Edit" and "Disable". When the I click "Edit" or "Disable", a bootstrap modal will appear with textboxes containing the account properties such as SamAccountName and DisplayName inside of form inputs.
Because there could be multiple results, I'm passing in an IEnumerable into the view, then foreach through the #Model to put the data into the table. My next step is trying to pass in just that specific User object into the modal. So if the I click on the Edit button beside John Smith, I want his User object to populate the modal.
My first thought is doing a variable of User I can pass the User object from the foreach loop into then use that object in the modal, but I'm not sure how to go about doing that. Can/should I keep this inside the razor view or should I be passing this data into the controller then back out?
Controller:
namespace ADM.Controllers
{
public class ManagementController : Controller
{
public new IActionResult User()
{
var user = new List<User>();
return View(user);
}
[HttpPost]
public new IActionResult User(string username)
{
var user = new User();
var result = user.Get(username);
return View(result);
}
public IActionResult Group()
{
return View();
}
}
}
User.cshtml:
#model IEnumerable<User>
#{
ViewData["Title"] = "User Management";
var count = 1;
}
<div class="container col-6">
<div class="card card-square">
<div class="card-body">
<form asp-controller="Management" asp-action="User">
<div class="form-row form-inline">
<input type="text" class="form-control col-10" name="username" placeholder="Username" />
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
</div>
<br />
#if (Model.Any())
{
<div class="small">
<table class="table table-hover table-sm">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Display Name</th>
<th scope="col">Description</th>
<th scope="col">SamAccountName</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
#foreach (var user in Model)
{
<tr>
<th scope="row">#(count++)</th>
<td>#user.DisplayName</td>
<td>#user.Description</td>
<td>#user.SamAccountName</td>
<td>
<button id="userEditBtn" class="btn btn-sm btn-info" data-toggle="modal" data-target="#userEditModal">Edit</button>
<button class="btn btn-sm btn-danger" data-toggle="modal" data-target="#userDisableModal">Disable</button>
</td>
</tr>
}
</tbody>
</table>
</div>
}
There's more than one way to do this.
If there isn't a lot of User fields, you can store the properties in the DOM for each User button
#foreach(var user in Model)
{
<button class="btnEdit" data-username="#user.Username">Edit</button>
}
In your JavaScript button handler you extract the data to populate a form submission or make an AJAX request.
$(".btnEdit").on("click", function(e) {
var form = $("#myform");
var username = $(this).data("username");
// populate a form
$("input[name='username']").val(username);
form.submit();
});
Another way is to make a AJAX call for the user you want to edit to obtain more data than what you first loaded with the initial collection. Then use the second call to populate the edit form. One quick way is to just load a html partial.
$(".btnEdit").on("click", function(e) {
var username = $(this).data("username");
var action = $(this).data("action"); // EditDetailsForm
$.ajax({
url: action,
method: "get",
data: { username = username }
})
.then(function(partialView) {
$("#editForm").html(partialView);
});
});
And the action
[HttpGet]
public ActionResult EditDetailsForm(string username)
{
var user = GetUser(username);
return PartialView("_editForm", user);
}
Returns a partial view _editForm.cshtml
#Model User
<form ...>
...
</form>
Which you just drop into your modal.

on bootstrap modal dialog load get data from controller

I have two controllers, both are having view.
control#1->view#1
control#2->view#2
on controller#1->view#1 I have data displayed in tabular format. in that data table, one column is hyperlink, when i click on that I want to pop up Bootstrap modal dialog. My retirement is Modal dialog should call action method of control#2 and display view#2 in modal dialog.
view#1:
#model xxx
<table id="myTable" class="table table-bordered">
<thead>
....
</thead>
<tbody>
#foreach (xx item in Model.xxx)
{
....
<td>#Html.ActionLink(item.Value.ToString(), "Display", "Controller#2", new { para1 = item.val1, para2 = item.val2}, null)</td>
}
#Html.ActionLink() is working fine it invokes Display() method of Controller#2 and eventually displays view#2. But now the requirement is view#2 should be popup modal dialog box.
FYI: both the views are using common _Layout.cshtml file.
Please help me doing this.
I've created a complete solution for you using #Ajax.ActionLink().To use Ajax.ActionLink it's very important that you add a reference to jquery.unobtrusive-ajax.min.js.In total you should have the following references in this order:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/jquery.ajax.unobtrusive/3.2.4/jquery.unobtrusive-ajax.min.js"></script>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var p1 = new Product { ID = 1, Description = "Product 1" };
var p2 = new Product { ID = 2, Description = "Product 2" };
return View(new List<Product> { p1, p2 });
}
public PartialViewResult GetDetails(string description)
{
return PartialView("_GetDetails", description);
}
}
_GetDetails.cshtml partial view:
<div id="myModal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">Modal Header</h4>
</div>
<div class="modal-body">
<h3>#Model</h3>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Main view:
#model IEnumerable<MVC_Master_Pages.Models.Product>
#{
ViewBag.Title = "Home";
Layout = null;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/jquery.ajax.unobtrusive/3.2.4/jquery.unobtrusive-ajax.min.js"></script>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
<script type="text/javascript">
function Done() {
$('#myModal').modal('show');
}
</script>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>Description</th>
<th>Details</th>
</tr>
</thead>
<tbody>
#foreach (var product in Model)
{
<tr>
<td>#product.ID</td>
<td>#product.Description</td>
<td>
#Ajax.ActionLink("Details",
"GetDetails",
new { description = product.Description },
new AjaxOptions
{
UpdateTargetId = "details",
InsertionMode = InsertionMode.Replace,
OnSuccess = "Done()"
})
</td>
</tr>
}
</tbody>
</table>
<div id="details">
</div>
Output:
The second view can't be a complete view with layout. It should be a partial view and called by ajax. Check this answer to learn how to implement it.

List<string> returns as System.Collections.Generic.List on HttpPost wiht MVC

I have a Web App written in MVC5 which passes a List of an Object(which contains many items) to a Page and renders successfully. I have a button on the form that forces a Post back. When I click the button the model appears to re-initialise the List of Objects, rather than return what was is on the Page.
I have read various posts on SO that cover similar issues that have made many suggestion like ensuring every item in the Object is on the form (at least hidden). Tried many of the options, but so far haven't been successful in solving my issue.
I decided to go back to basics on it, and created a very simple View Model with a List. This again renders ok, but when returned it as System.Collections.Generic.List.
View Model
public class TestVm
{
public List<string> CustomerNames { get; set; }
}
Controller
public ActionResult Index()
{
TestVm testmodel = new TestVm();
testmodel.CustomerNames = new List<string>();
testmodel.CustomerNames.Add("HELP");
testmodel.CustomerNames.Add("Its");
testmodel.CustomerNames.Add("Not");
testmodel.CustomerNames.Add("Working");
return View(testmodel);
}
[HttpPost]
public ActionResult Index(TestVm model)
{
// DO SOME WORK HERE WITH RETURNED model
return View(model);
}
View
#model WebApplication1.Models.TestVm
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>View</title>
</head>
<body>
#using (Html.BeginForm("Index", "Test", Model, FormMethod.Post, new { #class = "form-horizontal" }))
{
<button name="submit" type="submit" value="Refresh" class="btn btn-sm btn-default pull-right">Refresh</button>
}
<div>
#if (Model.CustomerNames != null)
{
<table>
<thead>
<tr>
<td class="text-center">CustomerName</td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.CustomerNames.Count(); i++)
{
<tr>
<td class="text-center">#Html.EditorFor(m => m.CustomerNames[i])</td>
</tr>
}
</tbody>
<tfoot>
</tfoot>
</table>
}
</div>
</body>
</html>
I thought creating a simple app like this would help me understand and solve my issue. But I can't work out why the model in the HttpPost contains "System.Collections.Generic.List", rather than the actual list of string that I would expect.
Initial Loadup
Page when loaded up the first time
After Refresh
Page after I clicked Refresh
You need to enclose every form control under Html.BeginForm brackets
#using (Html.BeginForm("Index", "Test", Model, FormMethod.Post, new { #class = "form-horizontal" }))
{
<button name="submit" type="submit" value="Refresh" class="btn btn-sm btn-default pull-right">Refresh</button>
<div>
#if (Model.CustomerNames != null)
{
<table>
<thead>
<tr>
<td class="text-center">CustomerName</td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.CustomerNames.Count(); i++)
{
<tr>
<td class="text-center">#Html.EditorFor(m => m.CustomerNames[i])</td>
</tr>
}
</tbody>
<tfoot>
</tfoot>
</table>
}
</div>
}

Categories

Resources