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.
Related
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.
I'm using an example from Code Project: ASP.NET MVC-4,Entity Framework and JQGrid Demo with simple Todo List WebApplication. However, the developer didn't maximize cohesion since the the controller is doing all the business logic. I'm trying to decouple the JsonResult method, from the controller, and create loosely coupled classes that handle all the business logic from the model.
Here is my original code:
[HttpPost]
public JsonResult getPriceTable(string sidx, string sord, int page, int rows)
{
int pageIndex = Convert.ToInt32(page) - 1;
int pageSize = rows;
var modelPrices = db.VW_MODELS_CHARGEs
.OrderBy(x => x.SERIAL_CODE)
.Where(x => x.ACTIVE == 1)
.Select(x => new { x.VW_MC_OBJID, x.S_MODEL, x.CHARGE, x.SERIAL_CODE });
int totalRecords = modelPrices.Count();
var totalPages = (int)Math.Ceiling((float)totalRecords / (float)rows);
var jsonData = new
{
total = totalPages,
page,
records = totalRecords,
rows = modelPrices
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
to begin breaking up the logic, in the controller, I created several classes:
public class jqGridTable
{
public int pageIndex { get; set; }
public int pageSize { get; set; }
public int totalRecords { get; set; }
public int totalPages { get; set; }
public List<adminPriceTable> modelPricesList = new List<adminPriceTable>();
}
public class adminPriceTable
{
public int VW_MC_OBJID { get; set; }
public string S_MODEL { get; set; }
public string SERIAL_CODE { get; set; }
public decimal? CHARGE { get; set; }
}
and then the class below invokes the method to return the data:
public class adminPriceTableList
{
public static jqGridTable priceTableResult(string sidx, string sord, int page, int rows)
{
var jqGrid = new jqGridTable();
jqGrid.pageIndex = Convert.ToInt32(page) - 1;
jqGrid.pageSize = rows;
jqGrid.modelPricesList = NREContext.dbConn.VW_MODELS_CHARGEs
.OrderBy(x => x.SERIAL_CODE)
.Where(x => x.ACTIVE == 1)
.Select(x => new adminPriceTable
{
VW_MC_OBJID = x.VW_MC_OBJID,
SERIAL_CODE = x.SERIAL_CODE,
S_MODEL = x.S_MODEL,
CHARGE = x.CHARGE
}).ToList();
jqGrid.totalRecords = jqGrid.modelPricesList.Count();
jqGrid.totalPages = (int)Math.Ceiling((float)jqGrid.totalRecords / (float)rows);
return jqGrid;
}
}
so now, in the controller, I should be able to have the controller just invoke:
[HttpPost]
public JsonResult getPriceTable(string sidx, string sord, int page, int rows)
{
return Json(adminPriceTableList.priceTableResult(sidx, sord,page,rows), JsonRequestBehavior.AllowGet);
}
However, the jqGrid is not getting populated. I suspect it has something to do with (shown below) from the original code when the controller was handling all the logic.
var jsonData = new
{
total = totalPages,
page,
records = totalRecords,
rows = modelPrices
};
Can someone help me understand what I didn't do correctly? Do you see where I went wrong or where I'm not invoking the proper methods?
Thanks.
After reviewing the code, I failed to notice that modelPriceList:
public List<adminPriceTable> modelPricesList = new List<adminPriceTable>
needs to be the same name as modelPrices
var jsonData = new
{
total = totalPages,
page,
records = totalRecords,
rows = modelPrices
};
so after renaming the list....
public List<adminPriceTable> modelPrices = new List<adminPriceTable>
...produces results.
I am receiving the following error when trying to insert an object into a child collection after the model binder has created the model, children and grandchildren and then using context.SaveChanges();
Multiplicity constraint violated. The role 'OrderDetail_OrderDetailPricelistProductOptions_Source' of the relationship 'PPLib.Models.OrderDetail_OrderDetailPricelistProductOptions' has multiplicity 1 or 0..1.
My models are as follows (removed properties for brevity);
public class Order
{
public int OrderId { get; set; }
public virtual List<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
public int OrderDetailId { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; } //FK NAV
public int? PricelistProductId { get; set; } // if a subscriber order ...has the ProductId from a PriceList.
private decimal _Price = 0;
public decimal Price { get { return _Price; } set { _Price = value; } }
private int _Quantity = 1;
public int Quantity { get { return _Quantity; } set { _Quantity = value; } }
public virtual List<OrderDetailPricelistProductOption> OrderDetailPricelistProductOptions { get; set; }
}
public class OrderDetailPricelistProductOption
{
public int OrderDetailPricelistProductOptionId { get; set; }
public int OrderDetailId { get; set; }
public virtual List<OrderDetailPricelistProductOptionsDetail> OrderDetailPricelistProductOptionsDetails { get; set; }
}
public class OrderDetailPricelistProductOptionsDetail
{
public int OrderDetailPricelistProductOptionsDetailId { get; set; }
public int OrderDetailPricelistProductOptionId { get; set; }
public string Name { get; set; }
}
To be clearer:
If I submit a complete new Order, with a list of OrderDetails, its list of OrderDetailPricelistProductOptions and its list of OrderDetailPricelistProductOptionsDetails, the model binder does its job and I receive no error doing:
db.Orders.Add(order);
db.SaveChanges();
If I submit an Edit with and Existing Order and a NEW a list of OrderDetails, its list of OrderDetailPricelistProductOptions and its list of OrderDetailPricelistProductOptionsDetails, I get the Order from the DB context and then merge the OrderDetails from the view model, using:
order.OrderDetails.AddRange(pricelistProductVM.Order.OrderDetails);
and I receive no error doing:
db.Entry(order).State = EntityState.Modified;
db.SaveChanges();
I have a particular situation, where I have to instantiate a new OrderDetail called autoFillOd, and inject its values from one of the existing OrderDetails assembled by the Model Binder. I change its Quantity value and then add it to the collection of OrderDetails in the ViewModel, like so:
pricelistProductVM.Order.OrderDetails.Add(autoFillOd);
When I do db.SaveChanges(), I receive the error.
You'll notice that the error is on the child of the OrderDetails: OrderDetail_OrderDetailPricelistProductOptions_Source
Why can I not add an OrderDetail dynamically into the collection of OrderDetails? All the OrderDetails are new (to be inserted) so the values are the same between the copies, except for the Quantity property which should not be an issue.
The controller action is as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Add(pricelistProductVM pricelistProductVM)
{
OrderLogic ol = new OrderLogic();
//Auth is running on execute
int userId = WebSecurity.CurrentUserId;
int websiteId = (int)Session["websiteId"];
int? id = null; // mediaId
int productId = pricelistProductVM.Product.ProductId;
int? eventId = pricelistProductVM.eventId;
string err = "";
if (productId > 0)
{
//Set Pricelist
Pricelist pricelist = ol.setPricelist(websiteId, id, eventId);
if (pricelist.PricelistId != 0)
{
//get the pricelistproduct from the pricelist
PricelistProduct pp = await (from ppx in db.PricelistProducts
where ppx.ProductId == productId
&& ppx.PricelistId == pricelist.PricelistId
&& ppx.isAvailable == true
&& ppx.DiscontinuedDate == null
&& ppx.Product.isAvailable == true
&& ppx.Product.DiscontinuedDate == null
select ppx).SingleOrDefaultAsync();
if (pp != null)
{
Order order = new Order();
//set some default values for the Order entity
if (pricelistProductVM.Order.OrderId == 0)
{
pricelistProductVM.Order.WebsiteId = websiteId;
pricelistProductVM.Order.UserId = userId;
pricelistProductVM.Order.EventId = eventId;
pricelistProductVM.Order.StartedDate = DateTime.UtcNow;
order = pricelistProductVM.Order;
}
else
{
order = await db.Orders.FindAsync(pricelistProductVM.Order.OrderId);
}
//set some default values for the OrderDetails entity
pricelistProductVM.Order.OrderDetails.First().InjectFrom(pp);
pricelistProductVM.Order.OrderDetails.First().IsPackage = false;
//determine if this product should be automatically added to any packages in the order
OrderDetail autoFillOd = ol.packageCheck(ref pp, ref pricelistProductVM, ref order, websiteId, db);
if (autoFillOd != null)
{
if (autoFillOd.Quantity > 0)
{
//This is where the OrderDetail that causes a problem is added
pricelistProductVM.Order.OrderDetails.Add(autoFillOd);
}
}
if (pricelistProductVM.Order.OrderId == 0)
{
db.Orders.Add(order);
}
else
{
order.OrderDetails.AddRange(pricelistProductVM.Order.OrderDetails);
db.Entry(order).State = EntityState.Modified;
}
db.SaveChanges();
}
else
{
//return error
err = "The product was not found in the available pricelist. Please reload your browser and make sure you are signed-in.";
}
}
}
else
{
//return error
err = "A productId was not passed so no product could not be found. Please reload your browser and make sure you are signed-in.";
}
if (err == "")
{
ViewBag.data = JsonConvert.SerializeObject(new { Success = 1, Msg = "The product was successfully added to your cart." });
}
else
{
ViewBag.data = JsonConvert.SerializeObject(new { Success = 0, Msg = err });
}
return View();
}
I appreciate the help!
I think OrderDetailPricelistProductOption.OrderDetailId can't be single -> it should be a list because it can appear in many OrderDetails...
IList<ReceiptAllocationMaster> objReceiptMaster = (IList<ReceiptAllocationMaster>)Session["AllocationResult"];
public class ReceiptAllocationMaster
{
public string application { get; set; }
public List<Users> users { get; set; }
}
public class Users
{
public string name { get; set; }
public string surname { get; set; }
}
I need to Update the above list with some value where application = "applicationame" and users where surname = "surname" into the same list.
Just iterate over your list and modify matched items:
for (int i = 0; i < objReceiptMaster.Count; i++)
{
var item = objReceiptMaster[i];
if (item.application == "applicationname" && item.users.Any(x => x.surname == "surname"))
objReceiptMaster[i] = new ReceiptAllocationMaster();
}
Instead of new ReceiptAllocationMaster() you can write any modification data logic.
though your question is not clear, this shd give u some idea:
objReceiptMaster.Where(x=>x.application=="applicationname" &&
x.users.Any(d=>d.surname=="surname"))
.ToList()
.ForEach(item=>{//update your list
item.application = "whatever value";
item.users.ForEach(user=>{//update users
user.name="whatever username";
});
});
I have two classes, Songs and Tags.The relationship is many-to-many. The problem I'm having is that when a user adds a new Song and checks off what Tags s/he wants and I call db.SaveChanges() in the controller, it adds duplicate Tags instead of adding only records to the join table. I've worked around this, but it's a hack. There's got to be a better solution?
Here's my code (abrreviated for clarity).
Song:
public int Id { get; set; }
public List<Tag> Tags { get; set; }
public string SongTitle { get; set; }
Tag:
public int Id { get; set; }
public string TagName { get; set; }
public bool Selected { get; set; }
public List<Song> Songs { get; set; }
Controller:
public ActionResult Create()
{
Song song = new Song();
song.Tags = utilities.TagNames(_db, User.Identity.Name);
return View(song);
}
[HttpPost]
public ActionResult Create(Song song)
{
//cycle thru the tags and grab ids of checked tags
int tagCount = song.Tags.Count;
List<int> tagIds = new List<int>();
for (int i = tagCount - 1; i > -1; i--)
{
Tag tag = song.Tags[i];
if (tag.Selected)
tagIds.Add(tag.Id);
}
song.Tags = null;
if (ModelState.IsValid)
{
_db.Songs.Add(song);
_db.SaveChanges();
int songId = song.Id;
string sql = "INSERT INTO [dbo].[TagSongs] (Tag_Id, Song_Id) VALUES ";
foreach(int tagid in tagIds)
{
sql += "(" + tagid + "," + songId + "),";
}
//remove the last comma
sql = sql.Substring(0, sql.Length - 1);
_db.Database.ExecuteSqlCommand(sql);
return RedirectToAction("Index");
}
SetCategoriesAndTags(song);
return View(song);
}
View:
#for (int i = 0; i < Model.Tags.Count; i++)
{
Tag t = Model.Tags[i];
#Html.CheckBoxFor(m => m.Tags[i].Selected)
<label for="Tags_#i.ToString()__Selected">#t.TagName</label>
#Html.HiddenFor(m => m.Tags[i].Id)
#Html.HiddenFor(m => m.Tags[i].TagName)
}
First off, do not use string concatenation for SQL queries or this will open you up to SQL Injection attack. This code should get you what you need. Call it from your controller ActionMethod.
private void AddSongs(Song song, List<int> tagIds)
{
_db.Songs.Add(song);
foreach(var tagId in tagIds)
{
var tag = _db.Tags.SingleOrDefault(tagId);
if(tag != null) song.Tags.Add(tag);
}
_db.SaveChanges();
}