this is an issue that has been happening for a couple of weeks now, was wondering if anyone has encountered this , and if there is a fix :
Say I have a View Model called 'ListFruitsViewModel' , which for the sake of argument contains a base model of 'Fruit' that has the properties
public Guid fruitID {get; set;}
public String fruitName {get; set;}
ok so I go ahead and create the ListFruits view , perform some action and all works ok.
but then I realise I need to change the 'Fruit' model to include a 'fruitColor' property. So I change the properties of 'Fruit' to look like :
public Guid fruitID {get; set;}
public String fruitName {get; set;}
public String fruitColor {get; set;}
now I can see the fruitColor property in the Razor intellisense etc, I ve changed the method that populates the fruits in the list to include the new property etc, and no matter what I try, the fruitColor property is always null in the View Model.
Its almost like although VS knows there is a new property, the underlying assembly files have not picked up that there is a new property in the 'Fruit' model, and is not populating it.
Various combinations of cleans / builds / rebuilds have not fixed the issue, and a VS restart holds no joy.
I am certain this is a quirk of VS and the way it updates complied code.
Any ideas?
EDIT: one real-world example then :
I have a class of type report with Properties:
public Guid reportID { get; set; }
public String reportName { get; set; }
public DateTime reportCreated { get; set; }
public String reportCreatedBy { get; set; }
and I have just added:
public String reportCode { get; set; }
Im bringing in a list of these back from a database so Ive changed the method that populates the list to :
public List<Report> ListReports() {
List<Report> list = new List<Report>();
DAL.DAL dal = new DAL.DAL();
DataTable dt = new DataTable();
Report item;
dt = dal.ListAllReports();
foreach (DataRow row in dt.Rows) {
item = new Report() {
reportID = new Guid(row["ReportID"].ToString()),
reportName = row["ReportName"].ToString(),
reportCreated = Convert.ToDateTime(row["ReportCreatedDateTime"].ToString()),
reportCreatedBy = row["ReportCreatedBy"].ToString(),
reportCode = row["ReportCode"].ToString()
};
list.Add(item);
}
return list;
}
I can step through the ListReports() method , and can see the values are being passed in to the object at this point, but the property is always null in the View Model, while the other properties are populated as expected.
Related
I'm writing code using eager loading to load the related data from Assigned using the Include method. I'm trying to get the value for IsAvailable, as to access the data from Assigned and check if there is a record which have the value of ReturnDate is equal to null. I keep on getting an error saying
ICollection does not contain a definition for Any
public ActionResult Index()
{
var item = db.Item.Include(h => h.Assigned).Select(b => new ItemViewModel
{
ItemID = b.ItemID,
ItemName = b.ItemName,
Category = b.Category,
Description = b.Description,
Model = b.Model,
IsAvailable = !b.Assigned.Any(h => h.ReturnDate == null)
}).ToList();
return View(item);
}
ItemViewModel
public class ItemViewModel
{
public int ItemID { get; set;}
[Display(Name = "Item")]
public string ItemName { get; set; }
[Required(ErrorMessage = "Category is required")]
public string Category { get; set; }
public string Model { get; set; }
public string Description { get; set; }
public bool IsAvailable { get; set; }
}
Item class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Collections;
namespace Inventoryproto3.Models
{
public class Item
{
public int ItemID { get; set; }
[Required(ErrorMessage ="Please enter a title"),Display(Name ="Item")]
public string ItemName { get; set;}
[Required(ErrorMessage ="Category is required")]
public string Category { get; set;}
public string Model { get; set;}
public string Description{get; set;}
public ICollection Assigned { get; set; }
}
}
'System.Collections.Generic.ICollection' does not contain a definition for 'Any' and no accessible extension method 'Any' accepting a first argument of type 'System.Collections.Generic.ICollection' could be found (are you missing a using directive or an assembly reference?
Screenshot of error
Every Item has a property Assigned, which is of type ICollection.
If you look at the definition of Queryable.Any you'll find:
public static bool Any<TSource> (this IQueryable<TSource> source,
Expression<Func<TSource,bool>> predicate);
Therefore property Assigned should implement ICollection<TSource> where TSource is the actual type you expect to find in your collection.
The proper solution would be to change the type of property Assigned, such that you know that the collection only contains items of the type you expect. Alas you chose to give us a lot of unimportant information, but you forgot to mention the class of items in Assigned, so let's assume it's of type MyClass.
class MyClass
{
public Datetime ReturnDate {get; set;}
...
}
class Item
{
...
public ICollection<MyClass> Assigned {get; set;}
}
This will solve your problem: Any(...) works, and you are certain that people can only put MyClass objects in the collection.
If you don't want to limit your collection to MyClass objects, you can at least limit yourself to classes with property ReturnDate:
interface IMyInterface
{
public Datetime ReturnDate {get; set;}
}
class Item
{
...
public ICollection<IMyInterface> Assigned {get; set;}
}
If you really want that people can add other things than MyClass objects in the collection, something I strongly advise against, you'll have to specify what you want:
Ignore items in the collection that are not MyClass objects? Use OfType
Just hope that no one ever put something else in the collection? Use Cast, but then again: why not prevent that someone puts an incorrect object in the class, and define ICollection<MyClass>?
OfType probably does not works AsQueryable, in that case you should move the data to your local process using AsEnumerable. A third reason to use a correct type for ICollection right.
IsAvailable = !b.Assigned.OfType<MyClass>().Any(h => h.ReturnDate == null);
IsAvailable = !b.Assigned.Cast<MyClass>().Any(h => h.ReturnDate == null);
The first one will ignore items of your collection with other types. The latter one will throw an exception if there is another type in your collection (fourth reason to make it an ICollection<MyClass>
I have a model that looks as follows:
public partial class RoutePartner
{
public Guid Id { get; set; }
[Required]
public Guid PartnerId { get; set; }
[Required]
public string RouteCompany { get; set; }
}
The partnerId comes from another TABLE that has an Id and Name. I'm trying to build my CRUD forms to load the list of partners and make them available to the form. That is the best way to do this in MVC Core? I see many .NET examples but none for CORE.
One last point is that partner drop down should be populated by a stored procedure.
Thanks
Dan
Your class doesn't have to be the same as your database. Why not have a class called Partner that has the ParnterID and PartnerName? In your RoutePartner class have a Partner property, instead of just the ID.
Public class SomeViewModel //Used by your view
{
public RouteParnter {get; set;}
public ParnterList List<SelectListItem> {get; set;}
public static List<SelectListItem> PopulateDropdown()
{
List<SelectListItem> ls = new List<SelectListItem>();
DataTable someTable = Database.LoadParnters(); // Database is your data layer
foreach (var row in someTable.Rows)
{
ls.Add(new SelectListItem() { Text = row["PartnerName"].ToString();, Value = row["PartnerID"].ToString(); });
}
return ls;
}
}
public partial class RoutePartner
{
public Guid Id { get; set; }
[Required]
public Partner SomePartner { get; set; }
[Required]
public string RouteCompany { get; set; }
public RouteParnter(Guid id)
{
DataRow returnedRow = Database.LoadRouteParnter(id);
Id = id;
// Assuming your DataRow returned has all the info you need.
RouteCompany = row["RouteCompany"];
SomePartner = new Partner();
SomePartner.PartnerId = row["PartnerID"]; // cast to guid, just example
SomePartner.PartnerName = row["PartnerName"].ToString();
}
}
public class Partner
{
public Guid PartnerId { get; set; }
public string PartnerName { get; set; }
}
I would loosely couple the data layer. You could use ADO.NET or any other way to communicate with the database. I usually do this by creating a parameterized constructor (say taking in an ID to load), to call the data layer. The data layer returns a DataTable, DataRow, DataSet, or a custom object. In the constructor, you populate the values of the RouteParnter object. Likewise you could have a List in the ViewModel.
I wanted to provide a quick update to how I solved this issue. I ended up creating a PartnerContext for EF. Once created, in controllers that need access to the list, I placed PartnerContext.ToList() into ViewBag and then used ASP helpers to load the list.
I am learning Asp.net MVC.
I am building an application in which I have following components:
1.Student class:
public class Students
{
[Required(ErrorMessage="Student Id is required")]
[Display(Name="Student Id")]
public int Sid { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Address { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
public string Email { get; set; }
}
2.StudentManager class
public List<Students> sList = new List<Students>()
{
new Students{Sid=1001,FirstName="A",LastName="T",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1002,FirstName="B",LastName="U",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1003,FirstName="C",LastName="V",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1004,FirstName="D",LastName="W",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1005,FirstName="E",LastName="X",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1006,FirstName="F",LastName="Y",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
new Students{Sid=1007,FirstName="G",LastName="Z",DOB=Convert.ToDateTime("14-10-1987"),Email="a#b.com",Address="I"},
};
public void Edit(Students s)
{
Students stud = sList.Where(st => st.Sid == s.Sid).First();
stud.FirstName = s.FirstName;
stud.LastName = s.LastName;
stud.Email = s.Email;
stud.DOB = s.DOB;
}
3. And a student controller
public static StudentManager sm = new StudentManager();
// GET: Student
public ActionResult Index()
{
return View(sm.sList);
}
public ActionResult Edit(int? id)
{
Students student = sm.sList.Where(s => s.Sid == id).First();
return View(student);
}
[HttpPost]
public ActionResult Edit(Students s)
{
sm.Edit(s);
return RedirectToAction("Index");
}
My queries are:
1.I am able to edit a student's details.But if I do not use the keyword static in Controller then it is not updating.
2.How Model Binding automatically passes Student object to HttpPost Edit()?
Can someone please explain?
Every time you create StudentManager class you are creating new collection of students.
When StudentManager is static it is created only once, so you ll use the same object for every call and modify collection in it.
When StudentManager is not static for every call you ll create new object with new collection, so your edit will modify collection within object that ll be thrown away with a next call (like view).
It's not a good practice to have references to dependencies as static properties. I would recommend to make you collection property static and initialize it only once for life cycle of application and later migrate to use db or file or a call, depending of what your goal is.
Model Binding uses one of the registered Model Binders, some are provided by default so for main types of data, like JSON, you don't have to to anything.
I would definitely recommend to read a good book about mvc, to get the mechanics behind it. As the good and the bad part about it is it works like "magic" =)
I am building a application using Entity Framework 6.1 code first and ASP.NET MVC5.
I got the following model:
class Person {
public Int32 Id {get; set;}
[Required]
public String Name {get; set;}
public List<Book> Books {get; set;}
}
class Book {
public Int32 Id {get; set;}
[Required]
public String Title {get; set;}
public List<Person> Authors {get; set;}
}
The two basic edit-views for changing Person.Name or Book.Title are straight forward.
Now I got one "real world"-view where the user can edit a Book and add existing Authors to them.
The view posts the values like this:
Title = "Nice Book"
Authors[0].Id = 7
Authors[1].Id = 3
The view will only send the Id of the Persons/Authors. After all, the user won't rename an author while editing a Book.
All the model binding works great and the controller can map the posted fields.
Yet the validation of the bound model fails when the author/Person has no (required) Name set.
Is there some kind of workaround? Currently I omit the RequiredAttribute for the affected properties. I would like to not make a view-model for every aspect of my (rather complex) model-graph where some or another item will get associated.
This is why you should use view models. As they allow you to customize what properties should be exposed to the view. However, you're also no handling the posting of selected authors correctly. For a multiselect like this you really need a view model.
public class BookViewModel
{
public BookViewModel()
{
SelectedAuthorIds = new List<int>();
}
[Required]
public string Title { get; set; }
public List<int> SelectedAuthorIds { get; set; }
public IEnumerable<SelectListItem> AuthorChoices { get; set; }
}
Notice, first, that there's no Id property. You may need to add this if you want to use this same view model to edit books, as well, but for the purposes of creation, there's no need to have an Id field in the view, so we leave it off. Next, there's no Authors property. Instead we have SelectedAuthorIds which is a list of integers and AuthorChoices which will hold all the Authors a user can select from. Finally, there's a constructor to initialize SelectedAuthorIds so it's never null. An empty list translates to nothing being selected, but we need an actual list.
Now, use this view model as the model for your view:
#model Namespace.To.BookViewModel
And, change your author select field(s) to:
#Html.ListBoxFor(m => m.SelectedAuthorIds, Model.AuthorChoices)
You'll also need to populate AuthorChoices, which you'll handle in your GET action for this form:
public ActionResult Create()
{
var model = new BookViewModel();
model.AuthorChoices = db.Authors.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = m.Name
});
return View(model);
}
Finally, after the user posts the form, you'll need to use SelectedAuthorIds to fetch those authors from the database and add them to your Book being created. As far as that goes, you'll also need to map the data from BookViewModel to an instance of Book. All this means is copying over the data from one to the other. There's libraries for this type of thing, such as AutoMapper, but this is relatively simple, so I'll handle it manually:
[HttpPost]
public ActionResult Create(BookViewModel model)
{
if (ModelState.IsValid)
{
var book = new Book
{
Title = model.Title
};
book.Authors = db.Authors.Where(m => model.SelectedAuthorIds.Contains(m.Id));
db.Books.add(book);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
The edit version will be similar, except you'll instead pull an existing Book instead of creating a new one:
[HttpPost]
public ActionResult Edit(int id, BookViewModel model)
{
var book = db.Books.Find(id);
book.Title = model.Title;
...
}
I am using entity framework 4.3 in my MVC 3 application, when trying to update the entity(creating and deleting works fine) I am getting this error:
Store update, insert, or delete statement affected an unexpected number of rows (0)
When I got into debug mode I saw that on the [HttpPost] method no feed Id was supplied:
public ActionResult Edit(Feed feed)
{
if (ModelState.IsValid)
{
db.Entry(feed).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.FolderId = new SelectList(db.Folders, "FolderId", "Name", feed.FolderId);
return View(feed);
}
although in the normal Get method the id in being passed. those are my entities
feed:
public class Feed
{
[ScaffoldColumn(false)]
public int FeedId { get; set; }
[StringLength(150, ErrorMessage = "The {0} must be less then {1} charecters")]
public string Title { get; set; }
[ScaffoldColumn(false)]
public string Description { get; set; }
[Required(ErrorMessage = "you must enter a valid link")]
[StringLength(500, ErrorMessage = "The {0} must be less then {1} characters long.")]
public string LinkUrl { get; set; }
[ScaffoldColumn(false)]
public DateTime PublishDate { get; set; }
[ScaffoldColumn(false)]
public string Image { get; set; }
[ForeignKey("Folder")]
[Required(ErrorMessage="you must choose a folder")]
public int FolderId { get; set; }
public virtual Folder Folder { get; set; }
public Feed()
{
PublishDate = new DateTime(2012, 1, 1);
}
}
folder:
public class Folder
{
public int FolderId { get; set; }
[Required(ErrorMessage = "you must enter a folder name")]
[StringLength(150, ErrorMessage = "the {0} must be less then {1} charecters")]
public string Name { get; set; }
}
I have looked for a solution but none of them worked, like trying the refresh method, which doesn't exist in DbContext or defining a [Key] property above the FeedId and FolderId.
You shouldn't be manually maintaining the entity state - change tracking should be performed by the context.
You're using a view model and assuming it should be attached to the database.
You need to do something like..,
Feed DbFeed = DBSet.Where(f => f.id = feed.Id);
DbFeed.Property = NewValue;
db.SaveChanges();
(excuse possibly incorrect syntax - I work in VB by default)
That is, get a new instance of the Feed object from the DB Context, then perform changes on the object you're given.
This is because the context doesn't actually give you a plain Feed object, but rather an anonymous type which wraps it and has the same properties. The wrapper overrides your methods and detects property changes which is how it maintains state.
The feed object you get back from your view doesn't contain this wrapper, hence the problems
Entity Framework tracks objects, the feed you get from your view is untracked. The pattern for this situation is to fetch the object you want to update from the db, then call UpdateModel which will apply the changes from your untracked entity to your tracked entity, which you can then save..
if (ModelState.IsValid)
{
var trackedEntity = db.Find(feed.Id)
UpdateModel(trackedEntity);
db.SaveChanges();
return RedirectToAction("Index");
}
apparently but putting the [ScaffoldColumn(false)] attribute in my model it didn't create it in my view and there for the id was not passed.
I added #Html.HiddenFor(model => model.FeedId) to my model and that took care of the issue.
Apparently you were having concurrency problems.
Your update state should be running as:
UPDATE tableA SET colA = 'value' WHERE colX1 = 'compare1' AND colX2 = 'compare2';
This colXn could be your primary keys etc, or could be every column you are using. If you are not handling concurrency, if someone gets the data as the same time as you, alter and save it before you, your WHERE statement will never match, since the record information you are updating already has new information.