Form is not binding to correct object - c#

I have similar objects (I'm using Model and ViewModels, where ViewModels has same properties as Model object).
When I'm editing one item, I put editors for viewmodel, and the controller that handles the update/edit, he receives the ViewModel:
[HttpPost]
public ActionResult Edit(DocumentViewModel model) {
Mapper.CreateMap < DocumentViewModel, BE.Document > ();
BE.Document instanceOfDestination = Mapper.Map < BE.Document > (model);
Container < BE.Document > container = BL.DocumentBL.UpdateDocument(instanceOfDestination);
if (!container.HasErrors) {
SetInfo("Saved!");
} else {
SetError(container.ErrorMessage);
}
return RedirectToAction("Index");
}
The problem is that this method is never reached because model binder constructs BE.Document instead of DocumentViewModel.
Here are the values sent by browser:
__RequestVerificationToken:xxx-dontcare
id:36
name:test flash files
documentType.id:5
unit.id:2
reference:FLASH0016
isActive:true
isActive:false
recyclingSpan:1
selectedTopics:1
selectedTopics:2
trainer.id:615952
selectedInstallations:1
selectedInstallations:2
selectedProfessions:3
selectedProfessions:4
selectedProfessions:6
Here is the Controller that return VM to make the edit page:
[HttpGet]
public ActionResult Edit(int id) {
var container = BL.DocumentBL.GetAllDocument(new BE.Document() {
id = id
});
if (!container.HasErrors) {
Mapper.CreateMap < BE.Document, DocumentViewModel > ();
DocumentViewModel instanceOfDestination = Mapper.Map < DocumentViewModel > (container.Value);
// fill values for dropdowns and co
instanceOfDestination.FillPredefinedValuesForUser(GetAdminOrVisibilityUnits());
return View(instanceOfDestination);
} else {
SetError(container.ErrorMessage);
return RedirectToAction("Index");
}
}
And there are model and viewmodels for document:
DocumentViewModel:
public int id { get; set; }
[Required]
public string name { get; set; }
[Required]
public string reference { get; set; }
[Required]
[Range(0, 100)]
public int recyclingSpan { get; set; }
[Required]
public bool isActive { get; set; }
[DocumentTypeValidator("DocType is required")] // custom validator
public DocumentType documentType { get; set; }
public PersonnelAM trainer { get; set; }
public List<DocumentVersion> versions { get; set; }
public List<Installation> installations { get; set; }
public List<Profession> professions { get; set; }
public List<Topic> topics { get; set; }
public Unit unit { get; set; }
// not used for edit or create
public PersonnelAM createdBy { get; set; }
public DateTime createdOn { get; set; }
public PersonnelAM editedBy { get; set; }
public DateTime editedOn { get; set; }
// to fill dropdowns
public IEnumerable<SelectListItem> documentTypeSelect { get; set; }
public IEnumerable<SelectListItem> personnelSelect { get; set; }
public IEnumerable<SelectListItem> installationsSelect { get; set; }
public IEnumerable<SelectListItem> professionsSelect { get; set; }
public IEnumerable<SelectListItem> topicTypeSelect { get; set; }
public IEnumerable<SelectListItem> unitSelect { get; set; }
// for multi-selects - uses FillListsFromIds to fill Lists from Ids
public int[] selectedProfessions { get; set; }
public int[] selectedInstallations { get; set; }
public int[] selectedTopics { get; set; }
// For file upload
[MinLengthAttribute(1)]
public HttpPostedFileBase[] files { get; set; }
// for file get
public List<string> filesList { get; set; }
BE.Document
public int id { get; set; }
public string name { get; set; }
public string reference { get; set; }
public int recyclingSpan { get; set; }
public bool isActive { get; set; }
public DocumentType documentType { get; set; }
public PersonnelAM trainer { get; set; }
public List<string> filesList { get; set; }
public List<Installation> installations { get; set; }
public List<DocumentVersion> versions { get; set; }
public List<Profession> professions { get; set; }
public List<Topic> topics { get; set; }
public Unit unit { get; set; }
public PersonnelAM createdBy { get; set; }
public DateTime createdOn { get; set; }
public PersonnelAM editedBy { get; set; }
public DateTime editedOn { get; set; }
Thanks to help me :-)
EDIT :
Here is the full Get/id Controller
[HttpGet]
public ActionResult Edit(int id)
{
if (User.IsInRole("Admin") || User.IsInRole("Moderator") || SessionManager.matricule.IsDocumentCreator(id))
{
var container = BL.DocumentBL.GetAllDocument(new BE.Document() { id = id });
if (!container.HasErrors)
{
Mapper.CreateMap<BE.Document, DocumentViewModel>();
DocumentViewModel instanceOfDestination = Mapper.Map<DocumentViewModel>(container.Value);
// fill values for dropdowns and co
instanceOfDestination.FillPredefinedValuesForUser(GetAdminOrVisibilityUnits());
return View(instanceOfDestination);
}
else
{
SetError(container.ErrorMessage);
return RedirectToAction("Index");
}
}
else
{
SetError("Vous n'avez pas le droit d'accéder à l'édition de ce document.");
return RedirectToAction("Index");
}
}
EDIT 2:
[HttpPost]
public ActionResult Edit(DocumentViewModel model)
{
if (User.IsInRole("Admin") || User.IsInRole("Moderator") || SessionManager.matricule.IsDocumentCreator(model.id))
{
Mapper.CreateMap<DocumentViewModel, BE.Document>();
BE.Document instanceOfDestination = Mapper.Map<BE.Document>(model);
Container<BE.Document> container = BL.DocumentBL.UpdateDocument(instanceOfDestination, new PersonnelAM() { id = SessionManager.matricule });
if (!container.HasErrors)
{
SetInfo("Modifications suavegardées");
}
else
{
model.FillPredefinedValuesForUser(GetAdminOrVisibilityUnits());
SetError(container.ErrorMessage);
return View(instanceOfDestination);
}
}
return RedirectToAction("Index");
}

