List<class> does not contain a definition for 'property' - asp.net-mvc

So I'm trying to make a simple MVC system that will search through a database of books by author and/or title. I have a model called Book.cs with title and author properties. In my controller I made an ActionResult as follows:
public ActionResult Search(string theAuthor, string theTitle)
{
if (theAuthor == null && theTitle == null)
{
ViewBag.title = "Search for a book by author and/or title";
}
else
{
ViewBag.title = "Results:";
}
List<Book> allBooks = db.Books.ToList();
List<Book> booksFound = new List<Book>();
foreach (Book theBook in allBooks)
{
if (theAuthor != null && theTitle != null)
{
if (theBook.author == theAuthor && theBook.title == theTitle) booksFound.Add(theBook);
}else if (theAuthor == null)
{
if (theBook.title == theTitle) booksFound.Add(theBook);
}else if (theBook == null)
{
if (theBook.author == theAuthor) booksFound.Add(theBook);
}
}
return View("Search", booksFound);
}
Now, this returns a List of books, so I assume that in my view I have to use List<Book> model, and so I did (#model List<Book>). But the problem is how am I going to send data to the action result? I tried using
#Html.TextBoxFor (x => x.author)
But that gives the
'List<Book>' does not contain a definition for 'author' and no extension method 'author' accepting a first argument of type 'List<Book>' could be found (are you missing a using directive or an assembly reference?)
error. Now that makes sense to me because I guess I can't access model class property if my model is a list. So am I doing something wrong or I should use another way to pass data?
Thanks

I didn't get your question when you said "But the problem is how am I going to send data to the action result?" but as much as I could comprehend from your question it seems you want to iterate the model which is a list of 'Book' objects inside MVC view. Controller has passed that list to your view as a model and then you want to create some UI elements based on the elements in the list. Here is how you can do it:
#for (int i = 0; i < Model.Count; i++)
{
#Html.TextBoxFor(x=> x[i].author)
}
To answer your other concern, there is absolutely no error in the way you have passed the model (which is a list) from controller to view but you were trying to access the author property on the list itself when it is available on the list elements. Hope this helps!

Related

Error checking in controller in ASP.NET Core MVC

I'm discovering ASP.NET Core MVC and I'm on my first project. Creating a cool web shop.
I'm currently wondering how to implement faulty information checking for example in the controller
Let's say there a product page, whenever users clicks on a product they will hit the function below.
As you can see the function accepts an int parameter named id, it will search in the database for the id that fits the productId, but I'm wondering how do I add error checking here? Like for example if the id does not exist in database return to page XX?
Also feel free to give suggestions to the function if you don't like it.
I've already tried to do a simple if and else statement
if(productvm == null)
{
then
return RedirectToPage("Index")
}
else
return View("ProductPage", productVm);
but it didn't seem to hit the if statement
[Route("ProductPage/{id}")]
public IActionResult ProductPage(int id)
{
Product product = _uow.Products.SelectProduct(id);
var stockViewModels = new List<StockViewModel>();
foreach (Stock stock in product.Stock)
{
stockViewModels.Add(new StockViewModel()
{
Id = stock.Id,
Description = stock.Description,
IsAvailable = stock.IsAvailable,
Quantity = stock.Quantity,
});
}
ProductViewModel productVm = new ProductViewModel
{
Name = product.Name,
Id = product.Id,
Description = product.Description,
Price = product.Price,
Stocks = stockViewModels,
};
if (productVm == null)
{
return RedirectToPage("Productslist");
}
else
{
return View("ProductPage", productVm);
}
}
I basically want an error handling the controller if the id is not found in the database then execute XX
The way how I test the function is to change the ID when browsing the page with an ID that does not exist in the database, then I get this error:
https://i.imgur.com/1amWx43.png
and I want to handle it
I think your problem is that you have new the productVm object before the if, so it will never be null, for your case, you should get check the product object and not the productVm, for example:
Product product = _uow.Products.SelectProduct(id);
if (product == null)
{
return RedirectToPage("Productslist");
}
else
{
return View("ProductPage", productVm);
}

Best Way to Update only modified fields with Entity Framework

Currently I am doing like this:
For Example:
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
db.Entry(oldobj).CurrentValues.SetValues(model);
}
It works, but for example,
I have 50 columns in my table but I displayed only 25 fields in my form (I need to partially update my table, with remaining 25 column retain same old value)
I know it can be achieve by "mapping columns one by one" or by creating "hidden fields for those remaining 25 columns".
Just wondering is there any elegant way to do this with less effort and optimal performance?
This is a very good question. By default I have found that as long as change tracking is enabled (it is by default unless you turn it off), Entity Framework will do a good job of applying to the database only what you ask it to change.
So if you only change 1 field against the object and then call SaveChanges(), EF will only update that 1 field when you call SaveChanges().
The problem here is that when you map a view model into an entity object, all of the values get overwritten. Here is my way of handling this:
In this example, you have a single entity called Person:
Person
======
Id - int
FirstName - varchar
Surname - varchar
Dob - smalldatetime
Now let's say we want to create a view model which will only update Dob, and leave all other fields exactly how they are, here is how I do that.
First, create a view model:
public class PersonDobVm
{
public int Id { get; set; }
public DateTime Dob { get; set; }
public void MapToModel(Person p)
{
p.Dob = Dob;
}
}
Now write the code roughly as follows (you'll have to alter it to match your context name etc):
DataContext db = new DataContext();
Person p = db.People.FirstOrDefault();
// you would have this posted in, but we are creating it here just for illustration
var vm = new PersonDobVm
{
Id = p.Id, // the Id you want to update
Dob = new DateTime(2015, 1, 1) // the new DOB for that row
};
vm.MapToModel(p);
db.SaveChanges();
The MapToModel method could be even more complicated and do all kinds of additional checks before assigning the view model fields to the entity object.
Anyway, the result when SaveChanges is called is the following SQL:
exec sp_executesql N'UPDATE [dbo].[Person]
SET [Dob] = #0
WHERE ([Id] = #1)
',N'#0 datetime2(7),#1 int',#0='2015-01-01 00:00:00',#1=1
So you can clearly see, Entity Framework has not attempted to update any other fields - just the Dob field.
I know in your example you want to avoid coding each assignment by hand, but I think this is the best way. You tuck it all away in your VM so it does not litter your main code, and this way you can cater for specific needs (i.e. composite types in there, data validation, etc). The other option is to use an AutoMapper, but I do not think they are safe. If you use an AutoMapper and spelt "Dob" as "Doob" in your VM, it would not map "Doob" to "Dob", nor would it tell you about it! It would fail silently, the user would think everything was ok, but the change would not be saved.
Whereas if you spelt "Dob" as "Doob" in your VM, the compiler will alert you that the MapToModel() is referencing "Dob" but you only have a property in your VM called "Doob".
I hope this helps you.
I swear by EntityFramework.Extended. Nuget Link
It lets you write:
db.Person
.Where(x => x.ID == model.ID)
.Update(p => new Person()
{
Name = newName,
EditCount = p.EditCount+1
});
Which is very clearly translated into SQL.
Please try this way
public update(Person model)
{
// Here model is model return from form on post
var oldobj = db.Person.where(x=>x.ID = model.ID).SingleOrDefault();
// Newly Inserted Code
var UpdatedObj = (Person) Entity.CheckUpdateObject(oldobj, model);
db.Entry(oldobj).CurrentValues.SetValues(UpdatedObj);
}
public static object CheckUpdateObject(object originalObj, object updateObj)
{
foreach (var property in updateObj.GetType().GetProperties())
{
if (property.GetValue(updateObj, null) == null)
{
property.SetValue(updateObj,originalObj.GetType().GetProperty(property.Name)
.GetValue(originalObj, null));
}
}
return updateObj;
}
I have solved my Issue by using FormCollection to list out used element in form, and only change those columns in database.
I have provided my code sample below; Great if it can help someone else
// Here
// collection = FormCollection from Post
// model = View Model for Person
var result = db.Person.Where(x => x.ID == model.ID).SingleOrDefault();
if (result != null)
{
List<string> formcollist = new List<string>();
foreach (var key in collection.ToArray<string>())
{
// Here apply your filter code to remove system properties if any
formcollist.Add(key);
}
foreach (var prop in result.GetType().GetProperties())
{
if( formcollist.Contains(prop.Name))
{
prop.SetValue(result, model.GetType().GetProperty(prop.Name).GetValue(model, null));
}
}
db.SaveChanges();
}
I still didn't find a nice solution for my problem, so I created a work around. When loading the Entity, I directly make a copy of it and name it entityInit. When saving the Entity, I compare the both to see, what really was changed. All the unchanged Properties, I set to unchanged and fill them with the Database-Values. This was necessary for my Entities without Tracking:
// load entity without tracking
var entityWithoutTracking = Context.Person.AsNoTracking().FirstOrDefault(x => x.ID == _entity.ID);
var entityInit = CopyEntity(entityWithoutTracking);
// do business logic and change entity
entityWithoutTracking.surname = newValue;
// for saving, find entity in context
var entity = Context.Person.FirstOrDefault(x => x.ID == _entity.ID);
var entry = Context.Entry(entity);
entry.CurrentValues.SetValues(entityWithoutTracking);
entry.State = EntityState.Modified;
// get List of all changed properties (in my case these are all existing properties, including those which shouldn't have changed)
var changedPropertiesList = entry.CurrentValues.PropertyNames.Where(x => entry.Property(x).IsModified).ToList();
foreach (var checkProperty in changedPropertiesList)
{
try
{
var p1 = entityWithoutTracking.GetType().GetProperty(checkProperty).GetValue(entityWithoutTracking);
var p2 = entityInit.GetType().GetProperty(checkProperty).GetValue(entityInit);
if ((p1 == null && p2 == null) || p1.Equals(p2))
{
entry.Property(checkProperty).CurrentValue = entry.Property(checkProperty).OriginalValue; // restore DB-Value
entry.Property(checkProperty).IsModified = false; // throws Exception for Primary Keys
}
} catch(Exception) { }
}
Context.SaveChanges(); // only surname will be updated
This is way I did it, assuming the new object has more columns to update that the one we want to keep.
if (theClass.ClassId == 0)
{
theClass.CreatedOn = DateTime.Now;
context.theClasses.Add(theClass);
}
else {
var currentClass = context.theClasses.Where(c => c.ClassId == theClass.ClassId)
.Select(c => new TheClasses {
CreatedOn = c.CreatedOn
// Add here others fields you want to keep as the original record
}).FirstOrDefault();
theClass.CreatedOn = currentClass.CreatedOn;
// The new class will replace the current, all fields
context.theClasses.Add(theClass);
context.Entry(theClass).State = EntityState.Modified;
}
context.SaveChanges();
In EF you can do like this
var result = db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
if(result != null){
result.Name = newName;
result.DOB = newDOB;
db.Person.Update(result);
}
Or you can use
using (var db= new MyDbContext())
{
var result= db.Person.Where(x => x.ID == model.ID).FirstOrDefault();
result.Name= newName;
result.DOB = newDOB;
db.Update(result);
db.SaveChanges();
}
For more detail please EntityFramework Core - Update Only One Field
No Worry guys
Just write raw sql query
db.Database.ExecuteSqlCommand("Update Person set Name='"+_entity.Name+"' where Id = " + _entity.ID + "");

Model does not contain a definition error

I am having an issue in my project and wonder if some can pinpoint me to where i may have gone wrong. so basically in my project i have got an ActionResult method like so:
public ActionResult PartNumberManufacturer(string id)
{
var partNumber = _Context.PartNumberTable.FirstOrDefault(x => x.partNumberId == id);
return PartialView("PartNumberView", partNumber);
}
Then this is my view
#if (Model != null && !string.IsNullOrEmpty(Model.PartNumberManufacturer))
{
#if (Model.PartNumberManufacturer == "Fr")
{
<div>
<p> This product is developed in france </p>
#if (#Model.PartNumberManufacturer == "Ger")
{
<p> This Product is developed in Germany </p>
}
well the above code works fine. but the thing is that if you look back into my ActionResult controller method , i am using :
var partNumber = Context.PartNumberTable.FirstOrDefault(x => x.partNumberId == id);
FirstOrDefault of-course will only return the first found part number in the database.
This isnot what i am looking for i don't want it only to return the first found record , i want it to return all the records that match by the search query in the database. So in order to do and after a few research online i found that i had to return the results into a list and this made me amend my ActionResult method to like this:
public ActionResult PartNumberManufacturer(string id)
{
var _methodOfRepair = Context.PartNumberTable.Where(x => x.PartNumberId == id); // changed thisline
return PartialView("PartNumberView", partNumber);
}
so yeah i get the error message saying that:
{"'System.Data.Entity.Infrastructure.DbQuery' does not contain a
definition for 'PartNumberManufacturer'"}
i made those changes. what am i doing wrong here? or is there a different approach i can be recommended to in order to achieve this?
thank you for your time.
First recommendation, read about implementing the repository pattern.
Second, if you are sending a collection of objects to your View then your View need to be expecting that data type. Change the Model data type that your View is expecting.
Inside your View, you should iterate over every element and render it.

Avoid to show Null or specific values to razor view engine

I am working on asp.net mvc3 web application using MS Sql server 2008 express rc2. In my app I have two different brands in DB and one of them have few Null or 'unknown' values (e.g. 'unknown' is added to DB instead of Null). My question is how to pass only non null values to View Engine instead of using If/Else statements in View?
in controller:
var model = _data.GetViewModel(query);
if (model != null)
{
return View(model);
}
else
return View("Error");
in ViewModel;
public int Id { get; set; }
public string Query { get; set; }
public string Brand { get; set; }
public string Family { get; set; }
public string Type { get; set; }
in Model:
public ViewModel GetViewModel(string query)
{
var data = _comp.Get(p => p.Query == query);
if (data == null) return null;
return new ViewModel
{
Id = data.id,
Brand = data.brand,
Family = data.family,
Type = data.type
};
}
in View (I am currently using If statement):
#if (Model.Brand != null)
{
<span class="brand">#Model.Brand</span>
}
#if (Model.Family != null)
{
<span class="family">#Model.Family</span>
}
#if (Model.Type != null)
{
<span class="type">#Model.Type</span>
}
Note: I want to avoid If statement because there are too many values in the Database of each brand, and many of the them are Null, So I don't want to generate Html for those Null values. I am using If/Else statement like above code, and for checking too many values in View using If, it costs Memory on server and processor, and it also slow down server response time.
I want to have an alternative method to do this. Should I use Partial views or anything else?
Please Please help me to solve this, Your help is very appreciated.
Thanks and Regards.
First, some background/context, then my suggestion.
(By the way, this all applies to any version of ASP.NET MVC or ASP.NET NancyFX (yes, there's another option out there!!), etc)
Context / Background
To solve this, people generally fall into two types of categories:
Just get data and let the View decide what to show (common one, not the proper way IMO).
The Controller should handle all the heavy lifting and give the view the exact answer (proper way, IMO).
The first way is quick and dirty. Sure it works, but it puts too much logic into the view. Views are not supposed to do any logic at all (exception: for loops, and maybe the odd if/else, maybe). The main reason for this is testing. Yep, that dirty word which people hate and think it's for hippies only. Or .. I don't have the time to test.. so I manually test, etc.. If you put any business logic into a view, you cannot test that.
The second way might seem a bit slower at first, but that's like anything - the more you practice, the faster you go. This is (IMO) the preferred method of doing things because you can test the controller. The controller should create a view model which will have -the exact- results that the view needs. Not extra. For example, imagine you want to return a list of Brands to the display/view. Most people do (the equivalent of) Get-all-brands into a list, and send that list to the view, even though 80% of those properties are -not- going to be used by that view! Even if ONE property is not going to be used by that view, do not retrieve it nor send it to the view!
So - TL;DR; do all the heavy lifting in the controller. The View is dumb. Just dump the exact view model data, to the view.
Solution to your problem
Ok, so let's roll with idea #2 and get all this stuff happening in the controller.
// Grab the results.
// ASSUMPTION: It is only returning the -exact- data I need. No more, no less.
var results = _data.GetViewModel(query);
if (model == null)
{
// Project the results into a perfectly tight & svelte view model
// 100% specific for this view.
var viewModel = results.
Select(x => new ViewModel
{
Id = x.Id,
Brand = string.IsNullOrEmpty(x.Brand)
? string.Empty
: x.Brand,
Family = string.IsNullOrEmpty(x.Family)
? string.Empty
: x.Family,
Type = string.IsNullOrEmpty(x.Type)
? string.Empty
: x.Type,
}).ToList();
return viewModel;
Testing this..
[Fact]
public void GivenSomeBrands_Index_ReturnsAViewModel()
{
// Arrange.
// NOTE: Our fake repostitory has some fake data. In it ..
// Id: 1, Brand: Gucci.
// Id: 22, Brand: null.
var controller = new BrandController(SomeFakeRepositoryThingy);
// Act.
var result = controller.Index(); // This calls that controller code, above.
// Assert.
Assert.IsNotNull(result); // Controller returned some result.
Assert.IsNotNull(result.Model); // We have some model data.
var model = result.Model as IList<ViewModel>(); // Cast the Model value.
Assert.NotNull(model); // We have a strongly typed view model.
// We check the first brand value.
Assert.Equal("Gucci", model.First().Brand);
// We know this item has a null Brand,
Assert.Equal(string.Empty, model[21].Brand); but the ViewModel converted it.
}
You could write a custom HTML helper:
public static string MyHelper<V>(this HtmlHelper helper, V value, string css)
{
if (value == null)
return "";
return String.Format("<span class='{0}'>{1}</span>", value, css);
}
Then in your view:
#Html.MyHelper(Model.Brand, "brand");
#Html.MyHelper(Model.Family, "family");
#Html.MyHelper(Model.Type, "type");

Pass multiple data items from controller to view

I am using VS 2010, MVC, VS 2005
I create .dbml file as my model and map tables inside .dbml file
i join tables using LINQ to SQL. I want to display record of two tables i.e. tbl_class, tbl_subject
COde in my controller looks like this
public ActionResult SubjectByTeacher()
{
var DataContext = new SMSAPPDataContext();
var resultclass = (from t in DataContext.tbl_teachers
from e in DataContext.tbl_teacherenrollments
from b in DataContext.tbl_batches
from c in DataContext.tbl_classes
from s in DataContext.tbl_subjects
where
t.Teacher_ID == e.Teacher_ID
&&
e.Batch_ID == b.Batch_ID
&&
b.Class_ID == c.Class_ID
&&
e.Sub_ID == s.Sub_ID
&&
t.Teacher_Name == "ABC"
select c;
var resultsubject = from t in DataContext.tbl_teachers
from e in DataContext.tbl_teacherenrollments
from b in DataContext.tbl_batches
from c in DataContext.tbl_classes
from s in DataContext.tbl_subjects
where
t.Teacher_ID == e.Teacher_ID
&&
e.Batch_ID == b.Batch_ID
&&
b.Class_ID == c.Class_ID
&&
e.Sub_ID == s.Sub_ID
&&
t.Teacher_Name == "ABC"
select s;
return View();
}
Then i create a class in controller to map above two variables i.e. resultclass, resultsubject
public class MyViewModel
{
public MyViewModel(SMSAPPDataContext resultclass, SMSAPPDataContext resultsubject)
{
this.rc = resultclass;
this.rs = resultsubject;
}
public SMSAPPDataContext rc { get; private set; }
public SMSAPPDataContext rs { get; private set; }
}
This class will be used in creating view as model in strongly typed view.
But i cant figure it out, what to pass in return view ?????
It may be just like i.e. return view(new myviewmodel);
But this gives error, Should i use ToList() property any where in code ???
If any one can told me any other way to do this, please help
Regards
Assuming your view is strongly typed to use MyViewModel, e.g:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<YourNameSpaceHere.MyViewModel>" %>
Then using something like
return View(new MyViewModel(resultclass, resultsubject));
In the controller should work. Although I don't think SMSAPPDataContext is the correct type that the LINQ query will be returning.

Resources