MVC Pagination in both the main and partial view - c#

My app shows two tabulated set of data: the main table with the primary data and a sub-table with related records in a rendered partial view.
For the main table, I have implemented a simple paging functionality. I use PageInfo class to store information on actual page, maximum page, number of all pages etc. All this is returned to View in a ViewModel. I also use hidden control and POST form method to send an altered page back to a Controller.
First I store the current page in a hidden control and then use jQuery to alter the page (+1, -1, frist, l;ast) and send such updated value of a control to the Controller. I struggled to send the page info to the Controller so added a hidden form field and retrieve its value in Action using ModelBinder.
The problem is that I render partial View which is also tabulated data. I would like to apply independent inner page navigation in this subtable. However, I don't know how to use second form if it's possible. It looks like POST method is not the best option.
How can I send separated page information to a partial view. Then how to control whether user clicked inner version of next, previous buttons etc. How can I send this info to controller of the Partial View?
EDIT.
In edit, I have added
A copy of class representing pagination information
Controller with specific actions.
View
This is the way I have done it. The first time Index() is requested, I set default values for SortingPagingFilteringInfo (currently it holds only info on pagination). Everything is put into ViewModel and sent to View. I use Ajax to replace html in <div id="listOfRecordings"></div> with code generated by a partial view and also to navigate around pages. As can be seen, I also use a Form Control in this little code
#using (Html.BeginForm())
{
#Html.Hidden("page", Model.SPFInfo.CurrentPage)
}
to return updated page info. I replace its values in JavaScript code below. At the moment it's just a page number. I find it difficult to apply this technique to added pagination to the partial view which also shows tabulated data. They are related to main table by foreign key. It's 1 to many relationship. I tried to add extra Form with another Form Control to PartialView but then I'd have to have two Forms. I don't know how to POST to two different actions / controllers. I haven't played with multi forms in one View. Perhaps there's a better way to solve this.
Pagination model:
public class SortingPagingFilteringInfo
{
public int CurrentPage { get; set; }
public int MaxPage { get; set; }
public int PageSize { get; set; }
public int PageCount { get; set; }
}
public class StudentViewModelWithFeatures
{
public IEnumerable<StudentViewModel> Students { get; set; }
public SortingPagingFilteringInfo SPFInfo { get; set; }
}
Controller and GET and POST Index() actions:
#region Index
[HttpGet]
public ActionResult Index()
{
StudentViewModelWithFeatures ViewModel =
new StudentViewModelWithFeatures();
ViewModel.SPFInfo = new SortingPagingFilteringInfo();
using (MyDBContext dbContext = new MyDBContext())
{
ViewModel.Students = getViewModel(dbContext).OrderBy(x => x.patient);
ViewModel.SPFInfo.CurrentPage = 1;
ViewModel.SPFInfo.MaxPage =
Convert
.ToInt32(Math.Ceiling((double)getViewModel(dbContext)
.Count() / ViewModel.SPFInfo.PageSize));
int skipRecords =
(ViewModel.SPFInfo.CurrentPage - 1) * ViewModel.SPFInfo.PageSize;
ViewModel.Students = ViewModel.Students
.Skip(skipRecords)
.Take(ViewModel.SPFInfo.PageSize)
.ToList();
}
return View(ViewModel);
}
[HttpPost, ActionName("Index")]
public ActionResult IndexPost(int? page)
{
StudentViewModelWithFeatures ViewModel = new StudentViewModelWithFeatures();
ViewModel.SPFInfo = new SortingPagingFilteringInfo();
using (MyDBContext dbContext = new MyDBContext())
{
ViewModel.Students = getViewModel(dbContext).OrderBy(x => x.patient);
// paging
ViewModel.SPFInfo.MaxPage =
Convert
.ToInt32(Math.Ceiling((double)getViewModel(dbContext)
.Count() / GlobalPageSize.Value));
page = page ?? 1;
page = page < 1 ? 1 : page;
page = page > ViewModel.SPFInfo.MaxPage ? ViewModel.SPFInfo.MaxPage : page;
ViewModel.SPFInfo.CurrentPage = page.Value;
int skipRecords =
(ViewModel.SPFInfo.CurrentPage - 1) * ViewModel.SPFInfo.PageSize;
ViewModel.Students = ViewModel.Students
.Skip(skipRecords)
.Take(ViewModel.SPFInfo.PageSize)
.ToList();
}
return View(ViewModel);
}
#endregion
View with JavaScript, div for a PartialView:
#model Program.ViewModels.StudentViewModelWithFeatures
#{
ViewBag.Title = "Students";
}
#using (Html.BeginForm())
{
#Html.Hidden("page", Model.SPFInfo.CurrentPage)
}
<div>
<!-- ... -->
<div class="navigation-block">
<span class="navigation-link" data-id="first"><<< First </span>   
<span class="navigation-link" data-id="previous">< Previous</span>  
#Model.SPFInfo.CurrentPage / #Model.SPFInfo.MaxPage
  <span class="navigation-link" data-id="next">Next ></span>   