Your view model is binding correctly, but in your POST method, this line of code
return View(instanceOfDestination);
is returning an instance of Document (as defined by BE.Document instanceOfDestination = Mapper.Map<BE.Document>(model);, not an instance of DocumentViewModel which is resulting in an exception
The model passed in the dictionary is of type BE.Document but this dictionary requires a model of type DocumentViewModel
Change it to
return View(model);
so that the correct type is passed back to the view.

Please make sure you bind the viewmodel to your view (your cshtml file), not your model, on top of your View file (for instance edit.cshtml) :
#model your.namespace.DocumentViewModel
instead of
#model your.namespace.BE.Document
But, why you use viewmodel if the viewmodel is identical to your model? why not just use your model instead

Related

MVC - Unable to set field/property on entity

I have 2 tables in my entity framework:
INATIVOS (Employees)
EMPRESAS (Companies)
When registering an employee I select a company in a #Html.DropDownListFor (List).
The registration is ok, the company is saved correctly. However, when trying to edit a registered employee shows the error "Unable to set field/property on entity" in the Companies list.
INATIVO.cs
public partial class INATIVOS
{
public decimal ID { get; set; }
public string COD_EMPRESA { get; set; }
public string CHAPA { get; set; }
public string NOME { get; set; }
public System.DateTime DATA_NASC { get; set; }
public string PLANO { get; set; }
public short LEI { get; set; }
public short APOSENTADO { get; set; }
public short ESTADO_VIDA { get; set; }
public short ISENTO { get; set; }
public Nullable<System.DateTime> INICIO_VIGENCIA { get; set; }
public Nullable<System.DateTime> FIM_VIGENCIA { get; set; }
public string CPF { get; set; }
public string EMAIL { get; set; }
public string ENDERECO { get; set; }
public string NUMERO { get; set; }
public string COMPLEMENTO { get; set; }
public string BAIRRO { get; set; }
public string CIDADE { get; set; }
public string ESTADO { get; set; }
public string CEP { get; set; }
public string TELEFONE { get; set; }
public string CELULAR { get; set; }
public string OBSERVACAO { get; set; }
public List<DEPENDENTES> DEPENDENTES { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
}
InativoController.cs
public ActionResult Index(int? id)
{
INATIVOS inaModel = new INATIVOS();
using (Entidades db = new Entidades())
{
if (id != null)
{
inaModel = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
inaModel.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
inaModel.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(inaModel);
}
If these are navigation properties:
public List<DEPENDENTES> DEPENDENTES { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
Then (1) they need to be virtual and (2) they need to be something like IList or ICollection:
public virtual ICollection<DEPENDENTES> DEPENDENTES { get; set; }
public virtual ICollection<EMPRESAS> EMPRESAS { get; set; }
public virtual ICollection<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
Though, as an aside, what you're doing here is very strange:
inaModel.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
inaModel.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
Essentially what you have in the database is, for a given Employee (INATIVOS) there are relationships to specific Companies (EMPRESAS) and specific Medical Plans (PLANOS_MEDICO). But you're ignoring whatever is in that data and replacing it with all companies and all medical plans in the entire database.
So every time you use this controller action to fetch an existing employee record, it's going to look like that employee has every company and every medical plan. Even though that's not what's in the database. I strongly suspect that's not what you want.
UPDATE: Based on comments on this answer, it sounds like those aren't navigation properties. They're not even properties of the model at all. They're just lists of data needed for the view to populate (presumably) <select> elements.
If they're not part of the data model then remove them from the model. Instead, consider using a view model. For example:
public class InativosViewModel
{
public INATIVOS Inativos { get; set; }
public List<EMPRESAS> EMPRESAS { get; set; }
public List<PLANOS_MEDICO> PLANOS_MEDICO { get; set; }
}
Then in the controller return an instance of the view model, which is a composite object of the model and the data needed for the view:
public ActionResult Index(int? id)
{
InativosViewModel result = new InativosViewModel();
using (Entidades db = new Entidades())
{
if (id != null)
{
result.Inativos = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
result.EMPRESAS = db.EMPRESAS.ToList<EMPRESAS>();
result.PLANOS_MEDICO = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(result);
}
And of course change the model binding in the view itself to now expect and use an instance of InativosViewModel. The resulting POST action can still accept an instance of INATIVOS if it needs to, or it can accept an instance of InativosViewModel just as well. That all depends on what the form structure is and what's being posted to that action.
Alternatively, if you want to keep using the INATIVOS model then still remove those lists from it but use something like ViewBag to send them to the view. Something like this:
public ActionResult Index(int? id)
{
INATIVOS inaModel = new INATIVOS();
using (Entidades db = new Entidades())
{
if (id != null)
{
inaModel = db.INATIVOS.Where(x => x.ID == id).FirstOrDefault();
}
ViewBag.Empresas = db.EMPRESAS.ToList<EMPRESAS>();
ViewBag.PlanosMedico = db.PLANOS_MEDICO.ToList<PLANOS_MEDICO>();
}
return View(inaModel);
}
Then in your view you would populate the <select> elements from there:
#Html.DropDownListFor(
model => Model.COD_EMPRESA,
new SelectList(ViewBag.Empresas, "CODIGO", "DESCRICAO"),
htmlAttributes: new { #class = "form-control"
})

How to validate IList foreign key? Entity Framework

Please help solve my problem. I want get message if textbox TagsSites is empty.
My models:
Site:
public int Id { get; set; }
[Required]
public string UserId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string TypeMenuId { get; set; }
public virtual IList<Page> Pages { get; set; }
[Required]
public virtual IList<TagSite> TagsSites { get; set; }
public virtual TypeMenu TypeMenu { get; set; }
public virtual ApplicationUser User { get; set; }
Tag:
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<TagSite> TagsSites { get; set; }
TagSite:
public int Id { get; set; }
public int SiteId { get; set; }
public int TagId { get; set; }
public virtual Site Site { get; set; }
public virtual Tag Tag { get; set; }
I now get this message for all empty inputs.
How to get message "The TagsSites field is required." ?
Thanks.
What you may want here is the MinLengthAttribute. Implementation looks something like this.
[Required]
[MinLength (1)]
public virtual IList <TagSite> TagSites { get; set; }
You should create a view model for your view with a property for a comma separated tag names and mark it with the Required attribute.
public class CreateSiteVm
{
[Required]
public string Name { set;get;}
[Required]
public string Description { set;get;}
[Required]
public string Tags { set;get;}
[Required]
public int TypeMenuId { set;get;}
public List<SelectListItem> TypeMenus { set;get;}
}
and in your GET action
public ActionResult Create()
{
var vm = new CreateSiteVm();
vm.TypeMenus = dbContext.TypeMenus.Select(x=> new SelectListItem {
Value=x.Id.ToString(),
Text=x.Name}).ToList();
return View(vm);
}
and in your view,
#model CreateSiteVm
#using(Html.BeginForm())
{
<p>#Html.ValidationSummary(false)</p>
<label>Name</label>
#Html.TextBoxFor(f=>f.Name)
<label>Descsription</label>
#Html.TextBoxFor(f=>f.Descsription)
<label>Tags</label>
#Html.TextBoxFor(f=>f.Tags)
<input type="submit" />
}
and in your HttpPost action method, create an object of your entity and set the values from view model object which is your method parameter. You can use Split method to split the comma separated string.
[HttpPost]
public ActionResult Create(CreateSiteVm model)
{
if(ModelState.IsValid)
{
var e=new Site { Name = model.Name, Description = model.Description};
e.TypeMenuId = model.TypeMenuId;
var arr = model.Tags.Split(',');
foreach (var s in arr)
{
e.Tags.Add(new Tag { Name = s});
}
dbContext.Sites.Add(e);
dbContext.SaveChanges();
return RedirectToAction("Index");
}
//to do : Load the dropdown again same as GET
return View(model);
}

ViewModel save selected value from dropdownlist

I have a dropdownlist - difficult. This is a dropdonwlinst where the user can select a value(easy, hard) but the value is not saved to the database, if I select a value. The strange thing I also have Country list, and that value is saved to the database.And If I put the values by hand in the database - for difficult the values are shown in the view
ModelVIew:
public RouteViewModel()
{
Countries = new List<SelectListItem>();
DifficultGrades = new List<SelectListItem>();
}
public int Id { get; set; }
[Required(ErrorMessage = "You need to give it a name")]
public string Name { get; set; }
public int SelectedValue { get; set; }
public int SelectedId { get; set; }
public IEnumerable<SelectListItem> Countries { get; set; }
public IEnumerable<SelectListItem> DifficultGrades { get; set; }
}
public class Difficult
{
[Key]
public int DifficultId { get; set; }
public string DifficultName { get; set; }
public virtual ICollection<Route> Routes { get; set; }
}
public class Route
{
[Key]
public int routeID { get; set; }
public string Name { get; set; }
public int? UserProfileID { get; set; }
public int? CountryID { get; set; }
public int? DifficultGradeID { get; set; }
public virtual UserProfile userProfile { get; set; }
public virtual Country country { get; set; }
public virtual Difficult difficult { get; set; }
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(RouteViewModel routeViewModel )
{
try
{
if (ModelState.IsValid)
{
var route = new Route();
UpdateRoute(route, routeViewModel);
db.Routes.Add(route);
db.SaveChangesAsync();
return RedirectToAction("Index");
}
}
catch (RetryLimitExceededException /* dex */)
{
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
ViewBag.Id = new SelectList(db.countries, "Id", "country_name", routeViewModel.Id);
ViewBag.DifficultId = new SelectList(db.difficulties, "DifficultId", "DifficultName", routeViewModel.Id);
return View(new RouteViewModel());
}
and the update method:
public void UpdateRoute(Route route, RouteViewModel routeViewModel )
{
route.routeID = routeViewModel.Id;
route.Name = routeViewModel.Name;
route.CountryID = routeViewModel.Id;
route.DifficultGradeID = routeViewModel.Id;
// climb.country.country_name = ModelViewClimb.Name;
}
Thank you

Binding data of Domain model type to ViewModel type

I'm having a hard time trying to figure out how to bind data from the database to ViewModel. Basically, I have a domain model which I find has too much properties and which I'd like to reduce so logically I've chosen ViewModel to do so.
Domain model (automatically created from the database):
public partial class Ticket
{
public Ticket()
{
this.Daily = new HashSet<Daily>();
this.Ticket1 = new HashSet<Ticket>();
}
public int idTicket { get; set; }
public Nullable<int> idNadredeniTicket { get; set; }
public short RedniBroj { get; set; }
public int idFirma { get; set; }
public Nullable<int> idKontakt { get; set; }
public Nullable<int> idManager { get; set; }
public string Tip { get; set; }
public string Status { get; set; }
public Nullable<System.DateTime> DatumPrijave { get; set; }
public string VrstaPrijave { get; set; }
public string Prioritet { get; set; }
public Nullable<System.DateTime> DatumDo { get; set; }
public string Opis { get; set; }
public string Biljeske { get; set; }
public Nullable<bool> Zatvoren { get; set; }
public Nullable<bool> IzdanRacun { get; set; }
public Nullable<System.DateTime> DatumZatvaranja { get; set; }
public Nullable<int> idAsset { get; set; }
public virtual ICollection<Daily> Daily { get; set; }
public virtual Firma Firma { get; set; }
public virtual Kontakt Kontakt { get; set; }
public virtual Kontakt Kontakt1 { get; set; }
public virtual ICollection<Ticket> Ticket1 { get; set; }
public virtual Ticket Ticket2 { get; set; }
}
ViewModel:
public class OpenTickets
{
public int idTicket { get; set; }
public Nullable<int> idNadredeniTicket { get; set; }
public short RedniBroj { get; set; }
public int idFirma { get; set; }
public Nullable<int> idKontakt { get; set; }
public Nullable<int> idManager { get; set; }
public string Tip { get; set; }
public string Status { get; set; }
public Nullable<System.DateTime> DatumPrijave { get; set; }
public string VrstaPrijave { get; set; }
public string Prioritet { get; set; }
public string Opis { get; set; }
public string Biljeske { get; set; }
public string BrojTicketa
{
get
{
return idNadredeniTicket.ToString() + "-" + RedniBroj.ToString();
}
}
public string NazivTicketa
{
get
{
return BrojTicketa + " - " + Opis;
}
}
public string DetaljiTicketa
{
get
{
return Opis + "\r\n" + Biljeske;
}
}
}
What I'd like to accomplish is to bind data from the database via query to the ViewModel but, understandingly, I get errors regarding different types of objects passed to the View. I'm posting controller and view for the reference.
Controller
public ActionResult OpenTickets()
{
var openTickets = db.Ticket
.Where(t => t.idFirma == 1)
.Where(t => t.Zatvoren == false);
return View(openTickets.ToList());
}
View (some code is intentionally ommited for brevity)
#model IEnumerable<IDE3_CRM.ViewModels.OpenTickets>
<table>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.Biljeske)</td>
<td>#Html.DisplayFor(modelItem => item.Opis)</td>
</tr>
}
</table>
I recommend wrapping your db calls in a Repository. In here, you can transform your database objects into view models. For example:
public ActionResult OpenTickets()
{
var openTickets = ticketRepo.GetOpenTickets();
return View(openTickets);
}
// Implementation of ITicketRepo
public IEnumerable<OpenTickets> GetOpenTickets()
{
return db.Ticket
.Where(t => t.idFirma == 1 && t.Zatvoren == false)
.Select(do => new OpenTickets
{
// Fill in view model properties from database object
});
}
Hrvach,
You can limit the data fields in the view itself and I think that maybe more efficient. That being said here is another approach you can take:
Create a list of type OpenTickets
Select the tickets you want
Loop over the selected tickets and add a new openTicket with the
properites you want to keep to the list of openTickets
return the list of open tickets
public ActionResult OpenTickets()
{
List<OpenTickets> openTicketList = new List<OpenTickets>();//create a list of openTickets
var Tickets = db.Ticket//select the tickets that you want
.Where(t => t.idFirma == 1)
.Where(t => t.Zatvoren == false);
foreach (var ticket in Tickets)//Loop over the tickets and create an openTicket out of each ticket then add the openTick to the openTicketList
{
OpenTickets openTicket = new OpenTickets();//create new OpenTickets object
openTicket.propery1 = ticket.propery1;//set each property of the openTicket equal to the property of the Ticket that you want to keep
openTicket.propery2 = ticket.propery2;
openTicket.propery3 = ticket.propery3;
openTicket.propery4 = ticket.propery4;
openTicketList.Add(openTicket);//add new OpenTickets object to the list
}
return View(openTicketList);
}
I hope this helps...Best wishes
Bill

BindAttribute, Exclude nested properties for complex types

How can I BindAttribute Include or Exclude nested properties in my controller?
I have a 'Stream' model:
public class Stream
{
public int ID { get; set; }
[Required]
[StringLength(50, ErrorMessage = "Stream name cannot be longer than 50 characters.")]
public string Name { get; set; }
[Required]
[DataType(DataType.Url)]
public string URL { get; set; }
[Required]
[Display(Name="Service")]
public int ServiceID { get; set; }
public virtual Service Service { get; set; }
public virtual ICollection<Event> Events { get; set; }
public virtual ICollection<Monitor> Monitors { get; set; }
public virtual ICollection<AlertRule> AlertRules { get; set; }
}
For the 'create' view for this model, I have made a view model to pass some additional information to the view:
public class StreamCreateVM
{
public Stream Stream { get; set; }
public SelectList ServicesList { get; set; }
public int SelectedService { get; set; }
}
Here is my create post action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Stream, Stream.Name, Stream.ServiceID, SelectedService")] StreamCreateVM viewModel)
{
if (ModelState.IsValid)
{
db.Streams.Add(viewModel.Stream);
db.SaveChanges();
return RedirectToAction("Index", "Service", new { id = viewModel.Stream.ServiceID });
}
return View(viewModel);
}
Now, this all works, apart from the [Bind(Include="Stream, Stream.Name, Stream.ServiceID, SelectedService")] bit. I can't seem to Include or Exclude properties within a nested object.
According to this answer, you could do something like:
[Bind(Include="Name, ServiceID")]
public class Stream
{
...
}
and then
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Stream, SelectedService")] StreamCreateVM viewModel)
{
...
}

Categories