tags:

views:

39

answers:

1

After so many years using ASP.Net, I’m still trying to figure out how to achieve the same results using MVC.

I have a materpage with a control that is strongly type to something. When I navigate to a view of a different strongly type model ...and click on the button to execute something, I get "The model item passed into the dictionary is of type Site.Models.RegisterModel', but this dictionary requires a model item of type Site.Models.LogOnModel'".

For the sake of this example, we can take the Default MVC app that is provided with VS 2010, let’s imagine I want to change the “LogonUserControl.ascx” so that it either tells me the logged user (as it works currently) OR allow me to login from there, showing me the text boxes for username and password (therefore in this case from the home page).

So I take the control and strongly type it as:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Gioby.Models.LogOnModel>" %>
<%
    if (Request.IsAuthenticated) {
%>
        Welcome <b><%: Page.User.Identity.Name  %></b>
        [ <%: Html.ActionLink("Log Off", "LogOff", "Account")%> ]
<%
    }
    else {
%> 
    <% using (Html.BeginForm()) { %>
        <div id="logon">
                <div class="editor-label">
                    <%: Html.LabelFor(m => m.UserName)%>
                    <%: Html.TextBoxFor(m => m.UserName)%>
                    <%: Html.ValidationMessageFor(m => m.UserName, "*") %>
                    <%: Html.LabelFor(m => m.Password)%>
                    <%: Html.PasswordFor(m => m.Password)%>
                    <%: Html.ValidationMessageFor(m => m.Password, "*") %>
                    <input type="submit" value="Log On" />
                </div>

                <div class="editor-label">
                    <%: Html.ActionLink("Register here", "Register", "Account")%> 
                    <%: Html.CheckBoxFor(m => m.RememberMe, new { @class = "pad-left" })%>
                    <%: Html.LabelFor(m => m.RememberMe) %>
                </div>
        </div>
    <% } %>
<%
    }
%>

Then on the HomeController, I add a procedure as:

 [HttpPost]
 public ActionResult Index(LogOnModel model, string returnUrl)
 {
     if (ModelState.IsValid)
     {
          // ==>> Check Login against your DB

          // Now check if param returnUrl is empty
          if (!String.IsNullOrEmpty(returnUrl))
              return Redirect(returnUrl);

          return RedirectToAction("Index", "Home");
     }

     // If we got this far, something failed, redisplay form
     return View(model);
 }

I tested it from the home page … it works !!!

BUT when I navigate to the “Register” view (remember that the “LogonUserControl.ascx” is located inside the “MasterPage”, therefore visible from the Register view).

So when I click on the Register button, I get the error: The model item passed into the dictionary is of type Site.Models.RegisterModel', but this dictionary requires a model item of type Site.Models.LogOnModel'.

QUESTION: Does that mean that I will never be able to different pieces together into one view?

Let’s say I want to write an eCommerce site and on the home page I want to see “Most used Tags”, “Most bought products”, “Product of the Month”, “List of Categories” …all within the same view and each one with his own HTTP POST action.

If this is possible using MVC?

A: 

If I'm understanding the problem correctly, you have two Views that use the same MasterPage, but which are strongly typed against different ViewModels. The master page is able to include a Partial View that is also strongly typed, as long as its expected ViewModel is the same as that of the parent view. However, if you're using a view with a different ViewModel type, it doesn't know what to do.

Consider the following:

<% Html.RenderPartial("LogOn") %>

The above code implicitly includes the model data for the current view being rendered. It's exactly the same as if you had said:

<% Html.RenderPartial("LogOn", Model) %>

So this will only work if Model is a LogOnModel. Remember that the MasterPage is really a part of whatever View inherits it, so even if you're putting this in the MasterPage, it's as if you'd put the same code in every view that inherits it. So if your View's Model is not the same as the PartialView's Model, this won't work. One alternative is to use inheritance to ensure that every ViewModel will include all the information required by the Master Page. This approach is described in detail here.

But that approach means that you have to always use a factory to produce your view model, and every view model has to be somewhat aware of which master page it will use. In our product, we can use a different master page on the same view depending on what mode the user is viewing the site in, so it doesn't make sense to tie the ViewModel to that of the Master Page. We accomplish what you're describing using the RenderAction method, which allows you to render an entire controller action as if it were just a part of the larger view. Some of the advantages of this approach are discussed here.

So now you can have your MasterPage include whatever little partial views you want, but you separate the logic for building the ViewModel of each of these Views into an individual controller action that's responsible for that particular Partial View:

<% Html.RenderAction("LogOnBox") %>

The Action:

public ActionResult LogOnBox()
{
    LogOnModel model = GetLogOnModel();
    return PartialView("LogOnUserControl", model);
}

Now, regardless of what model your current view uses, your Master Page can include “Most used Tags”, “Most bought products”, “Product of the Month”, “List of Categories”, etc. Better still, these portions of the page can leverage output caching so they don't have to be regenerated with every page load if they don't change very often.

StriplingWarrior
Not sure how RenderAction would work ...Are u saying to use it instead of RenderPartial ?? Isn't RenderAction a call to a method?
Filu
@Filu: See my updated answer.
StriplingWarrior
To clarify, are we saying that whenever we create a Partial view (i.e. a Control.ascx), in order for it to work, it needs to have his own ControllerFile.cs and own ModelFile.cs ?And then if we use RenderAction, on the View or MasterPage we want to use the Partial view ...that should work.so ideally we could have one View with 10 Partial Views and each will point to their own Controller and own Models, even if the Main View belongs to another Model/controller ... is this correct?I still need to research how to use Caching with MVC but surely that is in the list of things to do.
Filu
Yes, it sounds like you get it. In MVC, the ascx files are not called "User Controls" because they don't do the fancy stuff with ViewState, Events and PostBacks that you see in WebForms controls. You don't always need one Controller per partial view, but you will often have one Action per partial view. How you divide up your Actions among Controllers is entirely up to you. But generally each View and PartialView will have its own Model class, and the controller Actions' responsibility (in these cases) is to decide which view or partial view to use and then construct a Model object to match it.
StriplingWarrior
So, coming from WebForms, I'm still struggling to understand how the right action on the right Controller gets called when we click a button, let's say on a partial view ....so that the controller that manager the current big view does not get involved? how does an action get called ...
Filu
What makes the ASP engine call the LogonBox Action that is defined on a different controller than the Main View Controller, where it's display on.Is that based on:1) the name of the Button2) the way we tell the Big View how to Render the Partial View RenderAction("ActionName", "ControllerName")3) the fact that there is NO other Action named LogonBox?...?? hehe I still don't get that part
Filu
If you specify a controller name in RenderAction, the corresponding controller will be used. If you don't, it will try to use the controller that the current http request is invoking. So if multiple controllers invoke views that use the same MasterPage, you'll want your Master to be as specific as possible about the controller and area you want to use for your partial views.
StriplingWarrior
Concerning buttons: When you use HTML.ActionLink or Url.Action, it will produce a link or url that follows the same pattern as RenderAction. So you'll only want to have your buttons link to actions that lead to full Views (i.e. with a master page). It would be weird to click "Log On" and have *only* the logon box appear, without the master page around it. When a user clicks the link, MVC's routing will parse the URL and invoke the correct controller and action. The URL may include the controller and action names explicitly, or (in the case of defaults like "Index") it can omit them.
StriplingWarrior