<span class="navigation-link" data-id="last">Last >>></span>
</div>
</div>
<div id="listOfRecordings"></div> <!-- Partial view is placed here -->
<script>
// for better readability I put this java code below!
</script>
JavaScript code in the View
$('.navigation-link').click(function (evt) {
var id = $(this).data('id');
var url = '#Url.Action("Index", "Students")';
var MaxPage = "#Model.SPFInfo.MaxPage";
var CurPage = "#Model.SPFInfo.CurrentPage";
if (id == 'first') {
$('#page').val(1);
}
if (id == 'last') {
$('#page').val(parseInt(MaxPage));
}
if (id == 'next') {
$('#page').val(parseInt(CurPage) + 1);
}
if (id == 'previous') {
$('#page').val(parseInt(CurPage) - 1);
}
$('form').submit();
});
$('.show-list').click(function () {
$('.show-list').click(function () {
var id = $(this).data('id');
url = '#Url.Action("List", "Recordings")';
$('#listOfRecordings').html("Retrieveing data ...");
$.get(url, { StudentID: id }, function (data) {
$('#listOfRecordings').html(data);
});
});
});

Related

Remember search params

I'm new in mvc and I try to create a simple page with table and ajax search.
For example, I have a search model, which pass parameters from form to controller.
Model:
public class OrderSearchViewModel
{
[Display(ResourceType = typeof(Lang), Name = "OrderID")]
public int? OrderID { get; set; }
[Display(ResourceType = typeof(Lang), Name = "DeliveryType")]
public int? DeliveryTypeID { get; set; }
[Display(ResourceType = typeof(Lang), Name = "Partner")]
public string CustomerName { get; set; }
public SelectList DeliveryTypes { get; set; }
}
In controller I have an action witch return View with form:
public ActionResult Index()
{
var ordersSearchModel = // default init;
return View(model);
}
In my Index.cshtml I have a form
#model Models.Order.OrderSearchViewModel
<div class="row">
#using (Ajax.BeginForm("Orders", "Order", new AjaxOptions {UpdateTargetId = "ordersList"}, new {#id = "searchForm", #class = "form-horizontal"}))
{
// Editors templates for each params
}
</div>
<div id="ordersList" class="row">
</div>
}
In my Controller I have a method, witch take search model and return a partial view
[HttpPost]
public async Task<ActionResult> Orders(OrderSearchViewModel model, int page = 1, int pageSize = 50)
{
var models = // connect to db and get data filtered by model params
return PartilaView("_View", models);
}
In result partial I have a table with order num and link to edit view.
In edit view I have a link back to search:
#Html.ActionLink("Back", "Index", "Order", new { #class = "btn btn-default" })
And by click this link I get the Index view in default (without search parameters) and user must fill it once again.
What will be the best practice to remember user search parameters?
Thanks for any advice.
As devqon said, the preferred approach is to use query parameters, but it does sound like your usage might make this a bit difficult to manage
TempData/SessionData may solve your issue, but using this approach will cause problems if the user decides to use your application in multiple tabs/windows (they will all share the same search params)
Something that might be worth looking into is SessionStorage.(Link below)
This type of storage persists as long as the browser stays open
And importantly
Opening a page in a new tab or window will cause a new session to be initiated
So your pages shouldn't share state.
More details here:
https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage

Passing List of Model in Form Submission, Each Generated by HTML Partial View to a Controller Action ASP.Net MVC 5

