I am having trouble following good object-oriented design in ASP.NET MVC2 and I think the problem is a poor understanding of how ViewModels and Views should interact when the user will be posting information.
I have implemented a forum that allows users to create replies to a thread using a Reply action. My ReplyViewModel contains an int for the threadId and a string for the reply's content. The Reply action creates a ReplyViewModel with the ThreadId so we'll know to which thread the user is replying. The Reply View is strongly typed to the ReplyViewModel and has a form allowing the user to edit the model's content. The user can then post to the Reply action with the threadId as a parameter.
It works, but I don't think I'm doing this the correct way. Each post involves two ReplyViewModels: one with the threadId but null content, and the other with content but a null ThreadId. The Reply View is creating a new ReplyViewModel, and I think it should just be editing the ReplyViewModel that was passed to the view--but I don't know how.
Here's a stripped-down version of the view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<mvcForum.ViewModels.ReplyViewModel>" %>
<%: Html.TextAreaFor(model=> model.content, new{TextMode="multiline", id="postbox"})%>
<input type="submit" value="Reply" />
In case that's not enough to answer my question, here's the entire view: http://pastebin.com/BFGL4p67
The ViewModel is literally just a string (content) and an int (threadId).
The controller:
[Authorize]
public ActionResult Reply(int id)
{
ReplyViewModel reply = new ReplyViewModel
{
ThreadId = id
};
return View(reply);
}
[HttpPost]
public ActionResult Reply(int id, ReplyViewModel model)
{
/**TODO: Catch when no user*/
var newPost = new ForumPost
{
UserId = (Guid)Membership.GetUser(User.Identity.Name).ProviderUserKey,
ThreadId = id,
postContent = model.content
};
db.AddToForumPosts(newPost);
db.SaveChanges();
return RedirectToAction("Index");
}
So my question is "What's the best way to do what I'm trying to do? Can I use a single instance of the ReplyViewModel to pass from the controller, to the view, and back to the controller?"
I think what you're doing is fine.
You're thinking about a problem that you haven't come across yet, which is when the data to populate the form differs greatly from the data that will be posted. Here the 'Get' view model is a subset of the 'Post' view model, so using one view model is fine, but if the data for the 'Get' and the data for the 'Post' differed greatly, you could use two different view models such as:
public class GetReplyFromViewModel //...
public class PostReplyFromViewModel //...
But I would suggest against this unless both view models were very different and sufficently complex to require their own view models.
One important thing to remeber: just because the view is strongly typed to type 'A' doesn't mean the type of the parameter in the post method can't be type 'B'.
The type of the View will simply determine the compilation / intellisense in the view along with the type checking of the view whenever one is created.
The type of the post method parameter will simply use the MVC Model Binding to convert the posted HTTP form to the type you specify.
In other words the type of your view could simply be
<%# Page Title="" ... Inherits="System.Web.Mvc.ViewPage<int>" %>
Where the whole Model variable is simply the ThreadId if you like and the post method could still take the same parameter if your html fields were named properly.
but again, in this particular case I think the current implementation is just fine.
Related
I've seen flow charts of the life-cycle but they never seem to answer this question OR I'm just not getting it.
So if user hits a registration page is it:
Routing engine chooses controller.
Controller checks to see which view should be displayed, checks to see if it is strongly typed and if so instantiates the correct model.
Calls the View, passing the empty model in.
I'm also interested in what happens when the form is filled out and submitted.
I'm not looking for anything super technical, just something conceptual...
Regardless of the user actions (followed the link, entered the URL, submitted the form) the basic flow of the MVC application is the following:
According to the routing table Controller's name and method (aka Action) that will handle the request are defined.
If there were any request parameters (values in form for example) they are associated with the Action's parameters.
Request context is generated (contains details about request, client, server, etc.)
Object of Controller's type is created, Action (method) of this object is called with given parameters.
After processing, Action returns an appropriate result, most likely View (could also be Json, plain text, anything).
View is rendered and send back to the client as a response.
Of course, a lot of details are left aside here, but this is the general conception.
Update: some words about models.
Models are used to pass data from the Controller to the View. There are two main approaches:
Using ViewData collection - basically a regular key-value dictionary. In Controller it is filled with data:
ViewData["SomeKey"] = "someValue"; //not only string, any object can be here
return View();
And in View values are retrieved by keys:
<%= ViewData["SomeKey"] %>
Strongly typed Views. A Model class that will contain necessary data is created. View is specified to be strongly typed with this class, and when Action returns a View object, it passes an instance of this class as a parameter. Here is some code example:
Model:
public class SomeModel
{
public string SomeKey { get; set; }
}
Controller:
SomeModel model = new SomeModel();
model.SomeKey = "someValue";
return View(model);
View:
<%# Page ... Inherits="System.Web.Mvc.ViewPage<SomeModel>" %>
...
<%= Model.SomeKey %>
I've a problem with Partial View.
I am developing a blog in asp.net mvc and I would make in my masterpage a list of categories, last post, last comments.
I think that the best solution is to use strongly typed partial view, and in each partial view pass the necessary model.
MY problem is that the model in View.. in any view (connected to the masterpage's contentplaceholder) enter in conflict with the models in partial views and I get an error like this:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Blog.Models.Articoli]' but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Blog.Models.Categorie]'.
I found on web a dirty solution that consist of to pass togheter the model of any view, some viewdata, one for every model to pass in partial view. But this solution don't respect DRY Principle..because you must repeat this code for each action!
So, my question is: Can I create a model that contain also partial view's model?
If, yes, in that way?
It Exist another solution more simple?
Thanks for help
How about the View Model Pattern?
I've created wrapper classes that are passed to my views rather than whatever object I would normally use
public class MyCreateUserView
{
public User CreatingUser { get; set; }
public MyPartialViewObject Blah { get; set; }
}
In your view write:
public ActionResult CreateUser()
{
MyCreateUserView createUser = new MyCreateUserView()
{
CreatingUser = GetUserFromSomewhere(),
Blah = GetPartialViewObject();
}
return View(createUser);
}
Then your page header looks like so:
<%# Page Language="C#" Inherits="ViewPage<MyCreateUserView>" %>
and when you render your partial write:
<% Html.RenderPartial("../MyPartialViewObject ", Model.Blah); %>
Instead of solving that with the pattern you describe (which is generally a great pattern), I solve that with calls to RenderAction and have it return a partial view. That way the code is in one place as each call to each view does not have to worry about marshalling all the data you need. If you want to see a short discussion on how to use it, I would check Haack's blog here: http://haacked.com/archive/2009/11/18/aspnetmvc2-render-action.aspx. You can also check out the discussion on this other post here on SO: ASP.NET MVC Master Page Data
I have a page for creation of dynamic entities.
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
...
I have two actions:
public ActionResult Create()
{
dynamic model = ...
return View(model);
}
[HttpPost]
public ActionResult Create(dynamic(1) entity)
{
...
}
Well, the problem is that the entity comes empty from the page. If I change dynamic in (1) for the real type it works fine.
I'm not 100% on this, but I think the problem is that the default model binder has no idea what to do with a 'dynamic' type, since it doesn't have any defined properties to reflect over. You'd need to write your own model binder that would use the form input names instead, which is dangerous/unreliable because the form can be modified on the client side.
I've explored dynamically-typed ViewPages before (here on SO actually: Dynamic typed ViewPage), and I've come to the conclusion that it really doesn't give you anything in most situations. At least, not yet (MVC 3+ could be different story).
And here's some notes from Phil Haack on the matter: http://haacked.com/archive/2009/08/26/method-missing-csharp-4.aspx
Ok, i can use jeditable to edit-in-place some content on a page and the content will be saved to a database. But whats the best way to re get that text-content from db to show into a place holder?
p id="paraNo34" class="editable"
-->What i will write here so that it will get content from a
db's table: [Content], where id=="paraNo34".
/p
The problem is if i will use some hard coded text like
p id="paraNo34" class="editable"
-->Some text here
/p
I will able to edit-in-place using jeditable but when i will refresh page it will show the same "Some text here" as its not getting data from db.
Your pseudocode implies that you want the view to be responsible for fetching the required data, which is an anti-pattern in MVC. You need to retrieve the text in your controllers action and pass it to the view, either using ViewData or a custom view model, e.g.:
public ActionResult Index(string id)
{
// call some method to fetch data from db
ViewData["ID"] = id;
ViewData["Content"] = content;
return View();
}
And the view looks something like:
<p id='<%= ViewData["ID"] %>' class="editable">
<%= Html.Encode(ViewData["Content"]) %>
</p>
A better approach would be to create a strong-typed view model (Stephen Walther has a blog post about view models here), but the above example should illustrate how data can be passed from the controller to the view.
A function returns only one view.
what if I want to return multiple views in a function?
For example, I have this code:
Function Index() As ActionResult
Dim _news As DataTable = News.newsSelect()
Dim _announcement As DataTable = Announcement.SelectAnnouncement()
Return View()
End Function
I want to return _news and _announcement to be used in the aspx page. How would I do this?
Are you trying to show both sets at the same time? News and Announcements?
If so then why not implement either a PartialView or two PartialViews?
Then in your main view you can render them and pass the collection to the PartialViews?
There are heaps of samples on this and the one I recommend is in NerdDinner if you haven't already seen it.
I hope this helps. If you want sample code then let me know.
One simple way is just to have those two datasets sent in a ViewData element, which you can access in a field.
example:
ViewData["Elements"] = new SelectList(aElements, "Guid", "Name");
is consumed as:
<%= Html.DropDownList("Elements","Pick an element")%>
Also, I think that if you read between the lines of this blog post here you will find an elegant way of achieving what you want ;) but its a bit more involved..(only because you mentioned Views instead of just variables..
Quote:
We need to create our own implementation of IViewFactory. This
is responsible for locating and
creating an instance of an IView
(which both ViewPage and
ViewUserControl implement).
To “inject” (all you DI fans excuse me borrowing the term without
using a DI framework) our new View
Factory into every Controller we are
going to create our own
IControllerFactory implementation.
We need to configure the framework to use our new Controller
Factory.
Finally we can create two Views – an AJAX version and a pure
HTML version.
Building on that should be all you need
Good luck!
Ric
Assuming what you are trying to do is use both of those DataTables to populate some View, then my recommendation would be to create a wrapper object and then a strongly typed view based on this object.
The wrapper object would contain properties for all of the data elements that you need in order to render your view properly. In your case, it is 2 DataTable objects. I do not really know VB, so all my examples will be in C#. Here is an example of the data wrapper class...
public class IndexViewData
{
public DataTable News { get; set; }
public DataTable Announcement { get; set; }
}
You then might update the Index action in your controller as follows:
public ActionResult Index()
{
var viewData = new IndexViewData();
viewData.News = News.newsSelect();
viewData.Announcement = Announcement.SelectAnouncement();
return View(viewData);
}
Finally, you would need to create/update your view to be strongly typed. This is done by having your page inherit from the generic System.Web.Mvc.ViewPage<T> class. Just substitute the view data wrapper created earlier for T. To do this, you would set the inherits attribute of the <%# Page %> element. In your case, if we assume your root namespace is called "Foo", you might have the following page declaration in your Index.aspx view (added extra line breaks for readability):
<%# Page Title=""
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<Foo.Models.MyModelType.IndexViewData>"
%>
Once you have a strongly typed view for your view data wrapper, you can access the wrapper object in your view using the Model property. Here is an example of something you could do in your Index.aspx view
<%-- Output some random data (bad example, for demonstration only) --%>
<%= Model.News[0]["title"] %><br/>
<%= Model.Anouncement[0]["body"] %>
In reality you're probably going to do something like iterate over each row of the data table. Regardless, once you create the strongly typed view, your model object, which was passed to the view in the Index method of the controller, is available in the Model property within the view.
You can find detailed tutorials at the ASP.NET MVC site