how and where to connect viewmodel to dbcontext - asp.net

I inherited a mvc app. This app uses entity framework with database first. It was made with no viewmodels and viewbags everywhere for the dropdowns and error messages. Now I am tasked with making many changes to it because you can not validate the related properties that are not in the main class among other things.
I am trying to create a viewmodel so I can display only necessary data, validate it and not be linked directly to the model. So far I get null for all my fields on a form using the viewmodel I created. I have tried to use automapper but get a mapping error: "Missing type map configuration or unsupported mapping"
Here is part of the controller:
public ActionResult ChangeOwner(int id = 0)
{
var combine = new combineValidationAssetViewModel();
Mapper.CreateMap<ToolingAppEntities1, combineValidationAssetViewModel>();
Mapper.CreateMap<combineValidationAssetViewModel, ToolingAppEntities1>();
Asset asset = db.Assets.Find(id);
Mapper.Map(combine, asset, typeof(combineValidationAssetViewModel), typeof(Asset));
.....
return View(combine);
}
Here is part of the view model:
public class combineValidationAssetViewModel
{
public Asset Assets { get; set; }
public Transaction Transactions { get; set; }
public LocationType LocationTypes { get; set; }
public ToolType ToolTypes { get; set; }
public OwnerType OwnerTypes { get; set; }
public int AssetId { get; set; }
public int fkToolTypeId { get; set; }
[Required]
[Display(Name = "Owner")]
public int fkOwnerId { get; set; }
[Required]
[Display(Name = "Location")]
public int fkLocationId { get; set; }
public int LocationTypeId { get; set; }
public int OwnerTypeId { get; set; }
Here is part of the view:
#model ToolApp.ViewModels.combineValidationAssetViewModel
.....
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Asset</legend>
#Html.HiddenFor(model => model.AssetId)
#Html.HiddenFor(model => model.CreatedByUser)
#Html.HiddenFor(model => model.CreateDate)
#Html.HiddenFor(model => model.SerialNumber)
#Html.HiddenFor(model => model.LocationTypeId)
<div class="editor-label">
#Html.LabelFor(model =>model.SerialNumber)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.SerialNumber)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.fkToolTypeId, "Tool Name")
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.fkOwnerId, "New Owner")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.fkOwnerId, new SelectList(ViewBag.fkOwnerId, "Value", "Text"), new{style="width:320px;height:25px;"})
#Html.ValidationMessageFor(model => model.fkOwnerId),
The form displays but it is null (no values in any of the fields displayed. I would like to map it manually so I understand it. Have tried the automapper but it's not working yet. I have tried some ideas from here and other websites but same result. I don't completely understand the linq to ef yet so my problem may lie there also.
This main controller has 10 different action results on it and is filled with data calls and viewbags. I'm looking for advice on the direction I should go. I need to get the thing working but also want to make changes to it that will move it in the direction of a viable mvc app. Main issue at the moment is how to connect the viewmodel with the dbcontext. I found the context at the top of the controller like this:
{ private ToolingAppEntities1 db = new ToolingAppEntities1();
followed by many includes...
any suggestions would be appreciated

You map into the wrong direction:
Mapper.Map(combine, asset,
typeof(combineValidationAssetViewModel), typeof(Asset));
This maps the empty combine object to asset. You should reverse it, and use a strong-typed (generic) overload:
var combine = Mapper.Map<combineValidationAssetViewModel>(asset);

Related

Kendo DropDownListFor not s ending info to controller

This is baffling. In one of my MVC models, Project, I have two properties which both save the IDs for other models, like so.
public class Project
{
...
public int PlatformID { get; set; }
public int DepartmentID { get; set; }
...
}
I use a custom editor in which I edit these fields with a kendo dropdown, using their respective names and IDs as text and value fields.
<div class="col-md-11 details-item">
#Html.LabelFor(m => m.PlatformID, new { #class = "col-md-4 details-label" })
#(Html.Kendo().DropDownListFor(m => m.PlatformID)
.Name("PlatformID")
.DataValueField("ID")
.DataTextField("PlatformName")
.BindTo((System.Collections.IEnumerable)ViewData["Platforms"])
.HtmlAttributes(new { #class = "col-md-7 details-editor" })
)
</div>
<div class="col-md-11 details-item">
#Html.LabelFor(m => m.DepartmentID, new { #class = "col-md-4 details-label" })
#(Html.Kendo().DropDownListFor(m => m.DepartmentID)
.Name("DepartmentID")
.DataValueField("ID")
.DataTextField("Name")
.BindTo((System.Collections.IEnumerable)ViewData["Departments"])
.HtmlAttributes(new { #class = "col-md-7 details-editor" })
)
</div>
What's crazy is that PlatformID saves perfectly, but DepartmentID does not even get sent to the controller. If DepartmentID is the only thing I change while editing, it does not even register a change has been made. Otherwise, it gets sent as default 0.
The Department class is insanely simple.
public class Department
{
[Key]
public int ID { get; set; }
[DisplayName("Department Name")]
public string Name { get; set; }
}
PlatformID is essentially identical. Maybe I'm missing something stupid, or maybe there is evil afoot. Thanks for any help you can provide!
Figured out the answer - turns out having a DropDown in an editor template does not work if the item is nullable! So I just changed public int? DepartmentIDto public int DepartmentIDin my Project model, and life was good again.

DbContext.SaveChanges

I have an issue where inserting one record to the entity ProfessionalEmploymentRecord EF ends up duplicating existing Professional, Program, Role records in each entitiy, respectively. I have tried to explicitely initilize Professional, Program, Role with existing key/value pairs in my database, but EF still creates duplicate values with new keys.
public class ProfessionalEmploymentRecord
{
public int ProfessionalEmploymentRecordId {get; set; }
public virtual Professional Professional { get; set; }
public virtual Program Program { get; set; }
public virtual Role Role { get; set; }
}
public class Program
{
public int ProgramId { get; set; }
[MaxLength(20)]
public string Name { get; set; }
}
public class Role
{
public int RoleId { get; set; }
[MaxLength(50)]
public string Name { get; set; }
}
public class Professional
{
public int ProfessionalId { get; set; }
[MaxLength(20)]
public string Name { get; set; }
}
When using Create below I end up with new records in the three related entities although they exist and have a key set. In other words I end up with copies of values with new primary keys (incremented by one) in the three related entities.
public ActionResult Create(ProfessionalEmploymentRecord professionalemploymentrecord)
{
if (ModelState.IsValid)
{
db.ProfessionalEmploymentRecords.Add(professionalemploymentrecord);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(professionalemploymentrecord);
}
As requested
#model My.Models.ProfessionalEmploymentRecord
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend> Professional Employment Record</legend>
<div class="editor-label">
Professional
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Professional.Name)
#Html.ValidationMessageFor(model => model.Professional.Name)
</div>
<div class="editor-label">
Program
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Program.Name)
#Html.ValidationMessageFor(model => model.Program.Name)
</div>
<div class="editor-label">
Role
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Role.Name)
#Html.ValidationMessageFor(model => model.Role.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I have used code below to test. (I see that executing SaveChanges sets *Id++ in reco).
public void TestDummyAdd()
{
Professional prof = new Professional { ProfessionalId = 1, Name = "Chris" };
Program prog = new Program { ProgramId = 1, Name = "A" };
Role role = new Role { RoleId = 1, Name = "B" };
ProfessionalEmploymentRecord reco = new ProfessionalEmploymentRecord { Professional = prof, Role = role, Program = prog, ProfessionalEmploymentRecordId = 0 };
if (ModelState.IsValid)
{
db.ProfessionalEmploymentRecords.Add(reco);
db.SaveChanges();
}
}
Add is operation executed on whole object graph. If you add your record you are also adding all its relations, their relations, etc. There are two options how to solve this issue and both require you to manually set state of some entities (and sometimes also relations) in your persisted object grap:
Call Add as you did but after that you must set state for all existing entities in the graph to either Unchanged or Modified (entity will be udpated in database)
Call Attach instead of Add and after that set state for all new entities to Added
Settings state can be done by:
db.Entry(entity).State = EntityState.Added;

MVC metatdataprovider and html helpers

I have my own implementation of the metadataprovider, in it a check for my custom attribute and get the metadata from the database.
public class EntityPropertyMetadataAttribute: Attribute
{
[MaxLength(256)]
public string EntityFullName { get; set; }
[MaxLength(64)]
public string PropertyName { get; set; }
public string DisplayName { get; set; }
public string DisplayFormatString { get; set; }
public string EditFormatString { get; set; }
public object DefaultValue { get; set; }
}
Now I observed the following if I have a View like this:
<div class="editor-label">
#Html.LabelFor(model => model.Id)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Id)
#Html.ValidationMessageFor(model => model.Id)
</div>
The function
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
gets called 3 times for property Id, if I remove one for example:
<div class="editor-field">
#Html.EditorFor(model => model.Id)
#Html.ValidationMessageFor(model => model.Id)
</div>
it gets called 2 times.
Now when I use this:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.EditorForModel(Model)
<p>
<input type="submit" value="Create" />
</p>
}
CreateMetadata gets called a whopping 22 for each property in the model.
That's not really what you want performance wise. Probably I should hook up the DB code (currently inside CreateMetadata method) somewhere else.
any suggestion appreciated.
cheers
Ok finally on the right track with a bit of help from MVC extensions
http://mvcextensions.codeplex.com/SourceControl/changeset/view/f71bcadf0e76#Trunk%2fMvcExtensions%2fModelMetadata%2fModelMetadataRegistry.cs
In my case I shouldn't override
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType,
Func<object> modelAccessor, Type modelType, string propertyName)
from DataAnnotationsModelMetadataProvider but override the Get methods from AssociatedMetadataProvider like this one:
public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
And put hte database calls there.