Is there any way to pass list of models, each generated by HTML Partial View to a Controller Action in ASP.Net MVC 5?
So I have a View (with a model called WordModel) where I call a HTML partial View (with a model called MeaningModel) multiple times in a form like the following:
#using (Html.BeginForm("Create", "Entry", FormMethod.Post, new { id = "createForm" })) {
#Html.AntiForgeryToken()
<!--some other things here-->
#Html.Partial("_Meaning", new MeaningModel() { Number = 1 })
#Html.Partial("_Meaning", new MeaningModel() { Number = 2 })
#Html.Partial("_Meaning", new MeaningModel() { Number = 3 })
<!--some other things here-->
}
And the MeaningModel consists of multiple basic elements like the following:
public class MeaningModel {
public string MeaningValue { get; set; }
public int HomonimNumber { get; set; }
public string Example1 { get; set; }
public string Example2 { get; set; }
//and so on
}
Now, in my Controller, I have an Action which handles the Create form submission:
// POST: Meaning/Create
[HttpPost]
public ActionResult Create(WordModel model, List<MeaningModel> meanings, FormCollection collection) {
try {
// TODO: Add insert logic here
return RedirectToAction("Index");
} catch {
return View();
}
}
I could get the values I put in the main View in the WordModel model, but not the values in the Partial Views which consists of elements forming the List<MeaningModel> meanings. Is there any way to access them?
I could give each element in the MeaningModel different name per meaning model (such as MeaningValue_1, MeaningValue_2, MeaningValue_3, HomonimNumber_1, HomonimNumber_2, HomonimNumber_3, and so on) so that they will be captured by the FormCollection. But, as much as possible, I want to take advantage of the MeaningModel I have created and get them by the model.
Any way to do that?

Refreshing MVC PartialView with new Model on DropDownList Change

