Access ViewModel fields in Edit view - asp.net-mvc

My Model:
public ECmain()
{
this.Notes = new Collection<Notes>();
}
public int ID { get; set; }
public string Auth { get; set; }
public string KeyWords { get; set; }
public string Description { get; set; }
public string URL { get; set; }
public string Category { get; set; }
public string SubCategory { get; set; }
public string Title { get; set; }
public string Live { get; set; }
public virtual ICollection<Notes> Notes { get; set; }
public virtual ICollection<Email> Email { get; set; }
}
public class MyViewModel
{
public IQueryable<Notes> NotesList { get; set; }
public IQueryable<ECmain> ECmainList { get; set; }
public int ECmain.ID { get; set; }
public IQueryable<Email> EmailList { get; set; }
}
My Controller:
// GET: ECmain/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var viewModel = new MyViewModel
{
ECmainList = from m in db.ECmain.Take(10)
where m.ID == id
select m,
NotesList = from n in db.Notes
where n.ECmainID ==id
select n,
EmailList = from e in db.Email
where e.ECmainID ==id
select e
};
// viewModel.NotesList = new
if (viewModel == null)
{
return HttpNotFound();
}
return View(viewModel);
}
My Edit View:
#model EditSuite.Models.MyViewModel
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.ID )
I want to access the ECmainList.ID The error is
Compiler Error Message: CS1061: 'EditSuite.Models.MyViewModel' does not contain a definition for 'ID'
I tried
#Html.HiddenFor(model => model.ECmainListID.ID )
and
#Html.HiddenFor(Model.model.ECmainListID.ID )
Neither one worked.

It seems pretty obvious from the compiler message:
Compiler Error Message: CS1061: 'EditSuite.Models.MyViewModel' does not contain a definition for 'ID'
So where on the following model is the ID?:
public class MyViewModel
{
public IQueryable<Notes> NotesList { get; set; }
public IQueryable<ECmain> ECmainList { get; set; }
public IQueryable<Email> EmailList { get; set; }
// there is no: public int ID { get; set; } ?
}
I want to access the ECmainList.ID
However, per your model the ECmainList is a IQueryable<ECmain> and the IQueryable<T> also does not have a public property or field called ID. How can you add an editor for a ID of a list of objects? You'd need to loop through the list and have multiple ID fields.

Related

NullReferenceException in a three table model in Entity Framework

I'm trying to join three tables in a view model. It works with two tables but crashes when I add a third. Here are the models and the controller. The models section_detail, phone, and department were generated by Entity Framework.
EmployeeViewModel was created by copying properties from the other models. I've abbreviated some of the models shown here with:
public partial class section_detail
{
public int section_detail_id { get; set; }
public Nullable<int> parent_section_det_id { get; set; }
. . .
public string Comments { get; set; }
public string email { get; set; }
public virtual department department { get; set; }
public virtual phone phone { get; set; }
}
public partial class phone
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public phone()
{
this.section_detail = new HashSet<section_detail>();
}
public int phone_id { get; set; }
public string area_code { get; set; }
public string phone_nbr { get; set; }
. . .
public string activity_code { get; set; }
public string function_code { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<section_detail> section_detail { get; set; }
public virtual BudgetUnit BudgetUnit { get; set; }
}
public partial class department
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public department()
{
this.section_detail = new HashSet<section_detail>();
}
public int dept_id { get; set; }
public string description { get; set; }
public string cost_center_code { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<section_detail> section_detail { get; set; }
}
public class EmployeeViewModel
{
public int section_detail_id { get; set; }
public Nullable<int> parent_section_det_id { get; set; }
public Nullable<byte> page_code { get; set; }
public string cost_center_code { get; set; }
public string print_descrip { get; set; }
public Nullable<int> phone_id { get; set; }
public Nullable<int> employee_id { get; set; }
public static explicit operator EmployeeViewModel(List<section_detail> v)
{
throw new NotImplementedException();
}
public string first_name { get; set; }
. . .
public string Comments { get; set; }
public string email { get; set; }
public string description { get; set; }
public string area_code { get; set; }
public string phone_nbr { get; set; }
public string BU { get; set; }
}
Controller:
private vcpds_test1Entities db = new vcpds_test1Entities();
// GET: EmployeeList
public ActionResult Index()
{
List<section_detail> employeeList = db.section_detail.ToList();
List<EmployeeViewModel> employeeVMList = employeeList.Where(emp => emp.page_code == 3)
.Select(emp => new EmployeeViewModel
{
last_name = emp.last_name,
first_name = emp.first_name,
employee_id = emp.employee_id,
phone_nbr = "(" + emp.phone.area_code + ") " + emp.phone.phone_nbr.Substring(0, 3) + "-" + emp.phone.phone_nbr.Substring(3, 4),
BU = emp.phone.BU,
description = emp.department.description,
page_code = emp.page_code
}).OrderBy(emp => emp.last_name).ThenBy(emp => emp.first_name).ToList();
return View(employeeVMList);
}
I get these messages:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
VCPDS2.Models.section_detail.department.get returned null.
If I comment out description = emp.department.description from the controller, then it will return data from the section_detail and phone tables. I've checked the database and the relationships seem ok. I've tried refreshing the models from the database with no change.
It's possible that a emp doesn't have a department so it in itself is null. Description can't be a property of a null. So, what you can simply do is check if it is null first by using null operator:
...
//description = emp.department.description,
description = emp.department?.description ?? "",
...
Basically, if department itself is null, it will stop checking right there, and the ?? shortcut is to use the statement on the right side which is "" if the statement on the left is null.
If you were not expecting an emp not to have a department, you may need to revise your query
Quick edit: You probably need to use an Include in your query so it can bring the department's properties (for description):
List<section_detail> employeeList = db.section_detail
.Include(x => x.department)
.ToList();

