I am trying to pass a model to the controller using an ajax post, but every time I do so, the model is null and I lose all the data I am trying to persist.
The ajax call:
$('#next').click(function (e) {
e.preventDefault();
var url = '#Url.Action("Manage")'
var _model = JSON.stringify(#Html.Raw(Json.Encode(Model)));
var _page = #(Model.pager.CurrentPage + 1);
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: url,
data: JSON.stringify({ 'model': _model, 'page': _page }),
success: function(result) {
console.log(result);
}
});
});
When I look at the serialized object, it looks like a correctly formatted JSON object with no errors thrown in the developer console.
The link that is triggering this jQuery call is just a basic action link:
#Html.ActionLink(">", "Manage", "Announcements", null, new { id = "next" })
My model is a little more complicated...
public class ManageViewModel : IEnumerable<EditViewModel>
{
[Display(Name="Start Date")]
[DataType(DataType.DateTime)]
public DateTime? StartDate { get; set; }
[Display(Name="End Date")]
[DataType(DataType.DateTime)]
public DateTime? EndDate { get; set; }
public string SearchString { get; set; }
public Pager pager { get; set; }
public List<EditViewModel> Data { get; set; }
public List<CategoryViewModel> Categories { get; set; }
public ManageViewModel()
{
Data = new List<EditViewModel>();
Categories = new List<CategoryViewModel>();
}
public IEnumerator<EditViewModel> GetEnumerator()
{
return Data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Data.GetEnumerator();
}
}
public class Pager
{
public int TotalItems { get; private set; }
public int CurrentPage { get; private set; }
public int PageSize { get; private set; }
public int TotalPages { get; private set; }
public int StartPage { get; private set; }
public int EndPage { get; private set; }
public Pager(int totalItems, int? page, int pageSize = 10)
{
int totalPages = (int)Math.Ceiling((decimal)totalItems / (decimal)pageSize);
int currentPage = page ?? 1;
int startPage = currentPage - 5;
int endPage = currentPage + 4;
if(startPage <= 0)
{
endPage -= (startPage - 1);
startPage = 1;
}
if(endPage > totalPages)
{
endPage = totalPages;
if(endPage > 10)
{
startPage = endPage - 9;
}
}
TotalItems = totalItems;
TotalPages = totalPages;
CurrentPage = currentPage;
PageSize = pageSize;
EndPage = endPage;
StartPage = startPage;
}
}
I can't convert the links in to a form because that breaks the pagination. Or maybe I'm just not understanding the full picture here.
Here is the section of the View where the pagination is occurring
if (Model.pager.EndPage > 1)
{
<div style="color:#337AB7; padding-bottom: 0px;">Page #Model.pager.CurrentPage of #Model.pager.TotalPages</div>
<ul class="pagination">
#if (Model.pager.CurrentPage > 1)
{
<li>
#Html.ActionLink("<<", "Manage", new { model = Model, start = Model.StartDate, end = Model.EndDate, query = Model.SearchString }, null)
</li>
<li>
#Html.ActionLink("<", "Manage", new { model = Model, page = Model.pager.CurrentPage - 1, start = Model.StartDate, end = Model.EndDate, query = Model.SearchString }, null)
</li>
}
#for (var _page = Model.pager.StartPage; _page < Model.pager.EndPage + 1; _page++)
{
<li class="#(_page == Model.pager.CurrentPage ? "active" : "")">
#Html.ActionLink(_page.ToString(), "Manage", new { model = Model, page = _page, start = Model.StartDate, end = Model.EndDate, query = Model.SearchString }, null)
</li>
}
#if (Model.pager.CurrentPage < Model.pager.TotalPages)
{
<li>
#Html.ActionLink(">", "Manage", "Announcements", null, new { id = "next" })
</li>
<li>
#Html.ActionLink(">>", "Manage", new { model = Model, page = Model.pager.TotalPages, start = Model.StartDate, end = Model.EndDate, query = Model.SearchString }, null)
</li>
}
</ul>
}
The action links attempting to pass the model, obviously don't work but I left them because I am focusing on getting one to work, the one listed previously, before getting all the others configured.
I have looked at the following SO posts and have had no luck with them:
Post an MVC model with AJAX?
Model properties null on Ajax post from list box change event
How to send a model in jQuery $.ajax() post request to MVC controller method
Pass Model To Controller using Jquery/Ajax
How to pass model in ajax post request?
Any ideas on how I might be able to do this? I need the model data to persist for searching/filtering which is done through a form. Thank you in advance for taking a look and giving your insights!
You need to include a parameterless constructor for your models. From the screenshot you have sent it seems there isn't a parameterless constructor for public Pager model.
Related
I want to add paging to my table in ASP.NET Core MVC.
Issue: how can I link TotalRecords, PageNo, & PageSize from controller to the view? In Razor page its simple to bind them, but I don't know how to do this in MVC.
TotalRecords = Query.AsQueryable().Count();
PageNo = p;
PageSize = s;
Controller:
// Display Data
public async Task<IActionResult> DisplayData(int p = 1, int s = 20)
{
try
{
IQueryable<MyModel> query = await _services.GetData();
// Add Paging
query = await AddPaging(p, s, query);
// Display Data
List<MyModel> myData = await query.AsNoTracking().ToListAsync();
return View(myData); // return view with data
}
catch
{
return View(); // return empty view
}
}
// add paging
private async Task<IQueryable<MyModel>> AddPaging(int p, int s, IQueryable<MyModel> Query)
{
TotalRecords = Query.AsQueryable().Count();
PageNo = p;
PageSize = s;
Query = Query.Skip((p - 1) * s).Take(s);
return Query;
} // end of paging
View:
#model List<MyModel>
<table>
....
</table>
<paging total-records="Model.TotalRecords"
page-no="Model.PageNo"
show-prev-next="false"
show-first-last="false">
</paging>
I suggest to solve this problem with OOP standards.
First make a class that will do pagination.
Example:
public class Paginate<T>
{
public Paginate(List<T> datas, int currentPage, int totalPage)
{
Datas = datas;
CurrentPage = currentPage;
TotalPage = totalPage;
}
public List<T> Datas { get; set; }
public int CurrentPage { get; set; }
public int TotalPage { get; set; }
public bool HasPrev
{
get
{
return CurrentPage > 1;
}
}
public bool HasNext
{
get
{
return CurrentPage < TotalPage;
}
}
}
Then send data to view by this class.
public async Task<IActionResult> Index(int page = 1, int take = 5)
{
var datas = await _context.Datas
.Skip((page - 1) * take)
.Take(take)
.AsNoTracking()
.ToListAsync();
int dataCount = await _context.Datas.CountAsync();
int initCount = (int)Math.Ceiling((decimal)count / take);
Paginate<Categories> result = new Paginate<Datas>(datas , page, initCount );
return View(result);
}
Last step is sending initial datas to controller from view.
First your view will take data from pagination class.
#model Paginate<List<MyModel>>
Then your pagination component will send data to controller:
<ul>
<li class="#(Model.HasPrev?"":"disabled")">
<a asp-action="Index" asp-route-page="#(Model.CurrentPage-1)">Previous</a>
</li>
#for (int i = 1; i <= Model.TotalPage; i++)
{
<li class="#(i==Model.CurrentPage ? "disabled":"")">
<a asp-action="Index" asp-route-page="#i">#i</a>
</li>
}
<li class="#(Model.HasNext?"":"disabled")">
<a asp-action="Index" asp-route-page="#(Model.CurrentPage+1)">Next</a>
</li>
</ul>
If you analize all code I think it will be useful. Or you can take the business logic and apply it to your DisplayData and AddPaging methods.
I have a model class named categories that retrieves data from database :
public class HomeController : Controller
{
private StoreContext db;
public HomeController()
{
db = new StoreContext();
}
public ActionResult Categories()
{
return View(db.Categories.ToList());
}
}
I want to use DropDownList helper method to display them in the view and I want all the categories inside it to be clickable, say, when you click them it has to adress you to the specified url belongs to the clicked category. Is there a way to make this happen with DropDownList helper? if yes then how?
You can do this but you have to use Jquery . If you ask how?
For example:
My sample entity
public class Category
{
public int Id { get; set; }
public string Url{ get; set; }
public string Name { get; set; }
}
My action:
public IActionResult Categories()
{
var list = new List<Category>();
for (int i = 0; i < 10; i++)
{
list.Add(new Category(){Id = i, Url = "https://stackoverflow.com", Name = "stackoverflow" });
}
var selectList = list.Select(x => new SelectListItem() {Value = Url, Text = x.Name})
.ToList();
return View(selectList);
}
in my View:
#Html.DropDownList("url",Model, "Choose a URL", new { id = "url_list" })
and then using jquery you could subscribe for the change event of this dropdownlist and navigate to the corresponding url:
$(function() {
$('#url_list').change(function() {
var url = $(this).val();
if (url != null && url != '') {
window.location.href = url;
}
});
});
I have a viewpage like below
this is the controller method for above view
public ActionResult Add_Product()
{
var model = new AddNewProduct
{
ListProductFields = db.AB_ProductTypeCategoryField.ToList()
};
return View(model);
}
this is model class for above view
public class AddNewProduct
{
public string Product_ID { get; set; }
public string ProductTypeID { get; set; }
public string ProductCategoryID { get; set; }
public string Subsidary_ID { get; set; }
public IList<AB_ProductTypeCategoryField> ListProductFields { get; set; }
}
public partial class AB_ProductTypeCategoryField
{
public string Field_Value_EN { get; set; }
public string Field_Value_AR { get; set; }
}
this is viewpage
#model albaraka.Models.AddNewProduct
#using (Html.BeginForm())
{
#for (int i = 0; i < Model.ListProductFields.Count; i++)
{
#Html.TextAreaFor(m => m.ListProductFields[i].Field_Value_EN, new { #class = "form-control summernote", #row = 5 })
#Html.TextAreaFor(m => m.ListProductFields[i].Field_Value_AR, new { #class = "form-control summernote", #row = 5 })
}
}
Now I want to add pagination for above view page and limit one ListProductFields per one page , for that I following this Tutorial
So I change my code to like this
public ActionResult Add_Product(int? page)
{
var dummyItems = db.AB_ProductTypeCategoryField.Select(x => "Item " + x);
var pager = new PaginationModel.Pager(dummyItems.Count(), page);
var model = new AddNewProduct
{
Items = dummyItems.Skip((pager.CurrentPage - 1) * pager.PageSize).Take(pager.PageSize).ToList(),
Pager = pager
};
return View(model);
}
But then I'm getting following Run-time Error
Values of type 'AB_ProductTypeCategoryField' can not be converted to string.
Just try with following code , so you cannot use Skip method alone. so have use OrderBy before it
var dummyItems = db.AB_ProductTypeCategoryField;
var pager = new PaginationModel.Pager(dummyItems.Count(), page);
var model = new AddNewProduct
{
ListProductFields = dummyItems.OrderBy(i => i.SomeProperty).Skip((pager.CurrentPage - 1) * pager.PageSize).Take(pager.PageSize).ToList(),
Pager = pager
};
return View(model);
The issue of your observation is below line
var dummyItems = db.AB_ProductTypeCategoryField.Select(x => "Item " + x);
Since you are trying to select object with string concatenate operation ("Item " + x) that's why exception is thrown as AB_ProductTypeCategoryField is an object. I don't think you require "Item " + x at all.
You can change AddNewProdcut action implementation as
public ActionResult Add_Product(int? page)
{
var pager = new PaginationModel.Pager(dummyItems.Count(), page);
var model = new AddNewProduct
{
ListProductFields = db.AB_ProductTypeCategoryField.Skip((pager.CurrentPage - 1) * pager.PageSize).Take(pager.PageSize).ToList(),
Pager = pager
};
return View(model);
}
I have a page that is a list of users. The controller's Index function is the action responsible for showing the page of users. The user can select those users and chose to delete them.
I perform the delete action with an ajax request, however then the page's list of users is out of date. So I reload the page because I want to re-use the index action, and all the query string parameters are still there. This means I'm performing two round trips. How do I avoid this?
function DeleteUsers()
{
var selectedUserIds = ;
$.post("/Account/DeleteUsers",
{
userIds: selectedUserIds
},
function (data) {
if( data.status == "success"){
location.reload();
}
});
}
Index function:
[AuthorizeActionFilter]
public ActionResult Index(UserModel model)
{
ViewData["PageTitle"] = ServiceSite.Resources.Resources.REGISTERED_USERS;
ViewBag.MaxUsersPerPage = PAGE_MAX_COUNT;
if(model == null)
{
model = new UserModel();
}
int totalCount = 0;
//Get users
model.Users = CADC.GetUsers(AccountController.GetRegionID(), model.CompanyID, out totalCount,
model.SortField, model.PageNumber * PAGE_MAX_COUNT, PAGE_MAX_COUNT, model.Ascending, User.Identity.Name);
model.TotalUserCount = totalCount;
int totalPages = totalCount / PAGE_MAX_COUNT;
model.TotalPages = (totalCount % PAGE_MAX_COUNT) == 0 ? totalPages : totalPages + 1;
return View(model);
}
and the model:
public class UserModel
{
public bool Ascending { get; set; }
public int PageNumber { get; set; }
public string SortField { get; set; }
public int TotalUserCount { get; set; }
public int TotalPages { get; set; }
public long CompanyID { get; set; }
public List<User> Users { get; set; }
public UserModel()
{
this.Ascending = true;
this.PageNumber = 0;
this.SortField = "FirstName";
this.CompanyID = 0;
this.TotalUserCount = 0;
this.TotalPages = 0;
}
}
I agree with #David's comments. In the AJAX success block, remove the rows just deleted based on what you requested to delete.
The pagination is indeed more complicated; either adjust it in the same way, or revamp everything to use a proper view model in Javascript with bound elements--see AngularJS or other such frameworks.
I have a ViewModel and a DropDownList with some values on my page:
public class MyViewModel
{
public string SelectedItemDrop1 { get; set; }
public string SelectedItemDrop2 { get; set; }
public string SelectedItemDrop3 { get; set; }
public List<OptionViewModel> Options { get; set; }
}
public class OptionViewModel
{
public string Number { get; set; }
public string Option { get; set; }
}
And, into my View:
#using (Html.BeginForm("Save", "Controller", FormMethod.Post))
{
<ul id="cursos">
<li>
#Html.DropDownListFor(c => c.SelectedItemDrop1,
new SelectList(Model.Options, "Number", "Option", Model.SelectedItemDrop1))
Choose 1
</li>
<li>
#Html.DropDownListFor(c => c.SelectedItemDrop2,
new SelectList(Model.Options, "Number", "Option", Model.SelectedItemDrop2))
Choose 2
</li>
<li>
#Html.DropDownListFor(c => c.SelectedItemDrop3,
new SelectList(Model.Options, "Number", "Option", Model.SelectedItemDrop3))
Choose 3
</li>
</ul>
}
When I use Javascript to change options from these select elements, my return is null. What's the problem? Thank you so much!!!
EDIT:
My javascript code:
$("#cursos li select").each(function (i, item) {
$(this).change(function () {
updateCursos($(this), 7);
});
$(this).blur(function () {
if ($(this).val() != -1) {
$(this).attr('disabled', 'disabled');
}
});
});
function updateCursos(select, avaiableCourses) {
var selecteds = '';
$("#cursos li select").each(function (i, item) {
var selected = $(item).val();
if (selected != -1)
selecteds += selected + ',';
});
var arr = selecteds.split(',');
$("#cursos li select").each(function (i, item) {
if ($(item).val() != select.val()) {
var oldValue = $(item).val();
if ($(item).val() == -1) {
var options = "<option value='-1'></option>";
for (i = 1; i <= avaiableCourses; ++i) {
if (select.val() != i && !contains(i, selecteds)) {
options += "<option value='" + i + "'>" + i + "ª option</option>";
}
}
options += "<option value='0'>Don't want it</option>";
$(item).children("option").remove();
$(item).html(options);
$(item).val(oldValue);
}
}
});
}
This way I'm sure that works, not even being javascript.
If the values are not being filled are coming from the database, with fixed values example: active, inactive, do the following
Create an enumerated type with these values
After the controller transforms this type enumerated in list using an internal class, turn it into a enumerable and go to the dropdownlist
example of usage:
# Html.DropDownListFor (model => model.chamados.UrgenciaId, new SelectList (ViewBag.urgencia, "Id", "Name"))