Separate models for create and edit in .net mvc - asp.net-mvc

I am working on a basic CRUD .net MVC application, with a requirement to validate the same data differently depending on if it is being created or edited.
As per the answer to the following question, it seems a different model for create and edit is required.
I have tried adding a class to the current model, with the following error:
"Multiple object sets per type are not supported. The object sets
'ReportDetails' and 'ReportDetailsEdit' can both contain instances
of type 'Test.Models.ReportDetails'."
I have also tried creating a new model, with the following error:
The namespace 'Test.Models' already contains a definition for 'TestDBContext'
What is the correct way to achieve this?
Here is a current basic outline of the model and controller:
Model:
public class ReportDetails {
[Key]
public int ReportID { get; set; }
[Required]
public string Email { get; set; }
[NotMapped]
[Compare("Email", ErrorMessage = "Confirmation field must match")]
public string ConfirmEmail { get; set; }
//Further fields here
}
Controller:
[HttpPost]
public ActionResult Edit(ReportDetails reportdetails) {
if (ModelState.IsValid) {
db.Entry(reportdetails).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(reportdetails);
}
The model is currently used for both create and edit, although I do not want the confirm email field included in addition to different validation rules for further fields.

Related

Getting an EntityType has no defined key error when trying to create a view from a MVC ViewModel

I'm working on an ASP.NET MVC 5 project in which I'm trying to build a controller from a MVC View Model. That ViewModel brings together 6 tables which I need to show in the views. Its my understanding that using MVC ViewModels is one way of showing multiple tables in a view. Anyway, I'm getting the following error message:
Error
There was an error running the selected code generator: 'Unable to retrieve metadata for
'PrismSmallTasks.ViewModels.ManageInterviewVM'. One of more validation errors were
detected during model generation:
ManageInterviewVM:: EntityType 'ManageInterviewVM' has no key defined.
Define the key for this EntityType.
ManageInterviewVMs: EntityType: EntitySet 'ManageInterviewsVMs' is based on
type 'ManageInterviewVM' that has no keys defined.
There's no key in the ManageInterviewVM ViewModel. That's because it's comprised of lists of the tables represented in the models that are in the VM. And each of those model classes do have a column that has a key defined for it.
For example, here's ManageInterviewVM:
public class ManageInterviewVM
{
public List<FieldRecord> FieldRecords { get; set; }
public List<TaskList> TaskLists { get; set; }
public List<InterviewARVTreatment> InterviewARVTreatments { get; set; }
public List<Note> Notes { get; set; }
public List<Risk> Risks { get; set; }
public List<Interview1> Interviews { get; set; }
}
And here's a partial listing of one of those tables as it is defined in the model class:
public partial class TaskList
{
[Key]
public int ID_TaskList { get; set; }
[Required]
[StringLength(15)]
public string CD_TaskListType { get; set; }
public int? ID_Profile { get; set; }
public int? ID_FieldRecord { get; set; }
public int? ID_Interview { get; set; }
So, I don't know what I'm missing. Why is this error showing up and how I can resolve it?
Your ViewModels should be completely dissociated from your Data Context (Data Access Layer). Only your domain models should deal with the DAL. ViewModels are only for displaying specific information to the view.
So after you create your ViewModel.. you try and create your view. When you arrive at this screen:
Type in your view name
Pick your template (if you leave it as 'Empty (without model)' then you should be able to just create it without any issue).
Once you pick a specific template and Model class (ViewModel), the 'Data context class' will auto-populate with your connection string (dbcontext), which is where your problem lies.
Since viewmodels are not supposed to be associated with the data access layer, you can just delete what is auto-populated in the 'Data context class' and then you should be able to create your view.
If you fall into the trap of thinking that you need to define keys for your viewmodel.. then your viewmodel class will be added to your connection string's class (dbcontext class).. which is a no-no.
You need to query the database using your domain models.. then you assign those values to your ViewModels properties that you want to display.
Hope this helps!

Use TryUpdateModel to bind partially supplied values

I have a scenario where I need to update an object from information that was posted to the action. As long as the information is in the page this works fine. However, it requires that I put information into hidden fields if I don't want the modelstate to complain.
As an example, lets say I am using the class below as the model:
public class Client
{
[Required]
public string Name { get; set; }
public string Email { get; set; }
public int Id { get; set; }
}
If I don't want the user to edit the name, I need to include it in a hidden field so that it get bound to the model and the validation passes.
The problem I have is that is obviously not secure if used with more sensitive information. So I tried this:
public virtual ActionResult Save(Client model, int clientId)
{
var client = datasource.LoadEntity(adapter, clientId); // clientId is passed as a querystring to the action
if (!TryUpdateModel(client))
return new RedirectResult('The edit page URL');
}
The problem is that the modelstate still complains about the "Name" value not being available even though it was loaded to the client object from the database.
Obviously I am doing something wrong but I can't figure out what.
The view model is just for information coming from the client.
So you have to remove the Name property and get it from somewhere else.
If this is a view model also used by the administrator for example (who is able to enter/change the name) then the best would be a derived view model like this:
public class Client
{
public string Email { get; set; }
public int Id { get; set; }
}
public class ClientWithName : Client
{
[Required]
public string Name { get; set; }
}
You can use the overload TryUpdateModel(TModel, string\[\]); if my understanding is correct, this should allow to specify the property to include in the update, like this:
public virtual ActionResult Save(Client model, int clientId)
{
var client = datasource.LoadEntity(adapter, clientId);
if (!TryUpdateModel(client, new string[] { "Email" }))
return new RedirectResult('The edit page URL');
}
I never tried it though, can you let us know if it works as expected?
I've gone with the solution outlined here: Asp.net MVC 3 Validation exclude some field validation in TryUpdateModel
Essentially, it removes the validation from the Modelstate if those fields aren't present which works for me as those values are retrieved from the database.

mvc Controller Architecture and modelbinding

My User entity has numerous differing properties which define the User record. After the default scaffolded edit and create pages are created we are now trying to implement some regions to the pages so similar areas of the users profile can be edited and updated without posting back and refreshing the entire list of properties.
I was thinking of splitting the regions into separate partial views like below and then using #Ajax.BeginForm(
public partial class UserContact : UserBase
{
[DataType(DataType.EmailAddress)]
[StringLength(255)]
public string EmailAddress { get; set; }
[DataType(DataType.PhoneNumber)]
[StringLength(50)]
public string PhoneHome { get; set; }
...
}
public partial class UserAddress : UserBase
{
[StringLength(60)]
public string AddressLine1 { get; set; }
[StringLength(60)]
public string AddressLine2 { get; set; }
...
}
public partial class UserBase
{
[Key]
[Required(ErrorMessage = "User is required")]
public System.Guid UserId { get; set; }
}
just spotted the binding keyword and i was wondering which methods people use. I would imagine its not very efficient over the wire and both in terms of the necessary validation to post back an entire Usermodel each time so do people split the main model into seperate models, or is it possible (or even adviseable) with the bind parameter to specify only a subset of the properties?
In my opinion it is indeed advisable to split the model into multiple sub models, however you also need to split your actions into sub actions. Each action will be 'bound' to that sub class and not the entire UserBase class.
If you use only one action, I don't think it is possible to [dynamically] specify what properties to bind and which not.

Filter some property value in post data by ASP.NET MVC 3

I have a model like the followings:
public class MyModel {
[Required]
public string Name { get; set; }
public string Family { get; set; }
[Required]
public int Number { get; set; }
}
So for example in Edit View I have 3 Editorfor() objects and I am interesting to filter the post data of this page, actually I want to ignore Number field and just want to post Name and Family Also I need the validations of Number be active, One way is I remove Number property from MyModel and define in view by hand and write all validation script by own, but I am interesting to know is there any simpler way in MVC. Does anyone have any idea?
Controlling all that validation and model binding manually is way too complicated and error-prone. You should be using ViewModels
public class SomeSpecificViewModel
{
[Required]
public string Name { get; set; }
public string Family { get; set; }
}
public ActionResult SomeSpecificAction(SomeSpecificViewModel model)
{
//...
}
Now MVC wil validate only Name and Family
Any value not filled in the view will not be posted to the controller. However, if a field which is [Required] is not filled, then ViewModel.isValid will be false.

Validate a model in viewmodel?

I wonder if there is a way to validate just one of my models in the viewmodel send it to my action? I use the DataAnnotations as validate rules.
Like the if (!ModelState.IsValid)
let me know if the question is unclear and I will edit for a better explination
EDIT
my viewmodel looks like this
public class CompaniesViewModel
{
public Core.Model.Customer Company { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
public Core.Model.Meeting Meeting { get; set; }
}
What I want to do in this particular situation is to validate just Customer. I cant do the ModelState.IsValid then all get validated. So how can I do to just validate one of them like customer in this case. Hope this was more clear
There are a number of different ways you can do this. The first is to add a property called IsValid that checks the property. So something like:
public class Company
{
public bool IsValid
{
get { return GetValid() }
}
private bool IsValid()
{
if ( Some check here )
return false;
}
}
[HttpPost]
public ActionResult SomeAction(CompaniesViewModel model)
{
if (model.Company.IsValid)
{
}
}
However a better solution IMO would be just to post the Company to your controller rather than your entire view model. Just because your passing a view model to a view it doesn't mean that you need to post the entire view model back. When you create your HTML form specify only the properties you want to post back to your controller. So for example your controller would become:
[HttpPost]
public ActionResult SomeAction(Company company)
{
if (Model.IsValid)
{
}
}
Now when you check if Model.IsValid it just check company as that is all you've passed back to the controller.
At server side, you can try the ValidateModel(object) method, like in TryValidateModel(CompaniesViewModel.Company).
If you have enabled client sided validation, you need to post only the relevant entity. If you want to post all entities, but you need to validate only one, you can consider the following:
either removing the rules, using javascript ASP .NET MVC Disable Client Side Validation at Per-Field Level
or creating a Data-Transfer-Object, ie a View Model which has NO link to the Model, but reproduces the entities you want with the validation rules you want having applied in this scenario. Of course, then, you'll need in your controller or a model binder some way to bind from your ViewModel to your Model entities.
You can separate Customer Model to another class in your ViewModel and map that in Controller to a existing/new Customer:
public class CompaniesViewModel
{
public Company Company { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
public Core.Model.Meeting Meeting { get; set; }
}
//Validations for Company go here:
public class Company
{
public string CompanyId { get; set; }
[Required]
public string CompanyName { get; set; }
}

Resources