ASP.NetCore OData DTO $expand navigation property results in empty array

I am using Entity Framework with OData to get data from my mysql database but I don't want to expose database entites to the user, so I've created some DTO's and map them with Automapper.
My Problem is that everything works fine except loading entities with $expand.
There are 2 Entities with 2 DTO's (in my project the dto's and domain models do not look the same, this is only for better reading):
public partial class Product
{
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<ProductPrice> ProductPrices { get; set; }
}
public class ProductDTO
{
[Key]
public string Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public virtual ICollection<ProductPriceDTO> ProductPrices { get; set; }
public virtual CategoryDTO Category { get; set; }
}
public partial class Category
{
public int Id { get; set; }
public string Title { get; set; }
}
public class CategoryDTO
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
}
public partial class ProductPrice
{
public string VendorId { get; set; }
public string ProductId { get; set; }
public decimal Price { get; set; }
public virtual Product Product { get; set; }
public virtual Vendor Vendor { get; set; }
}
public class ProductPriceDTO
{
[Key]
[ForeignKey("Vendor")]
public string VendorId { get; set; }
[Key]
[ForeignKey("Product")]
public string ProductId { get; set; }
public decimal Price { get; set; }
public virtual VendorDTO Vendor { get; set; }
public virtual ProductDTO Product { get; set; }
}
The models are created the following way:
public IEdmModel GetEdmModel(IServiceProvider serviceProvider)
{
var builder = new ODataConventionModelBuilder(serviceProvider);
builder.Namespace = "Functions";
//category
builder.EntitySet<CategoryDTO>("Categories").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
//product
builder.EntitySet<ProductDTO>("Products").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
return builder.GetEdmModel();
//productprice
builder.EntitySet<ProductPriceDTO>("ProductPrices").EntityType.Select().Filter().OrderBy().Expand().Count().Page();
}
Automapper profile:
public AutoMapperProfile()
{
CreateMap<Product, ProductDTO>()
.ForMember(dto => dto.Category, conf => conf.AllowNull())
.ForMember(dto => dto.ProductPrices, dest => dest.MapFrom(x => x.ProductPrices))
.ForMember(dto => dto.ProductPrices, dest => dest.ExplicitExpansion())
.ForMember(dto => dto.ProductPrices, conf => conf.AllowNull());
CreateMap<ProductPrice, ProductPriceDTO>()
.ForMember(dto => dto.Product, conf => conf.AllowNull())
.ForMember(dto => dto.Vendor, conf => conf.AllowNull());
}
Controller:
[Authorize]
[ODataRoutePrefix("Products")]
public class ProductsController : BaseODataController
{
private readonly IProductService ProductService;
private readonly IProductPriceService ProductPriceService;
public ProductsController(IMapper mapper, IProductService productService, IProductPriceService productPriceService) : base(mapper)
{
ProductService = productService;
ProductPriceService = productPriceService;
}
[AllowAnonymous]
[EnableQuery]
public IQueryable<ProductDTO> Get(ODataQueryOptions queryOptions)
{
var query = ProductService.QueryProducts();
string[] includes = GetExpandNamesFromODataQuery(queryOptions);
if (includes != null && includes.Length > 0)
{
return query.ProjectTo<ProductDTO>(null, includes);
}
return query.ProjectTo<ProductDTO>();
}
[AllowAnonymous]
[EnableQuery]
[ODataRoute("({key})")]
public IQueryable<ProductDTO> Get([FromODataUri] string key, ODataQueryOptions queryOptions)
{
var query = ProductService.QueryProducts().Where(x => x.Id.Equals(key));
string[] includes = GetExpandNamesFromODataQuery(queryOptions);
if (includes != null && includes.Length > 0)
{
return query.ProjectTo<ProductDTO>(null, includes);
}
return query.ProjectTo<ProductDTO>();
}
}
As I mentioned above every query works fine ($select, $filter, $orderBy, $count).
But when I call the following:
https://localhost:44376/odata/Products('631794')?$expand=Category
I get:
{"#odata.context":"https://localhost:44376/odata/$metadata#Products","value":[
as response.
In the output of Visual Studio there is a message:
No coercion operator is defined between types 'System.Int16' and 'System.Boolean'.
I think there must be something wrong with the Automapper profile. As I read somewhere .ProjectTo() with include parameters creates a Select to get the related data from the navigation property. I thought it is enough to create the relation with [ForeignKey] in the DTO.

There is no ViewData item of type 'IEnumerable<SelectListItem> MVC

I've done this EF MVC Application (Code First) with listing/editing/deleting functions. Everything works fine, but now I need to add two dropdown fields. Product has a category and a subcategory which needed to be edited. This is what I have so far:
Main class where ProductSubcategoryID is a foreign key
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public string ProductNumber { get; set; }
public int? ProductSubcategoryID { get; set; }
public IEnumerable<SelectList> SelectedCat = new List<SelectList> {};
public IEnumerable<SelectList> SelectedSubCat = new List<SelectList> {};
}
public class ProductCategory
{
public int ProductCategoryID { get; set; }
public string Name { get; set; }
}
public class ProductSubcategory
{
public int ProductSubcategoryID { get; set; }
public int ProductCategoryID { get; set; }
public string Name { get; set; }
}
On the Product controller class I have:
public ActionResult Create()
{
ViewBag.SubcatSelection = new SelectList(dbSubcat.ProductSubcategories, "ProductSubcategoryID", "Name"); ;
return View();
}
and on Edit:
#Html.LabelFor(model => model.ProductSubcategoryID)
#Html.DropDownListFor(model => model.SelectedSubCat, ViewBag.SubcatSelection as SelectList, "ProductSubcategoryID", "Name");
The result:
There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'SelectedSubCat'.

Model or ViewModel in a ListViewModel

Should T be a for example Customer or CustomerViewModel ?
The annotations bound to Mvc namespace are on the ListViewModel so actually I could pass the Customer object. What do you think?
public class ListViewModel<T>
{
[Required(ErrorMessage="No item selected.")]
public int[] SelectedIds { get; set; }
public IEnumerable<T> DisplayList { get; set; }
}
UPDATE
[HttpGet]
public ActionResult Open()
{
IEnumerable<Testplan> testplans = _testplanDataProvider.GetTestplans();
OpenTestplanListViewModel viewModel = new OpenTestplanListViewModel(testplans);
return PartialView(viewModel);
}
public class OpenTestplanListViewModel
{
public OpenTestplanListViewModel(IEnumerable<Testplan> testplans)
{
var testplanViewModels = testplans.Select(t => new TestplanViewModel
{
Name = string.Format("{0}-{1}-{2}-{3}", t.Release.Name, t.Template.Name, t.CreatedAt, t.CreatedBy),
TestplanId = t.TestplanId,
});
DisplayList = testplanViewModels;
}
[Required(ErrorMessage = "No item selected.")]
public int[] SelectedIds { get; set; }
public string Name { get; set; }
public IEnumerable<TestplanViewModel> DisplayList { get; private set; }
}
public class TestplanViewModel
{
public int TestplanId { get; set; }
public string Name { get; set; }
}
public class Testplan
{
public int TestplanId { get; set; }
public int TemplateId { get; set; }
public int ReleaseId { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedAt { get; set; }
public Template Template { get; set; }
public Release Release { get; set; }
}
T should ideally be a view model. Having a view model referencing domain models is some kind of a hybrid view model, not a real one. But if you think that in this specific case the domain model will be exactly the same as the view model then you could keep it as well.

MVC 3 custom validator doesnt get called

I am trying to validate if my dropdownlist's item already exists in the DB. It should set Model.IsValid to false, but it pass true all the time, looks like validator just doesn't get called
DropDownList code:
</td>
</tr>
<tr>
<td> #Html.Label("VendorId ") </td>
<td> #Html.DropDownListFor(x => x.id, new SelectList(Model.validatingVendors, "VendorKey", "VerndorID", Model.id)) #Html.ValidationMessageFor(x=>x.validatingVendors.FirstOrDefault(s=>s.VendorKey == x.id).VerndorID)</td>
</tr>
My custom Validator Class
public class VendorAttribute : ValidationAttribute
{
DataManager manager = new DataManager();
public override bool IsValid(object value)
{
var stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
if (value == null)
{
return true;
}
if ((value is string) && string.IsNullOrEmpty((string)value))
{
return true;
}
var name = value.ToString().ToLower();
// fake a database lookup and bring back two widgets
return (manager.VendorAllreadyExcists(stringValue));
}
public override string FormatErrorMessage(string name)
{
return name;
}
}
My Model class
public class VelidationVendorViewModel
{
public int VendorKey { get; set; }
[Required]
[Vendor(ErrorMessage = "Vendor Allerede eksister")]
public string VerndorID { get; set; }
public int LatestSalesCWeek { get; set; }
public bool Active { get; set; }
}
My parentViewModel
public class ParentViewModel
{
public List<FactRefIdFileFormatIdViewModel> model { get; set; }
public RefrenceDataViewModel refModel { get; set; }
public List<MetaFileFormatsViewModel> metaModel { get; set; }
public int id { get; set; }
public VelidationVendorViewModel vendorId { get; set; }
public IEnumerable<VelidationVendorViewModel> validatingVendors { get; set; }
}
SOLUTION
Just got it solved
[Required]
[Vendor(ErrorMessage = "Vendor Allerede eksister")]
public string VerndorID { get; set; }
on my viewmodel is wrong since i haveto move my custom validation to my id inside of my ParentViewModel so it looks like this:
public List<FactRefIdFileFormatIdViewModel> model { get; set; }
public RefrenceDataViewModel refModel { get; set; }
public List<MetaFileFormatsViewModel> metaModel { get; set; }
[Required]
[Vendor(ErrorMessage = "Vendor Allerede eksister")]
public int id { get; set; }
public VelidationVendorViewModel vendorId { get; set; }
public IEnumerable<VelidationVendorViewModel> validatingVendors { get; set; }
And last but not least my validation have to call ID instead of vendorId of specific item in a list like this :
#Html.ValidationMessageFor(x=>x.Id)
have you tried debugging and see for yourself if validator get called ?
from your method name it seems you are using wring logic :
return (manager.VendorAllreadyExcists(stringValue));
return TRUE if DO EXISTS, is that what you want? dont you want to return TRUE if it DOES NOT EXISTS ? (assuming from the fact, that you return TRUE for cases when name is "invalid" - not testable against DB)
Just got it solved
[Required]
[Vendor(ErrorMessage = "Vendor Allerede eksister")]
public string VerndorID { get; set; }
on my viewmodel is wrong since i haveto move my custom validation to my id inside of my ParentViewModel so it looks like this:
public List<FactRefIdFileFormatIdViewModel> model { get; set; }
public RefrenceDataViewModel refModel { get; set; }
public List<MetaFileFormatsViewModel> metaModel { get; set; }
[Required]
[Vendor(ErrorMessage = "Vendor Allerede eksister")]
public int id { get; set; }
public VelidationVendorViewModel vendorId { get; set; }
public IEnumerable<VelidationVendorViewModel> validatingVendors { get; set; }
And last but not least my validation have to call ID instead of vendorId of specific item in a list like this :
#Html.ValidationMessageFor(x=>x.Id)

Resources