I have a budgeting application, I have 3 models I am pulling into 1 view.
Budget - get the users budgeting information details (i.e, name of budget, date, etc.)
BillBudgetTotal - Allows the user to add a cumulative total for that budget (i.d., budgetid, total amount)
BudgetTotalBreakdown - Allows the user to break their budget down into futher details (i.e., break the total amount down by bill name (water, gas, electric, misc, etc.)
The UI will give the user the option to select a budget (via dropdown) they want to work in and then allow them to enter in their dollar amounts based on which bill they selected.
Problem: I am trying to figure out a way to allow the partial view (the area under the dropdown) to refresh based on the dropdown selection. I can't seem to get the partial to refresh with the updated model (it needs to be reset based on the value of the dropdownlist selection). I have exhausted multiple options on stack overflow.
Something like this:
Model:
public class MyBudgets : Financials
{
public Budgets Budget{ get; set; }
public BillBudgetTotal BudgetTotals { get; set; }
public BillBudgetTotalBreakdown BudgetTotalBreakdown { get; set; }
}
Html:
<div class="col-md-3"></div>
<div class="row col-md-6">
#Html.DropDownListFor(model => model.Budget.SelectedBills, Model.Budget.SelectedBills.Select(b => new SelectListItem() { Value = b.Bill_Id.ToString(), Text = b.Bill}), "Select A Bill...", new { #class = "form-control"})
</div>
<div class="col-md-3"></div>
<br /><br />
<hr />
<div id="billBudgetPartial">
#Html.Partial("Budgeting/_BillTotalAmount", Model);
</div>
Controller:
[HttpGet]
public ActionResult Budgets(int budgetId)
{
MyBudgets model = new MyBudgets
{
Budgets = _executionRepository.RetrieveBudgets(budgetId)
};
model.Budget.SelectedBills = _executionRepository.SetSelectedBudgets(budgetId);
return View(model);
}
[HttpPost]
public ActionResult Budgets()
{
return Json(new { success = "false" });
}
public ActionResult BillTotalAmount(int id)
{
var model = new MyBudgets
{
Budgets = _executionRepository.RetrieveBudgetsByBillBudget(id),
BillBudgetTotal = _executionRepository.RetrieveBillBudgetByBillId(id),
BillBudgetTotalBreakdown = _executionRepository.RetrieveBillBudgetTotalBreakdown (id)
};
return PartialView("Execution/_BillTotalAmount", model);
}
You can use ajax to do partial update to your page. when razor render your page, it will generate a SELECT element with the id "Budget_SelectedBills". So listen to the change event on this dropdown, get the selected value and send that to your server(an action method) and let it return the partial view for the markup you want below. You may use jQuery load method to update the DOM with the new markup coming from server.
#section Scripts
{
<script>
$(function(){
$("#Budget_SelectedBills").change(function(e){
var val=$(this).val();
$("#billBudgetPartial").load("/Budgeting/BillDetails/"+val);
});
});
</script>
}
Assuming you have BillDetails action method in BudgetingController which accpets the billId an return the partial view for the bottom portion of screen.
public ActionResult BillDetails(int id)
{
var model = ReplaceYourModelForBillTotalAmountViewHere();
return PartialView("Budgeting/_BillTotalAmount", model);
}
EDIT: As per the comment
How can I pass 2 parameters in this? like not just the id from the
drop but something else the list the #Model.BudgetId
If your javascript code is in the same razor view, you can simply use Model.BudgetId as the second querystring param value.
Assuming BudgetId is an int type
#secion Scripts
{
<script>
$(function(){
$("#Budget_SelectedBills").change(function(e){
var val=$(this).val();
$("#billBudgetPartial").load("/Budgeting/BillDetails/"+val
+"?budgetId="+#Model.BudgetId);
});
});
</script>
}
Now make sure that your action method has this second parameter
public ActionResult BillDetails(int id,int budgetId)
{
var model = ReplaceYourModelForBillTotalAmountViewHere();
return PartialView("Budgeting/_BillTotalAmount", model);
}
If your javascript code is in an external js file, you may keep Model.BudgetId to somewhere in the DOM and read that. Either a hidden field or keep it in html 5 data attributes of the select element.

Passing a parameter back to a view after performing a form action?

I have a view that loads a record with a certain record number. Once the page is loaded, it gives the user an opportunity to login for additional information. Once the login logic is performed, I need to return to that same view with the same record number intact. I am passing the record number to the action using a hidden input in the form. What I can't seem to figure out is how to return to that same view and provide it with that record #. The code I am trying below is not working. I know this is MVC 101 stuff but a hint in the right direction would be appreciated, or feel free to scrap my method and suggest something better!
Form in view:
<form action="/MyView/Authenticate/#item.ID" method="post" enctype="multipart/form-data">
<input name="form_id" type="hidden" value="#item.ID">
.....
Form action:
[HttpPost]
public ActionResult Authenticate()
{
int myid = Convert.ToInt16(Request["form_id"]);
.....
return View("Index", new { id = myid } );
}
EDIT:
It turns out that the correct view is being returned, but it is expecting a model item type of "JobSummaryModel" per the Index action result below. So the question I actually need answered is, how do I pass both the record id and this view model to it?
public ActionResult Index(int id = 0)
{
List<JobSummaryModel> jdata;
ViewBag.IsResults = false;
if (id != 0)
{
ViewBag.IsResults = true;
}
jdata = db.Jobs.Where(c => c.ID == id).Select(c => new JobSummaryModel() { ID = c.ID, Name = c.Name, City = c.City, PostalCode = c.PostalCode, JobDescription = c.PositionDescription }).ToList();
return View(jdata);
}
EDIT:
Thanks Reddy, your suggestions worked! My only remaining issue is that when I return to my Index view from the Authenticate action, I do not seem to have my "jdata". Is my Index action result not being rerun when I return the Index view via my Authenticate action? I am coming from a web forms background where, in an instance like this, the Load/Init events would automatically run when a form is loaded. Do I need to bind my "jdata" in the Authenticate action and include it in the viewmodel?
EDIT: Resolved. Changed my "return View" to a "return RedirectToAction" to resolve my final issue. Thanks everyone!
Answer For your after Edit:
All you want to pass to view is a int Id and your List<JobSummaryModel> jdata right?
So create a ViewModel JObSummaryModelHelper
Public class JObSummaryModelHelper
{
public int Id {get;set;}
public List<JobSummaryModel> jdata {get;set;}
}
Now in your controller
public ActionResult Index(int id = 0)
{
JObSummaryModelHelper jobDetails = new JObSummaryModelHelper();
jobDetails.Id = id;
ViewBag.IsResults = false;
if (id != 0)
{
ViewBag.IsResults = true;
}
jobDetails .jdata = db.Jobs.Where(c => c.ID == id).Select(c => new JobSummaryModel() { ID = c.ID, Name = c.Name, City = c.City, PostalCode = c.PostalCode, JobDescription = c.PositionDescription }).ToList();
return View(jobDetails );
}
Now make sure your view is set to expect this new viewmodel
#model JObSummaryModelHelper
carry on with your manipulation......
You are better off creating a ViewModel for this like so:
Create a View Model class i.e.
public class AuthViewModel
{
public int MyId { get; set; }
}
In your View put the following directive at the top:
#model AuthViewModel
In your initial [HttpGet] method return the view model:
[HttpGet]
public ActionResult Authenticate()
{
var model = new AuthViewModel { MyId = 123 };
return View("Index", model );
}
It's best to use Html helpers in your view, so you can change it to this:
#using(Html.BeginForm()
{
#Html.HiddenFor(m => m.MyId)
...
}
The above uses naming conventions to post back to the action that you are on.
Then return it to your view like this:
[HttpPost]
public ActionResult Authenticate(AuthViewModel model)
{
int myid = model.MyId;
return View("Index", model );
}
Then you can output using this razor syntax #Model.MyId
It's really worth doing some tutorials to learn the conventions, a small amount of time invested in this will save you a lot of time in the future.
Instead of
return View("Index", new { id = myid } );
could you do
return Index(myid);

NullReference error on foreach used to create radiobuttons on POST

I am teaching myself asp .net mvc3. I have a "add property" form which allows user to upload property details to the website. I have been struggling with this error for a long time now.
For simplification, lets consider that I have two tables in my database.
CustomerTypes: The database has 1 Owner, 2 Broker, 3 Commercial etc
Property: This is the table that gets populated by the form.
I use CustomerTypes (and other such tables) to create radio buttons. The user fills the form and selects a choice for "customer type". However, I get an "object reference not set to an instance of an object" error on submit. This is is because "null" is
set for Model.CustomerTypes. However, Model.CustomerTypes is only used to create radio buttons. I am not sure what is wrong. The code is below:
View:
#model Website.ViewModels.AddPropertyViewModel
<fieldset>
<legend>Property</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Property.CustomerType)
#foreach (var item in Model.CustomerTypes)
{
#Html.RadioButtonFor(model => model.Property.CustomerType, Convert.ToInt32(item.Value)) #item.Text
}
</div>
...
AddPropertyViewModel:
namespace Website.ViewModels
{
public class AddPropertyViewModel
{
public Property Property { get; set; }
...
public IEnumerable<SelectListItem> CustomerTypes { get; set; }
...
}
Controller:
public ActionResult AddProperty()
{
AddPropertyViewModel viewModel = new AddPropertyViewModel
{
...
CustomerTypes = websiterepository.GetCustomerTypeSelectList(),
...
};
return View(viewModel);
GetCustomerTypeSelectList functions is:
public IEnumerable<SelectListItem> GetCustomerTypeSelectList()
{
var customerTypes = from p in db.CustomerType
orderby p.CustomerTypeDescription
select new SelectListItem
{
Text = p.CustomerTypeDescription,
Value = SqlFunctions.StringConvert((double)p.CustomerTypeId)
};
return customerTypes;
}
The value in POST is set for Property_CustomerType correctly based on the selection
--- Added further info ---
I start the form as:
#using (Html.BeginForm("AddProperty", "Property", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
...
}
The controller is:
[HttpPost]
public ActionResult AddProperty(AddPropertyViewModel viewModel)
{
if (ModelState.IsValid)
{
//
if (viewModel.File1.ContentLength > 0)
{
var fileName = Path.GetFileName(viewModel.File1.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data"), fileName);
viewModel.File1.SaveAs(path);
}
var property = viewModel.Property;
websiterepository.Add(property);
return RedirectToAction("Index", "Home");
}
return View(viewModel);
}
Here is a screenshot of error:
I have tried submitting the form commenting these radio buttons and it works.
The issue is that CustomerTypes isn't populated when your render the view after posting to the server.
If we look at the flow of actions being performed we see that
You populate the CustomerTypes collection before rendering the
inital page
You post your data back to the server but do not
preserve the CustomerTypes collection (Because there's no need to)
You render the view again but this time without populating
CustomerTypes.
Kaboom!
Populating the CustomerTypes property before you return the view for the second time should fix your problem:
[HttpPost]
public ActionResult AddProperty(AddPropertyViewModel viewModel)
{
[...]
viewModel.CustomerTypes = websiterepository.GetCustomerTypeSelectList();
return View(viewModel);
}

Categories

Resources