I have an order model (shown below)
public class Order
{
//[Key]
[ScaffoldColumn(false)]
public int OrderId { get; set; }
[DisplayName("Order Date")]
public DateTime OrderDate { get; set; }
public virtual ProductSelection ProductSelection { get; set; }
public virtual ShippingDetails ShippingDetails { get; set; }
public virtual BillingDetails BillingDetails { get; set; }
public virtual CardDetails CardDetails { get; set; }
public virtual AccountUser AccountUsers { get; set; }
}
As you can see is made up of a set of other models for example ProductSelection (shown below).
public class ProductSelection
{
public int SimulatorId { get; set; }
public string VersionNumber { get; set; }
[DisplayName("Quantity")]
public int Quantity { get; set; }
[DisplayName("Total Price")]
[ScaffoldColumn(false)]
public decimal TotalPrice { get; set; }
}
The issue I am having is when I post to the Controller which has a parameter of Order, I am unable to obtain any of the values from the sub-models (for example Order.ProductSelection.SimulatorId.)
Any ideas why this isn't working as I having to currently use FormCollection which isn't ideal and better messy.
Looking forward to replies
Steve
1) Silly question but just to make sure....Do you preserve values of your sub model on the view(In the form as hidden or any other input type,make sure name of your hidden are same as your properties name in the model) or in the query string.
Before giving you fully loaded model, model binder looks at different places to load your model like your form collection,rout data and query string
If you are not preserving them in any of these places then model binder has no way to find those values and give you loaded values on controller action.
Basics.. http://dotnetslackers.com/articles/aspnet/Understanding-ASP-NET-MVC-Model-Binding.aspx
2)Your example model seems fine but make sure all properties of your sub model have public access modifier and they must have set in their property declaration.
--->I had same issue before because I had private access modifier for set on those properties and I wasted whole day to figure that out.
3)If nothing works(hope that's not the case) then at last you can write your own model binder.
Here is the good post if you decide to head in that direction
http://buildstarted.com/2010/09/12/custom-model-binders-in-mvc-3-with-imodelbinder/
This is my first post (under my account) and it feels really good to participate..!!
You should apply ForeignAttribute on the ProductSelection property which points the primary key of the ProductSelection class:
[ForeignKey("SimulatorId")]
public virtual ProductSelection ProductSelection { get; set; }
Hope that helps.
Related
I am learning ASP.NET and trying to create two table using code first approach using MVC. Below is the model .cs file for Courses.
public class Instructor
{
[Key]
public string name { get; set; }
public string address { get; set; }
public string address_ { get; set; }
public string email { get; set; }
}
public class Course
{
[Key]
[DisplayName("Course")]
[Required(ErrorMessage = "CSExxx")]
public string progId { get; set; }
public string subject { get; set; }
public string semester { get; set; }
public string description { get; set; }
[ForeignKey("instructor")]
public virtual string name { get; set; }
public virtual Instructor instructor { get; set; }
}
I added the corresponding controller and as expected views got generated automatically by visual studio. Once I ran the project and clicked on instructor menu below is the window which got opened.
Clearly I am not able to see the column name which I declared as PK. Now when I click on Create New then I am able to see four columns including the Primary key column which is name.
My Question why I am not able to see the primary key column in the main display.
The templates and code generators at work here assume a technical key, ie one that is not visible or meaningful to the user.
Your choice of name is debatable, the simple solution here is to introduce
[Key]
public int Id { get; set; }
in all your classes. If you do want to stick to the name, then simply edit the generated cshtml.
I have 2 models like this.
public partial class Question
{
public int QuestionId { get; set; }
public string QuestionText { get; set; }
public string Ans1 { get; set; }
public string Ans2 { get; set; }
public string Ans3 { get; set; }
public string Ans4 { get; set; }
}
public partial class UserAnswer
{
public int UserAnsId { get; set; }
public Nullable<int> QuestionId { get; set; }
public Nullable<int> UserId { get; set; }
public Nullable<int> AnsVal { get; set; }
}
As you can see QuestionId is in both the models. How can I render it in view. There are multiple questions. Question Moldel has data in initial run but UserAnswer doesn't.
How can I combine these 2 models so that I can use it as IEnumerable in view. Ans1,Ans2,Ans3,Ans4 has text and AnsVal in UserAnswer will get its value from Raiobutton.
make a combine class like below..i am not sure this is perfect or not..any suggestions are acceptable.
public class QuestionAnswerViewModel
{
public Question Question {get;set;}
public ICollection<UserAnswer> Answers {get;set;}
}
You want to create a ViewModel that represents the combined model objects. This keeps things clean, your model is just that, the model, what gets passed to the view can be the model but in many cases the concept of a ViewModel will make things easier to design while keeping your code loosely coupled and clean. This also keeps things that are not important to the View out of the equation aka particular properties in your model such as maybe a CreatedDate should not be passed to the View, especially since View requests will pass back the value as null since it is not being used in the view and thus not populated on postback. This could lead to you updating the database with a null value for CreatedDate simply because it was not used in the View.
Maybe you have a Model class library in your solution? If you do, create another class library called MyNamespace.Web.ViewModels or something like that. Also you should look into using a tool like AutoMapper that will populate the ViewModel on View request to the Controller and populate the model on View postback to the controller.
A MVC controller takes a few form items passed to it.
Let's say Name and Address.
In the [Post] controller
It receives a Person Object.
The MVC magical mapping takes place and the Person Object is filled.
1) What is the correct term for this magical mapping?
MODEL BINDING
2) Why if my Person object has virtual object, it doesn't get magically filled up?
OK so here is some REAL code.
public class PackageItem
{
public int ProposalItemID { get; set; }
public virutal PackageByContract { get; set; }
public int Quantity { get; set; }
}
public class EquipmentItem
{
public int ProposalItemID { get; set; }
public virtual EquipmentByContract { get; set; }
public int Quantity { get; set; }
}
public class ProposalItem
{
public PackageItem PackageItem { get; set; }
public EquipmentItem EquipmentItem { get; set; }
}
EquipmentByContract
and
PackageByContract
objects both have
EquipmentByContractID
and
PackageByContractID
<select name="PackageItem.PackageByContract.PackageByContractID"...>
<select name="PackageItem.EquipmentByContract.EquipmentByContractID"...>
Post the controller
Upon Debugging PackageByContractID and EquipmentByContractID are both null
Valued being sent are int
In my controller
[HttpPost]
public ActionResult Index(ProposalItem Item)
{...}
Upon hovering over the Item, both objects appear.
When I drill through it both values are null.
MVC needs some very specific inputs with very specific ids to be posted in order to work its Model Binding magic.
If the model is coming back null, you either don't have an input corresponding to each property of your model, or your ids are wrong.
Check out this post for some ideas of what it should look like.
I have a Person model and a student model. The student model has 2 FKs of PersonIDs; one for student and the other for parent.
My view looks like this:
#Html.EditorFor(m => m.student.Person.FirstName)
#Html.EditorFor(m => m.student.Person.DOB)
#Html.EditorFor(m => m.student.Father.FirstName)
The models would look like this:
public partial class Person
{
public int PersonID { get; set; }
[Required]
[PlaceHolder("First Name")]
public string FirstName { get; set; }
[PlaceHolder("Birth Date")]
public Nullable<System.DateTime> DOB { get; set; }
}
public partial class Student
{
public int Student_PersonID { get; set; }
public int Parent_PersonID { get; set; }
}
I want the DOB to be required field for the student but not for the parent. If I add [Required] attribute to the DOB element, then it requires it for both. Is there a way I can set a require a field on the view? or is there a way in the model or using validation attribute to do this?
fyi... i am using EF database first approach
thanks
I would suggest having the view model match the fields that are displayed in the view. If later a field is to be removed from the view, then it will also be removed from the domain model.
In this case, if your view is to display the following fields:
StudentFirstName
StudentDOB
ParentFirstName
ParentDOB
Then I would suggest having the following view:
public class PersonViewModel
{
public int StudentPersonID { get; set; }
[Required]
public string StudentFirstName { get; set; }
[Required]
public DateTime StudentDOB { get; set; }
public int ParentPersonID { get; set; }
[Required]
public string ParentFirstName { get; set; }
public DateTime ParentDOB { get; set; }
}
Or if instead you have 2 seperate views displaying:
StudentFirstName
StudentDOB
AND displaying:
ParentFirstName
ParentDOB
Then I would suggest having 2 seperate view models:
public class StudentViewModel
{
public int StudentPersonID { get; set; }
[Required]
public string StudentFirstName { get; set; }
[Required]
public DateTime StudentDOB { get; set; }
}
public class ParentViewModel
{
public int ParentPersonID { get; set; }
[Required]
public string ParentFirstName { get; set; }
public DateTime ParentDOB { get; set; }
}
Using the view models in this way will allow you to use the [Required] data annotations for the fields that require them rather than trying to create a workaround. Note that the view models are not to be confused with the domain models and therefore this data would then need to be mapped to the domain model.
Hope this helps.
If your application is a simple application you may not need to create a seperate business logic layer and most books only present MVC with simple models which may be fine. However, if you search around you will find other examples where developers recommend having a view model seperate from a business model such as this
I would also recommend reading Wrox Professional Enterprise .Net 2009 where chapters 7 & 8 give great examples of the business layer with discussions of the Transaction Script pattern, Active Record pattern and Domain Model pattern.
One way is to make a PersonRequired class that inherits from Person. Add a metadata class to PersonRequired so you have PersonRequiredMetaData and in that specific that the inherited DOB field is required. You would need to manually copy the values between the Person and PersonRequired classes or use AutoMapper. I hope there is a better answer than this!
Another option is to use FluentValidation that would let you do the validation separate from the model (doesn't use data annotations). I wonder if some people are using data annotations for database requirements and fluent validation for programmatic requirements.
I have a code-first, POCO project in which I am trying to adjust an existing database so that it syncs up with what EF is expecting, given my existing model.
I have these entities:
public class FlaggedDate
{
[Key]
public long scheduledDayID { get; set; }
[Required]
public DateTime date { get; set; }
[StringLength(50)]
[Required]
public string dateStatus { get; set; }
[Required]
public bool isVisit { get; set; }
[Required]
public bool hasAvailableSlots { get; set; }
[Required]
public bool hasInterviewsScheduled { get; set; }
// navigation properties
public ICollection<ScheduledSchool> scheduledSchool { get; set; }
public ICollection<Interview> interviews { get; set; }
public ICollection<PartialDayAvailableBlock> partialDayAvailableBlocks { get; set; }
public Visit visit { get; set; }
public ICollection<Event> events { get; set; }
}
and
public class Visit
{
[Key]
public long flaggedDateScheduledDayID { get; set; }
[Required]
public bool isFullDay { get; set; }
// navigation property
public FlaggedDate flaggedDate { get; set; }
}
The relationship between these two is 1 : 0|1 -- i.e., FlaggedDate will exist but it may or may not have a corresponding single Visit object.
EF thinks, based on this model, that FlaggedDate should have an extra field, visit_flaggedDateScheduledDayID, which is nullable. I finally realized why: it thinks the Visit field, flaggedDateScheduledDayID, is an identity column. It's not supposed to be an identity column; it's supposed to be a foreign key that connects to FlaggedDate.
I think it does this by convention: I remember reading something to the effect that in CTP4, any field that is a single key and is int or long is assumed to be an identity column.
Is there any way I can tell EF that this is NOT an identity column? I tried fiddling with the Fluent API, but it's a mystery to me, and there are no data annotations that you can use for this.
Or, alternatively, is there any way I can fiddle with the navigation properties to get this to come out right?
If you're using mapping files with fluent API
this.Property(t => t.Id)
.HasColumnName("Site_ID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
I would imagine it should also work declaratively
[HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)]
although I didn't try that.
I discovered I can override the identity behavior with this code:
modelBuilder.Entity<Visit>().Property(v => v.flaggedDateScheduledDayID).StoreGeneratedPattern = System.Data.Metadata.Edm.StoreGeneratedPattern.None;
However, it is still not making it a foreign key. I guess that's a different question, though. It seems setting the StoreGeneratedPattern to None is the way to override the default behavior.