Asp.net MVC 3 "parameter conversion from type 'System.String' to type failed" when using SelectList Dropdown box

I'm stuck and after looking this up for hours, I think I need more eyeballs.
The situation is the following:
It's an Asp.Net MVC3 with Entity Framework 4 project. And I have two classes. One ConfigurationFile and another one Action. There is a one-to-many relationship between the two. Here is a simplified view on the code:
public class ConfigurationFile
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Column(TypeName = "uniqueidentifier")]
[Required]
public Guid ActionId { get; set; }
public virtual Models.Action Action { get; set; }
}
public class Action
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string ActionValue { get; set; }
}
Then I want to create a new ConfigurationFile, and are my two controller methods (and at this point, this is 95% Visual Studio 10 generated code):
// db is my context class.
//
// GET: /Configuration/Create
public ActionResult Create()
{
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue");
return View();
}
//
// POST: /Configuration/Create
[HttpPost]
public ActionResult Create(Models.ConfigurationFile configurationfile)
{
if (ModelState.IsValid)
{
configurationfile.Id = Guid.NewGuid();
db.ConfigurationFiles.Add(configurationfile);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue", configurationfile.ActionId);
return View(configurationfile);
}
And here is a snippet of my Create view:
#model MyProject.Areas.ConfigurationFile.Models.ConfigurationFile
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Configuration File</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ActionId, "Action")
</div>
<div class="editor-field">
#Html.DropDownList("ActionId", String.Empty)
#Html.ValidationMessageFor(model => model.ActionId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I open the Create page, I can clearly see that my dropdown for the Action class is fine (correct value -- the Action.Id -- and text -- Action.ActionValue -- ) but when I submit the form, I have the following error: "The parameter conversion from type 'System.String' to type 'MyProject.Models.Action' failed because no type converter can convert between these types."
Help please !!
Right now MVC has no way of connecting your dropdownlist from your view to the ActionId of your ConfigurationFile object.
I would try replacing this line:
#Html.DropDownList("ActionId", String.Empty)
for this
#Html.DropDownListFor(model => model.ActionId, ViewBag.ActionId)
Other than that, I can't think of what else you might have done wrong.
I hope that helps!
This is how I did to circumvent the problem. I just changed my controller this way:
Models.Action act = db.Actions.Find(configurationfile.ActionId);
ModelState.Clear();
configurationfile.Action = act;
TryValidateModel(configurationfile);
And after that, the validation was Ok. A bit hacky (and another possible hit on the DB), but at least, I can keep going.

