Using ASP.NET MVC, .NET Framework 4.5.2, Entity Data Model for SQL DB, Visual Studio 2017.
I have a class generated from the ADO.NET(EF Designer from Database) :
BookInfo.cs
namespace LibraryMS
{
using System;
using System.Collections.Generic;
public partial class BookInfo
{
public string BookID { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Publisher { get; set; }
public string PublishDate { get; set; }
public string Edition { get; set; }
public virtual Inventory Inventory { get; set; }
}
}
The database is designed where the "BookID" in the BookInfo table has a foreign key "BookID" in the Inventory table.
In a view to update an inventory's properties referenced by "BookID", I then proceed to query the list and update the correct instance.
Screenshot of update inventory page:
When landing on page to enter info the [HttpGet] UpdateInventory() is called, when clicking "Create" button as seen above, the [HttpPost] UpdateInventory(...) is called.
Logic/Code in Controller:
[HttpGet]
public ActionResult UpdateInventory()
{
return View();
}
[HttpPost]
public async Task<ActionResult> UpdateInventory(string bookID, string ttlIn, string lowin, string outnow)
{
var bf = await SqlRestApiHelper.searchFromBooks(bookID);
bf.Inventory.TotalIn = Convert.ToInt16(ttlIn);
bf.Inventory.LowIn = Convert.ToInt16(lowin);
bf.Inventory.Out = Convert.ToInt16(outnow);
await SqlRestApiHelper.UpdateBookInfoInventory(bf.Inventory);
await SqlRestApiHelper.SaveChanges();
return View("All");
}
[HttpGet]
public async Task<ActionResult> All()
{
return View(await SqlRestApiHelper.getAllBooksInfo(0, 10));
}
SqlRestApiHelper.cs
namespace LibraryMS
{
public static class SqlRestApiHelper
{
private static libraryDBEntities entities = new libraryDBEntities();
public static async Task<LibraryMS.BookInfo> searchFromBooks(string id)
{
return entities.BookInfoes.ToList().Find(book => book.BookID == id);
}
public static async Task UpdateBookInfoInventory(LibraryMS.Inventory inv)
{
var newInv = inv;
var el = entities.BookInfoes.ToList().Find(x => x.Inventory.BookID == newInv.BookID);
if (el != null)
{
el.Inventory.TotalIn = newInv.TotalIn;
el.Inventory.LowIn = newInv.LowIn;
el.Inventory.Out = newInv.Out;
// the above updates the list item referenced
}
}
public static async Task SaveChanges()
{
await entities.SaveChangesAsync();
}
public static async Task<IPagedList<BookInfo>> getAllBooksInfo(int page, int itemsPerPage)
{
List<BookInfo> bookinfo = new List<BookInfo>();
bookinfo = (from o in entities.BookInfoes
orderby o.Title descending //use orderby, otherwise Skip will throw an error
select o)
.Skip(itemsPerPage * page).Take(itemsPerPage)
.ToList();
int totalCount = bookinfo.Count();//return the number of pages
IPagedList<BookInfo> pagebooks = new StaticPagedList<BookInfo>(bookinfo, page + 1,10,totalCount);
return pagebooks;//the query is now already executed, it is a subset of all the orders.
}
The Null Exception Thrown:
Code for all.cshtml view page:
#model PagedList.IPagedList<LibraryMS.BookInfo>
#using PagedList.Mvc;
#{
ViewBag.Title = "All";
}
<h2>all</h2>
<table class="table">
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.DisplayFor(modelItem => item.Author)
</td>
<td>
#Html.DisplayFor(modelItem => item.Publisher)
</td>
<td>
#Html.DisplayFor(modelItem => item.PublishDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.Edition)
</td>
<td>
#Html.ActionLink("Details","Details",new { item.BookID})
</td>
</tr>
}
</table>
#Html.PagedListPager(Model, page => Url.Action("All","BookInfoController", new { page }))
your view is throwing error because you are returning the view without passing the model, you are using return View("All") without passing the model
the right way is by passing the model with the view, you can do it this way
View("ViewName", ModelData);
in your case
return View("All", await SqlRestApiHelper.getAllBooksInfo(0, 10));
for the saving part I am not sure why, but i can see few errors,
first what if book does not have inventory info ?
it will throw null error, so first check if null, create new inventory, if not update accordingly
here is how i would do it
public static async Task UpdateBookInfoInventory(Inventory inv)
{
var newInv = inv;
// get book info
var el = entities.BookInfoes.FirstOrDefault(x => x.BookID == inv.BookID);
if (el != null)
{
if(el.Inventory != null)
{
// update accordingly
el.Inventory.TotalIn = newInv.TotalIn;
el.Inventory.LowIn = newInv.LowIn;
el.Inventory.Out = newInv.Out;
// the above updates the list item referenced
}
else
{
/// add new if null
el.inventory = newInv;
}
await SqlRestApiHelper.SaveChanges();
}
As a first step , put the breakpoint in the getAllBooksInfo method and see whether the list count is coming or not in visual studio. This will help you a lot.
And As an alternative step, you can also solve this error by using the ToPagedList(pageIndex, pageSize); method, i have used it personally and it was worked well
ToPagedList example as per your code:
**public static async Task<IPagedList<BookInfo>> getAllBooksInfo(int page, int itemsPerPage)
{
List<BookInfo> bookinfo = new List<BookInfo>();
bookinfo = (from o in entities.BookInfoes
orderby o.Title descending //use orderby, otherwise Skip will throw an error
select o)
.Skip(itemsPerPage * page).Take(itemsPerPage)
.ToList();
int totalCount = bookinfo.Count();//return the number of pages
//changes made to the below line
IPagedList<BookInfo> pagebooks = bookinfo.ToPagedList(1, 10);
return pagebooks;//the query is now already executed, it is a subset of all the orders.
}**
official source:https://github.com/troygoode/PagedList
Note: Even with this approach, kindly check whether you are getting the data or not from the database by using the breakpoint in the changed line.
Hope this will surely solve your problem kindly let me know thoughts or feedbacks
Thanks
karthik
Related
I have retrieved data from database using a LINQ query.
var data = (from z in db.BlogPostTbl
where z.PostId == id
select z).ToList();
in this result, I have an attribute name user_rankings. The value for this field is [1,2,3] in database but I have to display as good if 3, better if 2 or best if 1. How can I do this?
<td><b>User Ranking:</b> #Html.DisplayFor(modelItem => item.user_rankings) </td>
Example of using the Enum type.
Defining the UserRankingEnum type and the GetEnumDescription() method to obtain a description by the enum value:
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace WebApp.Models
{
public class BlogPost
{
public int PostId { get; set; }
public int user_rankings { get; set; }
}
public enum UserRankingEnum
{
[Description("BEST")]
Best = 1,
[Description("BETTER")]
Better = 2,
[Description("GOOD")]
Good = 3
}
public static class EnumExtension
{
public static string GetEnumDescription(this UserRankingEnum value)
{
if (value.GetType().GetField(value.ToString()) is FieldInfo fi)
{
if (fi.GetCustomAttributes(typeof(DescriptionAttribute), false) is DescriptionAttribute[] attributes && attributes.Any())
{
return attributes.First().Description;
}
}
return value.ToString();
}
}
}
The view accordingly:
#using WebApp.Models
#model IList<WebApp.Models.BlogPost>
#foreach (var item in Model)
{
<div>
<b>User Ranking: </b>
#{ var description = ((UserRankingEnum)item.user_rankings).GetEnumDescription(); #Html.DisplayFor(m => description) }
</div>
}
One possibility is to use a separate view model class / poco and map the db entity to this poco:
var data = (from z in db.BlogPostTbl
where z.PostId == id
select new BlogPostViewModel()
{
Ranking = GetRankingDescription(z.user_rankings)
}).ToList();
public class BlogPostViewModel
{
public string Ranking { get; set; }
}
private static string GetRankingDescription(int ranking)
{
switch (ranking)
{
case 1:
return "best";
case 2:
return"better";
case 3
return"good";
default
return "";
}
}
I think you want something like this
// inside the razor page
#foreach (var item in Model)
{
string rank_ = "";
switch(item.user_rankings)
{
case "1":
ward_ = "BEST";
break;
case "2":
ward_ = "BETTER";
break;
case "3":
ward_ = "GOOD";
break;
}
<td><b>User Ranking:</b> #rank_ </td>
}
I could have sworn that you can use the
public enum UserRankingEnum
{
[Description("BEST")]
Best = 1,
[Description("BETTER")]
Better = 2,
[Description("GOOD")]
Good = 3
}
and then
#Html.DisplayFor(modelItem => item.user_rankings)
Will display the description. In fact checked some code and it should do.
Although it depends on what you need and things like multi-lingual reasons might mean you lean towards a table or some other way to support it.
How can I remove a record from List in MVC 4 ASP.NET by click on Delete button Here I am not using any database I want to delete a record from list which I have define in controller. without any database remove a record from list using delete action
StudentController
public class StudentController : Controller
{
//
// GET: /Student/
public ActionResult Index()
{
List<StudentVM> students = new List<StudentVM>();
StudentVM obj1 = new StudentVM();
obj1.Name = "Zeeshan";
obj1.id = "1";
obj1.Address = "Lahore";
students.Add(obj1);
StudentVM obj2 = new StudentVM();
obj2.Name = "Zeshan";
obj2.id = "2";
obj2.Address = "Lahore";
students.Add(obj2);
return View(students);
}
public ActionResult Delete(string? i)
{
List<StudentVM> students = new List<StudentVM>();
var st = students.Find(c => c.id = i);
students.Remove(st);
return View("Index");
}
}
View
#model List<Activity2.Models.StudentVM>
#{
ViewBag.Title = "Index";
}
<table border="1">
<tr>
<th>Id</th>
<th>Name</th>
<th>Address</th>
</tr>
#foreach (var obj in Model)
{
<tr>
<td>#obj.id</td>
<td>#obj.Name</td>
<td>#obj.Address</td>
<td>#Html.ActionLink("Delete","Delete",new{i = obj.id}) </td>
</tr>
</table>
}
Error
Error 1 The type 'string' must be a non-nullable value type in order
to use it as parameter 'T' in the generic type or method
'System.Nullable'
Error 3 Cannot implicitly convert type 'string?' to 'string'
You are trying to use nullable reference-types which is a feature of C# 8.0 (which hasn't been released yet). In order to fix your errors you'd have to change your string? i to string i.
To remove items from the list you'd have to create it outside of your Index() and make it static, so other Endpoints like Delete() are able to access the list. That way each time somebody accesses the Index of your Controller two new students are added to the List (probably not the behavior you'd want in the long run, i just copied your code on this one):
public class StudentController : Controller
{
private static readonly List<StudentVM> _students = new List<StudentVM>();
public ActionResult Index()
{
StudentVM obj1 = new StudentVM();
obj1.Name = "Zeeshan";
obj1.id = "1";
obj1.Address = "Lahore";
_students.Add(obj1);
StudentVM obj2 = new StudentVM();
obj2.Name = "Zeshan";
obj2.id = "2";
obj2.Address = "Lahore";
_students.Add(obj2);
return View(students);
}
public ActionResult Delete(string i)
{
var student = _students.FirstOrDefault(c => c.id == i);
if(student == null){ /* Failed to find student */ }
_students.Remove(student);
return View("Index");
}
}
Also there seems to be an Error in your View code. The </table> should be outside of the foreach.
Firstly, as the error message is clearly saying you need to use string instead of string?:
public ActionResult Delete(string i)
And secondly you should be aware that, MVC is stateless because HTTP is. So this code won't delete the record as per your expectation. In order to make this code works, you need to use Session. Something like this:
if(Session["StudentVM"] == null)
{
//Your Code
Session["StudentVM"] = students;
}
And:
public ActionResult Delete(string i)
{
List<StudentVM> students = Session["StudentVM"] as List<StudentVM>;
var st = students.Find(c=>c.id=i);
//The rest of your code...
}
Am having issues loading navigation(child ) element from a controller to a view
I created this model
public class MultipleItems
{
public IEnumerable<Order> Orders { get; set; }
public IEnumerable<support> Supports { get; set; }
public IEnumerable<DbModel.Track> Tracks { get; set; }
public IEnumerable<Receipt> Receipts { get; set; }
public IEnumerable<Quota> Quotas { get; set; }
}
And the controller
public ActionResult Client()
{
string useremail = User.Identity.GetUserName();
var myModel = new MultipleItems();
myModel.Supports = new SupportW().GetSupports(useremail);
myModel.Orders = new Orders().GetOrders(useremail);
myModel.Receipts = new Receipts().GetReceipts(useremail);
myModel.Tracks = new Track().GetTracks(useremail);
myModel.Quotas = new Quotas().GetQuota(useremail);
return View(myModel);
}
Am interested in Tracks() and this is the method
public IEnumerable<DbModel.Track> GetTracks(string email)
{
try
{
using (var da = new CourierEntities())
{
da.Tracks.Include(a => a.Order);
da.Tracks.Include(a => a.Order.User);
da.Tracks.Include(a => a.destination);
da.Tracks.Include(a => a.Source);
da.Tracks.Include(a => a.CurrentLocation);
var q = (from p in da.Tracks where p.Order.User.Email == email select p).ToList();
if (q != null)
{
return q;
}
}
return null;
}
catch (Exception ex)
{
returnMsg = ex.Message;
return null;
}
}
While the view is
<td>
#if (Model.Orders.Count() != 0)
{
foreach (var i in Model.Tracks)
{
<tr>
Order id #i.OrderID
<br />
Trackid #i.TrackID
<br />
#i.Order.Packagename
</tr>
}
}
</td>
am having issues with #i.Order.Packagename and i recieve this error
The ObjectContext instance has been disposed and can no longer be used for
operations that require a connection.
Description: An unhandled exception occurred during the execution of the
current web request. Please review the stack trace for more
information about the error and where it originated in the code.
Exception Details: System.ObjectDisposedException: The ObjectContext
instance has been disposed and can no longer be used for
operations that require a connection.
Source Error:
Line 120:
Line 121: <br />
Line 122: #i.Order.Packagename
Line 123: </tr>
Although the comments suggest the use of a View Model, which would be a good idea, the fundamental issue with your code is the lifetime of the database context(s).
When you execute code such as:
myModel.Supports = new SupportW().GetSupports(useremail);
The object SupportW has a lifetime of that statement. It will likely be disposed before your view executes. If the method GetSupports(useremail) returns IQueryable then when that is resolved in the view, the source is no longer there.
To resolve this you should change the lines to:
myModel.Supports = new SupportW().GetSupports(useremail).ToList();
The addition of the ToList() resolves the `IQueryable' immediately and the data will be available in your view.
I am batting 1000 here with my questions. So i will try to be as descriptive as possible.
I have multiple views in a layout that come from different models.
When a record is selected from a list it opens this layout. At the top of the layout it displays the record information in a table format. This is a simple ID - /AuditSchedule/1122. This is currently the Body. This works.
In another area of the layout i have a list of action links (side menu) that are generated from a another table. The links, I think should be as follows but not sure /AuditSchedule/1122/1. This was accomplished by using the Global.asax with routes.
Naturally when you open this layout you should get all the above plus the first record of the next area of the layout which is the form. In this form i need it to show a question from a table of questions and have a set of check boxes created with, what i will call scores, to the right of the question. These scores are also in a table called scores. Everything that i have in this is pretty much in a data table so that everything can be edited and changed if needed.
When a user submits the form it will store in another table called MainAnswers the id's of the auditSchedule, mainQuestion, and a string of the score. This table is a blank table so it would insert a new record for every Main Question for that AuditSchedule.
So far i have had no luck with help on this. If anyone could point me to an example of this that they have seen. It would be great. I cannot be the only one that has tried to do this. However i am new to MVC C#. If this were Zend and PHP i would have no issues.
I have used the code first approach. All of my relationships are done. The issue lies in implementing the view and saving the information in the right tables.
Anyone that can help would be much appreciated. I am struggling here.
Updated 08/16/2012 3:12pm
Let me take baby steps first.
I want to be able to select a menu Item from the side and have a list of questions come up from that section. Here is my Code:
#{ Layout = null; }
#model QQAForm.ViewModels.AuditFormEdit
<table width="698" border="2" cellpadding="2">
<tr>
<td align="center"><b>Section</b><br />1.0</td>
<td>
<br />(Title of Section Goes Here - SubcategoryName - Located in Subcategory Model)<br />
<br />
(Child Questions Go here - QuestionText - Located in ChildQuestion Model)
</td>
<td>
(This should be the result of what is written in AuditFormEdit view model - it does not currently work - Nothing shows up)
#for (int index = 0; index < Model.ScoreCardCheckBoxHelperList.Count; index++)
{
#Html.CheckBoxFor(m => m.ScoreCardCheckBoxHelperList[index].Checked)
#Html.LabelFor(m => m.ScoreCardCheckBoxHelperList[index], Model.ScoreCardCheckBoxHelperList[index].ScoreName)
#Html.HiddenFor(m => m.ScoreCardCheckBoxHelperList[index].ScoreID)
#Html.HiddenFor(m => m.ScoreCardCheckBoxHelperList[index].ScoreName)
}
</td>
</tr>
</table>
Here is the View model i am working on:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using QQAForm.Models;
namespace QQAForm.ViewModels
{
public class AuditFormEdit
{
public List<SubcategoryHelper> SubcategoryHelperGet { get; set; }
public class SubcategoryHelper : Models.SubCategory
{
public SubcategoryHelper(Models.SubCategory subCat)
{
this.SubCategoryID = subCat.SubCategoryID;
this.SubcategoryName = subCat.SubcategoryName;
}
}
public Models.MainAnswer ScoreInstance { get; set; }
public List<ScoreCardCheckBoxHelper> ScoreCardCheckBoxHelperList { get; set; }
public void InitializeScoreCheckBoxHelperList(List<Models.Score> ScoreList)
{
if (this.ScoreCardCheckBoxHelperList == null)
this.ScoreCardCheckBoxHelperList = new List<ScoreCardCheckBoxHelper>();
if (ScoreList != null
&& this.ScoreInstance != null)
{
this.ScoreCardCheckBoxHelperList.Clear();
ScoreCardCheckBoxHelper scoreCardCheckBoxHelper;
string scoreTypes =
string.IsNullOrEmpty(this.ScoreInstance.Score) ?
string.Empty : this.ScoreInstance.Score;
foreach (Models.Score scoreType in ScoreList)
{
scoreCardCheckBoxHelper = new ScoreCardCheckBoxHelper(scoreType);
if (scoreTypes.Contains(scoreType.ScoreName))
scoreCardCheckBoxHelper.Checked = true;
this.ScoreCardCheckBoxHelperList.Add(scoreCardCheckBoxHelper);
}
}
}
public void PopulateCheckBoxsToScores()
{
this.ScoreInstance.Score = string.Empty;
var scoreType = this.ScoreCardCheckBoxHelperList.Where(x => x.Checked)
.Select<ScoreCardCheckBoxHelper, string>(x => x.ScoreName)
.AsEnumerable();
this.ScoreInstance.Score = string.Join(", ", scoreType);
}
public class ScoreCardCheckBoxHelper : Models.Score
{
public bool Checked { get; set; }
public ScoreCardCheckBoxHelper() : base() { }
public ScoreCardCheckBoxHelper(Models.Score score)
{
this.ScoreID = score.ScoreID;
this.ScoreName = score.ScoreName;
}
}
}
}
Here is the controller parts:
//get
public ActionResult _Forms(int section)
{
AuditFormEdit viewModel = new AuditFormEdit();
//viewModel.ScoreInstance = _db.MainAnswers.Single(r => r.MainAnswerID == id);
_db.SubCategories.Single(r => r.SubCategoryID == section);
viewModel.InitializeScoreCheckBoxHelperList(_db.Scores.ToList());
return View(viewModel);
}
//post
[HttpPost]
public ActionResult _Forms(AuditFormEdit viewModel)
{
if (ModelState.IsValid)
{
viewModel.PopulateCheckBoxsToScores();
_db.Entry(viewModel.ScoreInstance).State = System.Data.EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("/");
}
else
{
return View(viewModel);
}
}
So if you look at the Layout, where it show the spot for _Forms the Section should change with the link /AuditSchedule/1132/1 - it does not. As well as my check boxes which currently do not show up either.
...my check boxes which currently do not show up either.
That's because the line which sets the viewModel.ScoreInstance in your controller's GET action is commented out:
//viewModel.ScoreInstance = _db.MainAnswers.Single(r => r.MainAnswerID == id);
Hence viewModel.ScoreInstance is null and in InitializeScoreCheckBoxHelperList you fill the ScoreCardCheckBoxHelperList only when viewModel.ScoreInstance is not null:
if (this.ScoreCardCheckBoxHelperList == null)
this.ScoreCardCheckBoxHelperList = new List<ScoreCardCheckBoxHelper>();
if (ScoreList != null
&& this.ScoreInstance != null)
{
//... add elements to ScoreCardCheckBoxHelperList
}
Empty ScoreCardCheckBoxHelperList = no checkboxes.
I have the following action method, when I press the update button on my cart and post to this method I need it bind all productId and partquantity values into the respective parameters/arrays (int[] ProductId, int[] partquantity) and it does this. I am presuming when form data, that is keys and values are posted they arrive in some sort of order, likely as elements are laid out on the HTML page (top to bottom)? So I wish for the operation on each cart item to be performed using the correct partquantity entered, that is for the correct productId. I am guessing if they post and bind in strict order then partquantity[2] should be the correct quantity for ProductId[2] etc. ?
The below logic in trying to increment f by 1 for each operation on each productId in the ProductId[] array does not work. I need to get this to work because say I have 5 items added to the cart and change the quantity for 4 of them I wish to just press the one update button and it will update for all these items\lines in the cart. So method needs to catch all the posted productId and quantities and use in the correct order, so the right quantity is assigned to the right cart item which is looked up by ProductId.
public RedirectToRouteResult UpdateCart(Cart cart, int[] ProductId, int[] partquantity, string returnUrl)
{
int f = 0;
int x = partquantity.Length;
while (f <= x)
{
foreach (var pid in ProductId)
{
f++;
var cartItem = cart.Lines.FirstOrDefault(c => c.Product.ProductID == pid);
cartItem.Quantity = partquantity[f];
}
}
return RedirectToAction("Index", new { returnUrl });
}
This is the View:
<% foreach (var line in Model.Cart.Lines)
{ %>
<tr>
<td align="center"><%: Html.TextBox("partquantity", line.Quantity)%></td>
<td align="left"><%: line.Product.Name%></td>
<td align="right"><%: line.Product.ListPrice.ToString("c")%></td>
<td align="right">
<%: (line.Quantity * line.Product.ListPrice).ToString("c")%>
</td>
</tr>
<%: Html.Hidden("ProductId", line.Product.ProductID)%>
<% } %>
Custom Binder
public class CartModelBinder : IModelBinder
{
private const string cartSessionKey = "_cart";
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.Model != null)
throw new InvalidOperationException("Cannot update instances");
Cart cart = (Cart)controllerContext.HttpContext.Session[cartSessionKey];
if (cart == null)
{
cart = new Cart();
controllerContext.HttpContext.Session[cartSessionKey] = cart;
}
return cart;
}
}
}
Your Cart object should be enough as parameter for this scenario, you can have something like this.
The general idea would be to use an index so you get your Cart object with your Lines as you passed them to the view originally but with updated Quantity values.
Your model as I understand it:
public class Cart
{
...
public List<CartItem> Lines {get; set; }
}
public class CartItem
{
public Product Product {get; set;}
public int Quantity {get; set;}
...
}
In your view:
#model Cart
...
#using(Html.BeginForm())
{
#{ int index = 0; }
#foreach(var l in Model.Lines)
{
#Html.Hidden("cart.Lines.Index", index);
#Html.Hidden("cart.Lines[" + index + "].Product.ProductID", l.Product.ProductID)
#Html.TextBox("cart.Lines[" + index + "].Quantity")
#{ index++; }
}
<input type="submit" value="Update Quantity" />
}
Your controller:
public ActionResult UpdateCart(Cart cart)
{
// you should have new values on Quantity properties of the cart.Lines items.
}
Wouldn't it be easier to have an Object - say BasketItem that has the productId and the quantity as Properties? so you would have one array/list/ienumerable to pass on to update and to bind.
Your problem would be obsolete since the connection between quantity and productid is done via the object.