I am trying to create a timesheet application in MVC 2, but I feel like I am still struggling to grasp the model/view relationships and all that.
The problem I have is, I want to let the user report a new time segment in a create view. But I want to have dropdownlists populated with Projects, Tasks, and Consultants from the model.
Basically the database structure looks like this:
(table) TimeSegments
TimeSegmentID
Hours
Date
ConsultantID (FK)
TaskID (FK)
ProjectID (FK)
(table) Projects
ProjectID
ProjectName
(table) Tasks
TaskID
TaskName
(table) Consultants
ConsultantID
ConsultantName
This design may be extended in future, right now I want to get basic functionality working before I complicate it further.
Now, I am passing the entire model to the create view (actually a viewmodel based on it, just to simplify some coding, but it might as well have been the entire model).
The problem is, normally when I have done similar things with a create view, I would have created a new object in the controller and passed that to the view. In this case it would have been the TimeSegment object, since it is a new time segment that should be created in the database. Then I could just submit it and update the database in the controller. However, if I only pass a new TimeSegment object to the view, I can’t populate the dropdownlists with Projects, Tasks and Consultants.
And oppositely, if I only pass the entire model, how would I bind a textbox to a new TimeSegment to be updated in the database?
I feel like I need to send both a new TimeSegment object and the entire model to do this, but then I have no idea how I would access it (there’s only that one “Model” to access from the view). Also, back in the controller after a submit, how would the controller know what to update?
I’m sure I’m just thoroughly confused still by the MVC way of thinking, but I would really appreciate it if someone could clarify this for me and tell me (as pedagogically as possible) what to do to solve this.
Okay, I will give it a shot.
MVC is not hard, but you do have to alter your way of thinking a bit. In MVC you have the Models (your data layer[s]), the Views and the Controllers.
Before we continue, I make the assumptions with my examples below that you are using LINQ to SQL for you data access layer (Model), and I have labeled it as
dc.The Controllers fetch and format the data out of the Models and hand it off to the Views to display. So lets start with your first view which would be the view to create a TimeSegment.
This action will create a
TimeSegmentViewobject and pass that to theViewas itsModel. Keep in mind that this action is decorated with[HttpGet]. TimeSegmentView` is a container class for the objects you need to pass to the view to create your UI and it looks like this:NOTE: I’m not using the TimeSegment property yet, it’s further down…
In the view make sure you have it inherit from TimeSegmentView. Assuming that you’re following the default MVC project structure and with me taking the liberty to add a
Viewsfolder into theModelsfolder your full reference would look like this:Now you’ve typed the view to that object and you can now interact with its properties. So, you can build a form such as:
As you can see it created 3 select fields and just performed loops in each of them to build up their values based off of the model.
Now, taking a submission of this form, we’ll need to get the data and add it to our database with:
Okay, first notice that I’ve named the action the same, but this one has an
[HttpPost]decoration. I’m telling the action that I’m sending it aTimeSegmentobject and that I want it to bind the properties in the Include clause (this is mostly for security and validation). I then take theTimeSegmentobject I’ve passed in, add it to the data context, submit the changes and redirect. In this case I’m redirecting to another action to edit the object I just created passing in the newTimeSegmentID. You can redirect to what ever, this just felt appropriate to me…In the edit action your doing the same thing as in the create action by building a new
TimeSegmentViewobject and passing it to the view. The key difference here is that you’re now populating theTimeSegmentproperty. Your form would look something like this (shortened from above):And your receiving action on the controller would look like this:
Lastly, if you want to display a list of TimeSegments you can do something like this:
And
TimeSegmentsViewlooks like this:And in the
Viewyou’d want to do this:I hope this is enough to give you a start. It’s by no means complete, but its 5 AM and I haven’t slept yet, so this will have to do for now (from me). Feel free to name your actions what you want, you don’t have to stick to my naming conventions.
I would suggest however that you change the naming of the properties of your tables. For example when your writing the expressions like in the table above you’ll have to do
TS.Project.ProjectNameand that’s redundant. You’re already accessing theProjectproperty ofTSthrough their relationship so you know you’re only going to work with aProject. This then makesProjectNamea pointless blob of text re-describing the object your working with. Instead just useName, and turn your expression toTS.Project.Name.Anyway, just a suggestion, do what you like better. I’m passing out, so good night and happy Thanksgiving!
UPDATE
The process with collections is essentially the same as far as the controller side is conserned. It’s the client side and the JavaScript that’s more difficult to get going, so I’ll assume that you have something established on that end.
So, here’s how the controller would work. You pass in an array of TimeSegment and the model binder is smart enough to figure it out through the Prefix of your form elements.
And the controller:
And that’s it. Of course you’ll want to validate or do other stuff before sending to the database, but that’s roughly all there is to it.
UPDATE 2
I believe you can do an
IList<TimeSegment>instead ofTimeSegment[]without issues, but as far as if it’s better, that’s up for debate. The way I look at it the browser still sends a virtual array to the server so having the action receive an array feels natural, but its up to you what you want to use.So, a generic list action would look like this:
Keep in mind that I haven’t used this (meaning the IList) before so I can’t guarantee it will work, just speculating…
UPDATE 3
About what you want to do with the
Consultant, it sound a lot like what I do with Cookies. I have aBaseViewclass which is the type used by the Site.Master and then all other views extend from it. In theBaseViewI have a Cookie property which is always populated by each controller action. I then use that property to get the id of the currently authorized user.So, in code it looks like this (using examples from one of my apps):
And with this, say I want to add a note to an employee, my form would look like this where I pass in a hidden field which is where your
ConsultantIDcomes into play.Hope this helps.