I am learning MVC by converting an old Web Form to MVC 5. The page is pretty simple, just a few text boxes, a submit button and a grid. I want similar functionality where there is no grid on page load, and after the submit takes place, the grid appears. The issue I am having is all the examples I have seen contain the data coming at page load.
Here is my View
<h2>Search Craigslist</h2>
#using (Html.BeginForm())
{
<p>
Enter Search Criteria:<br />
#Html.TextBox("Criteria", null, new { #style = "width: 450" })
#Html.DropDownList("DropDownValues")
Days:
#Html.TextBox("Days")
<br />
<input type="submit" value="Submit" />
</p>
}
#Html.Partial("~/Views/Search/_Search.cshtml")
and the partial
#model IEnumerable<CraigsListSearch.MVC.Models.SearchModel>
#{
Layout = null;
}
<table cellspacing="0" border="1" style="border-collapse:collapse;">
<tr>
<th>
#Html.DisplayNameFor(model => model.Location)
</th>
<th>
Link
</th>
<th>
#Html.DisplayNameFor(model => model.DateSubmitted)
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Location)
</td>
<td>
#Html.Hyperlink(Html.DisplayFor(modelItem => item.URL).ToString(), Html.DisplayFor(modelItem => item.Title).ToString())
</td>
<td>
#Html.DisplayFor(modelItem => item.DateSubmitted)
</td>
</tr>
}
and finally the controller
public class SearchController : Controller
{
[HttpGet]
//public ViewResult Index(string DropDownValues, string Criteria, string Days)
public ViewResult Index()
{
Populate();
Session["list"] = SearchModel.GetQuickList();
return View(new List<SearchModel>());
}
[HttpPost]
public ViewResult Index(string DropDownValues, string Criteria, string Days)
{
Populate();
Criteria = Server.UrlPathEncode(Criteria).Replace(",", "%2c");
List<Location> list = (List<Location>)(Session["list"]);
List<SearchModel> results = SearchModel.Search(Criteria, Days, DropDownValues, list.Take(5).ToList());
return View(results);
}
private void Populate()
{
List<DropDownModel> ddlList = DropDownModel.GetDropdowns();
ViewBag.DropDownValues = (from a in ddlList
select new SelectListItem
{
Text = a.Text,
Value = a.Value
}).ToList();
}
}
What I am looking for is to have the partial only be "called" on POST. Is this something that can easily be done? Let me know if other info is needed.
Thanks!
There are a couple of ways you could approach solving this problem.
One option would be to use AJAX. When the user clicks the button make an AJAX call to the controller and have the controller render the partial and return the HTML which you then inject into the page DOM.
Another option is to add a "DisplayGrid" property to your model. When you first render the View set it to false and then set it to true after the post.
One point I want to make is that rendering a View from the Post action isn't a great idea. If the user hits Refresh in their browser it's going to post again, giving the user that annoyingly ugly dialog that tells them they're going to send data to the server. In general you should use a Post-Redirect-Get pattern, the user posts, you redirect, and then you use a get to re-render in the browser. If you use the AJAX approach this all becomes moot because you never post the whole form back to a controller action. If you continue to use a full post the HttpPost version of Index should return a RedirectToAction result sending the user back to the Get version of Index. You can communicate between the two actions by using TempData, for example:
[HttpGet]
public ActionResult Index()
{
if( TempData.ContainsKey( "DisplayGrid" )
{
// Use the other values from TempData to populate the model with the grid data
myModel.DisplayGrid = (bool)TempData["DisplayGrid"];
}
}
[HttpPost]
public ActionResult Index( string dropDownValues, string criteria, string days )
{
TempData["DisplayGrid"] = true;
TempData["DropDownValues"] = dropDownValues;
TempData["Criteria"] = criteria;
TempData["Days"] = days;
return RedirectToAction( "Index" );
}
You should pass whatever information you need to render view via model (or ViewBag).
Since you already have sample of how to use Model here is how you can use ViewBag
#if(ViewBag.RenderSearch)
{
#Html.Partial("~/Views/Search/_Search.cshtml")
}
And set RenderSearch in particular action:
public ViewResult Index(string DropDownValues...
{
....
ViewBag.RenderSearch = true;
With model just add property to your model (you'd need to make it custom class instead of just List) and check that flag instead.
Try This, the Ajax only replace the GridDiv only
<h2>Search Craigslist</h2>
using (Ajax.BeginForm("BindGridAction", "Contoller",new AjaxOptions { UpdateTargetId = "GridDiv" }))
{
<p>
Enter Search Criteria:<br />
#Html.TextBox("Criteria", null, new { #style = "width: 450" })
#Html.DropDownList("DropDownValues")
Days:
#Html.TextBox("Days")
<br />
<input type="submit" value="Submit" />
</p>
}
<div id="GridDiv">
#Html.Partial("~/Views/Search/_Search.cshtml")
</div>
Bind your partial view in controller
public PartialViewResult BindGridAction()
{
// your model coding
return PartialView("~/Views/Search/_Search.cshtml", model);
}
Related
I have a table that has one empty column into which user can enter a comment:
Table
-----
TbMapId | UniqueAdp | Dealership | Line
--------------------------------------------------------------------
1 | [Insert comment here] | Derby | abc123
2 | [Insert comment here] | Keighley | cda345
3 | [Insert comment here] | Manchester | 876ghj
There is a lot of data to comment on, I can't expect a user to open an 'Edit' page and type in a comment one by one. Instead I need user to be able to input a bunch of comments (say 20 at a time against 20 rows) and save them all at one click of submit button.
If you want to jump straight to working solution go to EDIT #2 & look at Rudi's accepted answer
View
<form asp-action="TbMapViewEdit">
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.TBMapBalancesList.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(Model => Model.TBMapBalancesList[i].TbMapId)
#Html.HiddenFor(Model => Model.TBMapBalancesList[i].TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.TBMapBalancesList[i].UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].Line)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].MapResult)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
Model
I've learned today that I need to use List to be able to iterate through the lines in table by the use of #for loop (as shown above). before I was trying to use IEnumerable. So I added a definition to the model for public List<TBMapBalances> TBMapBalancesList { get; set; }
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapBalances> TBMapBalances { get; set; }
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalancesList { get; set; }
[...]
}
Controller:
Now this is where I need the help with, my code doesn't throw any errors at all. When I press Submit nothing happens:
[Authorize]
[HttpPost]
public async Task<IActionResult> TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
try
{
_context.Update(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
}
return RedirectToAction("TbMapView");
}
EDIT #1
Changes to View
<form asp-action="TbMapViewEdit">
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.TBMapBalances.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(Model => Model.TBMapBalances[i].TbMapId)
#Html.HiddenFor(Model => Model.TBMapBalances[i].TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.TBMapBalances[i].UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].Line)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].MapResult)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
Changes to model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNET_Core_1_0.Models.TbMapViewModels
{
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; }
[...]
}
}
Changes to Controller:
Now this is where I need the help with, my code doesn't throw any errors at all when at the current state - when I press Submit nothing happens and no data gets saved to the database.
however, when you uncomment line _context.Update(tbMapViewModel.TBMapBalances); I get an error that List is not part of any Model and is not found.
Also, below code is something I wrote trying to follow this SO post: update-multiple-records-at-once-in-asp-net-mvc - Initially I was trying to make it Async but I was getting even more errors and couldn't continue. I thought I am going to follow it as closely as possible in hope that it will get me a working starting point.
[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalances)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
// _context.Update(tbMapViewModel.TBMapBalances);
_context.SaveChanges();
}
return RedirectToAction("TbMapView");
}
EDIT #2 - A hero to the rescue - big thanks to #RudiVisser for help
First of all if any of you guys are - like me - stuck using .net core 1.0.0
make sure you upgrade to the latest version first (1.1.7 lts). Part of my grief was that I was an USER 1.0 and did not upgrade my installation as fixes and additions kept coming out. Don't be that guy, like I was...
All below is thanks to Rudi's help:
View
#using (Html.BeginForm("TbMapViewEdit", "TbMap"))
{
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#Html.EditorFor(m => m.TBMapBalances);
</tbody>
</table>
</div>
</div>
</div>
}
Put your "Method", "Controller" in (Html.BeginForm("TbMapViewEdit", "TbMap")) otherwise the form POST action will be set to the current location.
Model
Truncated for brevity. I have view model with List that I will be saving the data to and one other table just for displaying some info.
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; } = new List<TBMapBalances>();
[...]
}
Controller
[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalances)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.UniqueAdp = TbListId.UniqueAdp;
}
}
_context.SaveChanges();
}
return RedirectToAction("TbMapView");
}
Error that I was making here is that I was trying to replace the key with essentially the copy of itself (Find ID of 1 and set it to ID of 1) instead of picking up on the actual one field that I needed to edit which in my case was UniqueAdp.
Then came the new thing to me, which was Editor Template:
Editor Template
Create a folder called EditorTemplates in 'Shared' Folder under your 'Views' folder with the exact name of the model that you intend to edit. In my case the model was called TBMapBalances so I created a TBMapBalances.cshtml file inside the newly created folder, then pasted this (this was originally in my main view file):
#model ASPNET_Core_1_0.Models.TBMapBalances
<tr>
<td>
#Html.DisplayFor(Model => Model.TbMapId)
#Html.HiddenFor(Model => Model.TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.Line)</td>
<td>#Html.DisplayFor(Model => Model.MapResult)</td>
</tr>
By the way the new { #class = "control-label_DI" } is there to supposedly add class to each created input field. This doesn't seem to work in .net core and is left there just as a reminder to myself that I need to do this somehow.
Research:
Update multiple records at once in asp.net mvc
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms
http://www.binaryintellect.net/articles/b1e0b153-47f4-4b29-8583-958aa22d9284.aspx
How to bind an Array in MVC Core
https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core/
ASP.NET Core 1.0 POST IEnumerable<T> to controller
This problem has 2 parts to it, the first is that there needed to be a way to edit collections of data. This can be solved with EditorTemplates, which involves creating a single editor model and then calling #Html.EditorFor(..) on the collection of items you wish to edit.
A minimal sample (Full Fx, not Core) is available on Github.
The second problem was with the way the entities were being updated, the property being changed was wrong and hence not saving (the PK was being updated to the PK) and the entity was being attached when it's already tracked.
foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
try
{
_context.Update(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
It's important to remember what Entity Framework does for you when you retrieve a model from the database. It is automatically tracked by the context, and so it's already attached and ready to update, anything you change will be automatically tracked and subsequently saved.
The call to _context.Update(..) tries to attach the non-tracked models (from your POSTed data) to the context based on ID, but because you've already pulled that ID out (in your .Where(..).FirstOrDefault(..)) it's already tracked, and so blows up.
Also given that this is EFC 1.0 and you have no .Find(..) method, using .SingleOrDefault(..) is probably a better method to use on a primary key field.
Your rewritten code could be as so:
foreach (var postedModel in tbMapViewModel.TBMapBalancesList)
{
var dbModel = _context.TBMapBalances.SingleOrDefault(p => p.TbMapId == postedModel.TbMapId);
if (dbModel != null)
{
dbModel.UniqueAdp = postedModel.UniqueAdp;
}
}
await _context.SaveChangesAsync();
For posterity though I wouldn't recommend it for security reasons, if you wanted to attach your whole posted model to the context (based on ID) and update it, you can do so with code similar to your original, removing the foreach loop:
_context.UpdateRange(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
(I don't recommend it because everything that was posted will then be set in the database, from experience it's advisable to only set the fields you're expecting to update as per the first code set. It should, however, be quicker than the foreach loop given that you're not loading from the database and saving back in, only the latter)
Do you have the inputs for the comments already built into the razor page? I do not see them. What you would want to do is create a form with the input types that you want for each item in the loop inside the loop. Each form would then reference the iterator as a hidden value to pass when posted. If the loop is foreach(var item in Model.items){} you would have a form containing the inputs in that block with a hidden input that looks like <input type="hidden" name="index" value="#item.index"/> This will allow you to post with whatever identifier you need to attach that specific form data to the correct model.
See this answer for the structure of the form inside the loop Multiple forms on one MVC form, created with a loop, only the first submits data
I'm trying to build an inbox that is very similar to facebooks message inbox, where you have a list of conversations(I only want a list of a message title) and when you click the conversation or message title in my situation, I want the whole message to be rendered next to it in a partial view.
Here's my Inbox view:
#model BlocketProject.Models.ViewModels.ProfilePageViewModel
#{
ViewBag.Title = "Inbox";
}
<h2>Dina meddelanden:</h2><br />
<div class="left">
<table id="messageTable">
#foreach (var message in Model.UserMessages)
{
<tr>
<td>
<button type="submit" class="messageButton">
#if (message.Unread == true)
{
<h4 style="font-weight:bold;">#message.MessageTitle</h4>
}
else if (message.Unread == false)
{
<h4>#message.MessageTitle</h4>
}
</button>
</td>
</tr>
}
</table>
</div>
<div class="right">
#Html.Partial("ReadMessage")
</div>
When I click this message-element that is a button, I want to pass that messageId to the PartialView ReadMessage:
#model BlocketProject.Models.DbClasses.DbMessages
<h2>#Model.MessageTitle</h2><br />
<p>#Model.MessageText</p>
and the controller looks like this:
[HttpPost]
public ActionResult Inbox()
{
var allMessages = ConnectionHelper.GetAllMessages(ConnectionHelper.GetUserByEmail(User.Identity.Name).UserId);
var model = new ProfilePageViewModel();
model.UserMessages = allMessages;
return View("Inbox", model);
}
[HttpPost]
public ActionResult ReadMessage(int messageId)
{
var model = ConnectionHelper.GetMessageByMessageId(messageId);
return PartialView("ReadMessage", model);
}
I've tried passing the messageId through a post as you can see in my controller, but then the partialView is returned as a new page and I simply want to render it in the Inbox view.
Any ideas?
EDIT:
Jonesy's answer fixed my problem when I edited it like this:
Controller:
public ActionResult ReadMessage(int messageId)
{
var model = ConnectionHelper.GetMessageByMessageId(messageId);
return PartialView("ReadMessage", model);
}
View:
<div class="left">
<table id="messageTable">
#foreach (var message in Model.UserMessages)
{
<tr>
<td>
#using (Ajax.BeginForm("ReadMessage", new { #messageId = message.MessageId }, new AjaxOptions { UpdateTargetId = "showMessage" }, FormMethod.Post))
{
<button type="submit" class="messageButton">
#if (message.Unread == true)
{
<h4 style="font-weight:bold;">#message.MessageTitle</h4>
}
else if (message.Unread == false)
{
<h4>#message.MessageTitle</h4>
}
</button>
}
</td>
</tr>
}
</table>
</div>
<div class="right" id="showMessage">
#Html.Partial("ReadMessage", new BlocketProject.Models.DbClasses.DbMessages())
</div>
Razor is run on the server, before the page is rendered. Once the page is on the client, and they can click a message, the concept of the PartialView is gone - it's all just one HTML page.
The easiest way for you to do this is to use an Ajax.BeginForm where your button is, and on click, update an element with a partial view retrieved from the server. Something like:
#using(Ajax.BeginForm("ReadMessage", "Messages", new AjaxOptions() { UpdateTargetId = "showMessage" })) {
//...
}
//...
<div class="right" id="showMessage">
//ReadMessage partial rendered on button click
</div>
A little method that could help you :
protected ActionResult View(string viewName, object model)
{
if (ControllerContext.IsChildAction)
return PartialView(viewName, model);
else if (Request.IsAjaxRequest())
return PartialView(viewName, model);
else
return View(viewName, model);
}
This will return a PartialView when you call you action via #Html.RenderAction() or call the action via Ajax (jQuery).
You can then use jQuery to prevent the form to be posted and post it with Ajax or when you click on a message, you can also use jQuery to get the result from the action and insert it in your DOM.
I have a strongly typed view which has text fields and a submitted link. After editing data I press link and try to submit form. Placing a break point I am able to see control comes to action; modle is not null but all of its properties are always null, not sure where I m doing wrong. My tiny view code is:
#model BL.Model.Speaker
#using (Html.BeginForm())
{
<table>
<tr>
<td>#Html.EditorFor(s => #Model.Name)</td>
<td>#Html.EditorFor(s => #Model.Email)</td>
</tr>
</table>
#Html.ActionLink("Submit", "All");
}
and my controller action is:
public ActionResult All(Speaker model){
return View(database.Speakers.FirstOrDefault());
}
Help please
You should change your button from ActionLink to a submit button like this
#Html.ActionLink("Submit", "All");
By
<input type="submit" value="submit"/>
Also change your Action to recieve the data by post
[HttpPost]
public ActionResult All(Speaker model)
{
return View(database.Speakers.FirstOrDefault());
}
I've currently got a View like this:
#using (Html.BeginForm("Compare", "Carousel", FormMethod.Post))
{
#foreach (var product in Model.Products)
{
<tr>
<td>#product.Provider.Name</td>
<td>£#Math.Round(product.MonthlyPremium, 2, MidpointRounding.AwayFromZero)</td>
<td>#Html.ActionLink("More Details", "MoreDetails", new { id = product.ProductId })</td>
<td><button name="compare" value="#product.ProductId">Compare</button</td>
</tr>
}
}
Controller:
[HttpPost]
public ActionResult Compare(ProductsWithDetailModel model) // This is a guess, Model may not persist?
{
// Code here
return View("Index", model);
}
What I actually want is, when a button is clicked, the Controller is going to be called and the product that has been selected (the button clicked) will be added to a list of items.
How do I actually find out which button has been clicked in the Controller? How come I cant find any tutorials out there on the web that have done this before, surely its basic functionality? Or am I approaching it wrong?
ASP.NET MVC takes all inputs and send it by a verb to an action. The bind of these controls is made by the name attribute on your View and the value is made by depending of what control we are talking about (text, checkbox, radio, select, button etc...). You could have a a property on your ProductsWithDetailModel called compare and it would recevie the value of the button you've clicked.
How about having 1 form per item?
E.g.
#foreach (var product in Model.Products)
{
<tr>
<td>#product.Provider.Name</td>
<td>£#Math.Round(product.MonthlyPremium, 2, MidpointRounding.AwayFromZero)</td>
<td>#Html.ActionLink("More Details", "MoreDetails", new { id = product.ProductId })</td>
<td>
#using (Html.BeginForm("Compare", "Carousel", FormMethod.Post))
{
<input type="hidden" name="productId" value="#product.ProductId" />
<button name="compare">Compare</button>
}
</td>
</tr>
}
use onclick. set js hidden value in loop. in controller check.
I am new to ASP.NET MVC, so please help me to solve this, at a first glance, simple issue.
I have a table of students on my View and a Load More link below it. By clicking on this link I need to load more students records with AJAX.
So this is my View (omitted unnecessary data)
<table id="studentTable">
<thead>
...
</thead>
<tbody>
#Html.Partial("_StudentDetailsList", Model)
</tbody>
</table>
#Ajax.ActionLink("Load More", "LoadMoreStudents", new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "studentTable"
})
_StudentDetailsList partial view to display students
#model IEnumerable<MvcApplication1.Models.Student>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
</tr>
}
action
public ActionResult LoadMoreStudents(int skip = 0)
{
return PartialView("_StudentDetailsList", studentRepository.Get(orderBy: s => s.OrderBy(st => st.FirstName).OrderBy(st => st.LastName)).Skip(skip).Take(10));
}
So as you can see, I want to load next 10 students every time, but I have to know how much are already loaded. So the question is: from where should I get this skip parameter which I want to pass to the Action from the View. How can I count on the View how many partial views are already loaded and how many students each partial has? Or is it a bad practice to pass this parameter to action, maybe I should handle it somewhere else (e.g. some service)? Please advice the right way to do this. Thanks.
P.S. Please feel free to edit the caption as I can't come up with appropriate one.
The easiest method would be to create a ViewModel that contains these variables for you.
ViewModel
public class YourViewModel(){
public int skip {get;set;}
public IEnumerable<StudentDetails> StudentDetailList {get;set;}
}
The Controller/Action Method
public ActionResult LoadMoreStudents(int skip = 0)
{
return PartialView("_StudentDetailsList", new YourViewModel{
StudentDetailList = studentRepository.Get(orderBy: s => s.OrderBy(st => st.FirstName).OrderBy(st => st.LastName)).Skip(skip).Take(10),
skip = skip + 10});
}
The View
#Html.Partial("_StudentDetailsList", Model.StudentDetailList)
The Partial View
#model MvcApplication1.Models.YourViewModel
<table id="studentTable">
<thead>
...
</thead>
<tbody>
#foreach (var item in Model.StudentDetailList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
</tr>
}
</tbody>
</table>
#Ajax.ActionLink("Load More", "LoadMoreStudents", new {skip = Model.skip}, new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "studentTable"
})
Basically, you have to be able to dynamically update the Ajax.ActionLink with values coming back from the controller. Here, I have moved it into the PartialView (arguabley you could move the entire table including the fetch more link into the PartialView and have it be even cleaner).
You are on the way. You should replace the Ajax.ActionLink for a link with $.get, and retrieve the skip by counting the numbers of rows at the tbody that you already have:
First replace the ActionLink for:
Load More Students here!
Second, create this function to do the get:
$('mylink').click(function (e) {
e.preventDefault();
//Gets the number of rows that you alredy have loaded
var rowCount = $('studentTable').find('tbody > tr').length;
//The URL for the get, with the number of rows
var url = "http://www.myurl.com/controller/action?skip=" + rowCount;
$.get(url, function (data) {
//Append the content from the partial view to the tbody
$('studentTable').find('tbody').append(data);
});
});
I think that this is a good way as well!
Hopes this help you!