ASP.NET MVC 3 Remote validation stopped working

I'm using the client side validation for my views, and have just created a ViewModel which contains an organisation object and an address object.
I used to have a ViewModel that just mapped to the domain entity. On my domain entity I had the following:
[NotMapped]
[Remote("ValidOrganisation", "Manage", "Organisations", ErrorMessage = "That organisation doesn't exist")]
public string Organisation { get; set; }
However, I have now created a ViewModel for the view that contains the following:
public class PersonModel
{
public Person Person { get; set; }
public AddressModel Address { get; set; }
}
The person object contains the Organisation property.
In my view, I have the following:
<div>
<label for="Organisation">Organisation</label>
<div class="input large">
#Html.TextBoxFor(m => m.Person.Organisation)
<span class="help-block">Type the first letter of the organisation to search</span>
#Html.ValidationMessageFor(m => m.Person.Organisation)
#Html.Hidden("OrganisationID")
</div>
</div>
The only thing that changed was:
#Html.TextBoxFor(m => m.Organisation)
to:
#Html.TextBoxFor(m => m.Person.Organisation)
My remote validation code is:
public JsonResult ValidOrganisation(string organisation)
{
var exists = orgs.SelectAll().Where(o => o.Name.ToLower() == organisation.ToLower()).Count() > 0;
return Json(exists, JsonRequestBehavior.AllowGet);
}
The problem is that NULL is now always being passed in, which is always returning false.
Is this something to do with the Organisation property now changing to be Person.Organisation?
1] Open your view Page Source and see what is Id renderd for the
#Html.TextBoxFor(m => m.Person.Organisation)
2] and rename organisation in method to accept same ID I am expecting it to be Person_Organisation
public JsonResult ValidOrganisation(string organisation)
use below
public JsonResult ValidOrganisation(string Person_Organisation)

Resources