One question about ASP / MVC 2 Data Annotations.
I have ViewModels with inheritance:
public class CategoryModel
{
[Required]
[Display(Name = "XName")]
//[DisplayName("XXX")]
[StringLength(255)]
public virtual string Name { get; set; }
}
public class CategoryListModel : CategoryModel
{
[Display(Name = "WName")]
//[DisplayName("WWW")]
[StringLength(255)]
public new string Name { get; set; }
}
Then in Controller:
public ActionResult List()
{
CategoryListModel model = new CategoryListModel();
return View(model);
}
And then in typed View:
<%=Html.LabelFor(m => Model.Name)%>
Why [Display(Name="SomeName")] does not work, neither in base model nor inherited one?
Meanwhile [DisplayName("SomeName")] works fine including inheritance ...
Thanks, Art
Related
One of the overload methods of SelectList (from the Microsoft.AspNetCore.Mvc.Rendering namespace) is defined as:
public SelectList(IEnumerable items, string dataValueField, string dataTextField);
When I scaffold an "MVC Controller with view, using Entity Framework" and I create my CRUD pages, I may see the following method inside of my Controller:
public IActionResult Create()
{
ViewData["Continent"] = new SelectList(_context.Continent, **"ContinentID", "ContinentID"**);
ViewData["Country"] = new SelectList(_context.Country, **"CountryID", "CountryName"**);
return View();
}
The field supplied to the dataTextField parameter is different between Continent/Country. How does MVC/EntityFramework decide which field to supply to dataTextField when scaffolding a Controller? Is there something in the individual models or in the DbContext that I am overlooking? I'd like for the dataTextField of Continent to be "ContinentName" so that I don't have to change it manually in the future when I need to delete and then re-scaffold the Controller.
Edit:
Here are the model definitions:
The Model of the Controller that I posted above:
using System;
using System.Collections.Generic;
namespace Project.Models
{
public partial class ProjectForm
{
public int ProjectFormID { get; set; }
public int ContinentID { get; set; }
public int CountryID { get; set; }
public virtual Continent ContinentNavigation { get; set; }
public virtual Country CountryNavigation { get; set; }
}
}
The one that displays the "CountryName" in the dataTextField the way that I want to see it:
namespace Project.Models
{
public partial class Country
{
public int CountryID { get; set; }
public string CountryName { get; set; }
public virtual ICollection<ProjectForm> ProjectForm { get; set; }
}
}
The one that displays the "ContinentID" in the dataTextField the way that I do NOT want to see it:
namespace Project.Models
{
public partial class Continent
{
public int ContinentID { get; set; }
public string ContinentName { get; set; }
public virtual ICollection<ProjectForm> ProjectForm { get; set; }
}
}
There is nothing obviously different to me in the model definitions unfortunately.
I stumbled across this post today (a bit late), but see it still hasn't been answered.
While I can't say why the scaffolding chose to use one field over another in your scenarios (unless you initially had your class/model written differently the last time you cleaned/built your project), I can say how to force it to use a specific column.
Add the DisplayColumn attribute to your class. You will need to rebuild before scaffolding again for the change to take.
namespace Project.Models
{
[DisplayColumn("ContinentName")]
public partial class Continent
{
public int ContinentID { get; set; }
public string ContinentName { get; set; }
public virtual ICollection<ProjectForm> ProjectForm { get; set; }
}
}
I want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}
I am using Entity Framework 6 with Generic Repository and DTOs.
I want to create new entities via navigation property.
Here is my model:
public partial class Project
{
public Project()
{
this.ProjectAssets = new List<ProjectAsset>();
}
public int ProjectID { get; set; }
public string Name { get; set; }
public virtual ICollection<ProjectAsset> ProjectAssets { get; set; }
}
public partial class Asset
{
public Asset()
{
this.Revisions = new List<Revision>();
}
public int AssetID { get; set; }
public string Name { get; set; }
public short Type { get; set; }
public virtual ICollection<Revision> Revisions { get; set; }
}
public partial class ProjectAsset
{
public int MappingID { get; set; }
public int ProjectID { get; set; }
public int AssetID { get; set; }
public virtual Asset Asset { get; set; }
}
I have already created Project. And if i am creating new Asset, then create Project Asset with AssetID from just created Asset, it's OK, but i have to re-fetch Project from DB.
I want to do it in one transaction, like that:
Project.ProjectAssets.Add(new ProjectAsset(new Asset((short)type, fileName)));
ServiceLocator.Default.ResolveType<IPipeLine>().Update(Project);
public void Update<TEntity>(TEntity entity) where TEntity : class
{
var fqen = GetEntityName<TEntity>();
object originalItem;
var key = ((IObjectContextAdapter)DbContext).ObjectContext.CreateEntityKey(fqen, entity);
if (((IObjectContextAdapter)DbContext).ObjectContext.TryGetObjectByKey(key, out originalItem))
((IObjectContextAdapter)DbContext).ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
//DbContext.Entry(entity).State = EntityState.Modified;
}
But after SaveChanges there is no record in DB, and MappingID still 0.
I thought that ApplyCurrentValues must work with Navigation Properties.
Is there any good way to solve that problem?
EDIT:
I accessing DAL throughBusiness Entities with contain the same properties, but they also implement INotifyPropertyChanged and other WPF stuff. So i think i can subscribe to CollectionChanged event and manualy create/delete entities from navigation property. And in property setters i can call update, but i think it can strongly decrease perfomance.
I have build a .Net Mvc 4 application and now I want to extend it with REST.
I am using the Entity Framework and I have the following problem.
My goal is to have a system where categories have a number of products and where products can belong to multiple categories.
As follows:
public class Categorie
{
[Key]
public int Id { get; set; }
[Required]
public string Naam { get; set; }
[Required]
public string Omschrijving { get; set; }
public byte[] Plaatje { get; set; }
private List<Product> producten;
public virtual List<Product> Producten
{
get { return producten; }
set { producten = value; }
}
}
public class Product
{
[Key]
public int Id { get; set; }
[Required]
public string Naam { get; set; }
[Required]
public string Omschrijving { get; set; }
[Required]
public double Prijs { get; set; }
private List<Categorie> categorien = new List<Categorie>();
public virtual List<Categorie> Categorien
{
get { return categorien; }
set { categorien = value; }
}
[Required]
public byte[] Plaatje { get; set; }
}
NOTE: There are virtual properties in there so that my entity framework creates a merging table. Normally it links all the categorie's to the products and vice versa.
And my rest looks like:
// GET api/Rest/5
public Product GetProduct(int id)
{
Product product = db.Producten.Find(id);
Product newProduct = new Product();
if (product == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
else
{
product.Categorien = null;
}
newProduct.Id = product.Id;
newProduct.Naam = product.Naam;
newProduct.Omschrijving = product.Omschrijving;
newProduct.Plaatje = product.Plaatje;
newProduct.Prijs = product.Prijs;
newProduct.Categorien = product.Categorien;
return newProduct;
}
First problem: I cannot send any product aslong as it has a categorie. I have to make it null.
Second problem: I cannot send the original product because of the first problem.
I am assuming your problem is with a circular reference during serialization, since categories reference multiple products and products reference multiple categories. One solution is to use Data Transfer Objects (DTO) instead of returning the straight entities you are using for EF. To make it easy to map your entities to the DTO's I would use AutoMapper. This is essentially what you are doing when you create an instance of newProduct in your REST API method, but AutoMapper takes the hard coding and drudgery out of mapping. Your DTO for a product would look very similar but they would not have the virtual navigation properties or the attributes needed by EF. A DTO for a product would look something like this.
public class Categorie
{
public int Id { get; set; }
public string Naam { get; set; }
public string Omschrijving { get; set; }
public byte[] Plaatje { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Naam { get; set; }
public string Omschrijving { get; set; }
public double Prijs { get; set; }
public List<Categorie> categorien = new List<Categorie>();
public List<Categorie> Categorien
{
get { return categorien; }
set { categorien = value; }
}
public byte[] Plaatje { get; set; }
}
Notice that the DTO for Categorie does not contain a list of products, since in this case you want a listing of products. If you keep the field names the same for your DTO's as your entities AutoMapper will handle the mapping automatically. I usually keep the same class name for the DTO's and just distinguish them from the entities by having a different namespace. Your REST API method would look something like this.
// GET api/Rest/5
public Product GetProduct(int id)
{
Product product = db.Producten.Find(id);
return Mapper.Map<Product, Dto.Product>(product);
}
I am receiving this error:
The ViewData item that has the key 'DepartmentId' is of type 'System.Int32' but must be of type 'IEnumerable'.
with the following set up. I am not sure how to resolve it. The error is happening in the Model View code. This line: public void MapTo(Person domainModel). I am using AutoMapper to map ViewModel back to DomainModel (reversing the initial mapping of DomainModel to ViewModel).
Domain model (using LINQ to SQL, so this is a partial class):
public partial class Person { }
// Validation rules
public class Person_Validation
{
[HiddenInput(DisplayValue = false)]
[ScaffoldColumn(false)]
public object PersonId { get; set; }
[HiddenInput(DisplayValue = false)]
[ScaffoldColumn(false)]
public object DepartmentId { get; set; }
[DisplayName("Employee Name")]
[Required(ErrorMessage = "Employee Name is required")]
[StringLength(50, ErrorMessage = "Employee Name cannot be more than 50 characters")]
public object Name { get; set; }
[HiddenInput(DisplayValue = false)]
public object Active { get; set; }
[HiddenInput(DisplayValue = false)]
public object DateAdded { get; set; }
[HiddenInput(DisplayValue = false)]
public object DateDeleted { get; set; }
public object Department { get; set; }
}
This is my Model View:
public class PersonViewModel
{
public object PersonId { get; set; }
public object DepartmentId { get; set; }
public object Name { get; set; }
public object Active { get; set; }
public object DateAdded { get; set; }
public object DateDeleted { get; set; }
public object DepartmentName { get; set; }
//helper method
public void MapTo(Person domainModel)
{
Mapper.Map(this, domainModel);
}
}
Controller Class Code:
[HttpPost]
public ActionResult Edit(PersonViewModel viewModel)
{
var domainModel = new Person();
try
{
viewModel.MapTo(domainModel);
UpdateModel(domainModel);
_personRepository.Save();
return RedirectToAction("Index", "Person");
}
catch
{
return View(viewModel);
}
}
And my View HTML code:
<div class="editor-field">
<%: Html.DropDownList("DepartmentId", (IEnumerable<SelectListItem>)ViewData["DepartmentList"])%>
<%: Html.ValidationMessageFor(model => model.DepartmentId) %>
</div>
you're not really following the best practices of developing a mvc application.
About the error:
the Html.DropDownList looks for Data of type IEnumerable<SelectListItem> in the model but it finds an int instead (DepartmentId)
your ViewModel should not have the MapTo method, it breaks the single responsibility principle
in your action method you don't do any server side validation like:
if(!ModelState.IsValid)
{
//rebuild the viewmodel and return the view
}
catching everything in the action is also not necessary (and bad)
you do this in Global.asax Application_Error instead
attributes like HiddenInput, ScaffoldColumn, Validation and anything else UI related should be on you ViewModel not in your domain model
for a good sample of using viewmodels & validation & mapping between entity <-> viewmodel I recommend you to look at the Samples solution from here
I did this sample and it's main purpose is to demonstrate the usage of ValueInjecter (mapping technology)