I'm building one page to edit various product types. Each product type has a view model (TentProductVM, BootProductVM) that inherits from ProductVM. My MVC2 View checks the model type and adds fields as appropriate. For example, if the model is of type BootProductVM I call Html.TextBoxFor to add a field for the boot's foot size. Page displays fine.
The problem is the post. I've declared a function (in VB) as follows:
<HttpPost()>Function Edit(byval prod as ProductVM) As ActionResult
Of course, this function only receives the form data from the base class ProductVM. So instead I added a function for each product type:
<HttpPost()>Function EditTent(byval prod as TentProductVM) As ActionResult
<HttpPost()>Function EditBoot(byval prod as BootProductVM) As ActionResult
and point the View to the appropriate post function:
Using Html.BeginForm("Edit" & Model.ObjectTypeName, "Catalog")
However, when EditTent or EditBoot gets called, the prod parameter only contains data from the base class. All the fields declared in the subclass view models are left at default values.
What am I doing wrong? Or is there a better approach? (The obvious solution is multiple pages but, since ProductVM has many fields relative to the subclasses, I'd rather not.)
After much experimentation, I've decided not to use this approach. First, I couldn't get it to work without resorting to having an Action parameter of type FormCollection. Second, the obvious solution I discarded is appealing if I use a partial view. The partial view has all the fields associated with the base class (ProductVM), leaving only the fields associated with the derived classes (TentProductVM, BootProductVM) in the regular views.
Felt like I was fighting against the MVC auto-magic, which is never the right approach.
The thing to remember about MVC is that it's based on the "Convention over Configuration" mindset. So if you're passing a strongly typed class class instance to your action method, it expects it to be named "model".
Try changing your declarations to look like this:
<HttpPost()> Function EditTent(byval model as TentProductVM) As ActionResult
<HttpPost()> Function EditBoot(byval model as BootProductVM) As ActionResult
The other (less ideal) option would be to expect a FormCollection object in your action method.
<HttpPost()> Function EditTent(byval form as FormCollection) as ActionResult
Update
Just updating to include some of the discussion points below... In order to post a strongly typed object to a controller action method, the types need to match up.
Assuming your controller's action method looks like this:
<HttpPost()> Function EditTent(byval model as ProductVM) As ActionResult
Your view should be typed accordingly:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Your.Namespace.ProductVM>" %>
Related
In my View i have to put two different Forms and i have to use two different ViewModels. So i decided to use Tuple.
View:
#model Tuple<pi.Models.AddNewMechanic,pi.Models.ExistingUser>
and I got two forms
But how can i recive it from View in HttpPost Controler?
I tried like that:
public ActionResult AddMechanic(Tuple<pi.Models.AddNewMechanic, pi.Models.ExistingUser> model) {}
but i got message it cannot find method with parameter, so how I have to implement it inside this method, but there is a question how?
I strongelly sugest you create a new Model and inside of your new model, you can put this two object. Take care to dont mix domain model and view model. Otherwise, you will have always this kind of problem.
Domain Model
- class a
- class b
View Model
- class AB
prop A, prop B
I suggest you do this by the book, which would be:
AddMechanic.cshtml has no special view model, but contains two partial views. This is your "page with two forms".
_AddNewMechanic.cshtml is the first partial view form, which has AddNewMechanic as view model.
_ExistingUser.cshtml is the second partial view form, which has ExistingUser as view model.
The form in _AddNewMechanic.cshtml posts to the AddNewMechanic action method, which takes an AddNewMechanic as parameter.
The form in _ExistingUser.cshtml posts to the AddExistingMechanic action method, which takes an ExistingUser as parameter.
Upsides:
No need for any Tuple weirdness.
You can keep your existing View Models.
Your action method parameters will not normally be null, so no need for weird null checks.
Easy to follow and understand your code.
I think I'm not clear with the #model that can be part of the view
For example
#model MyModel
Is it the input argument that I can populate and call the view with?
return View("MyView", MyModel);
Is it the output variable I can populate during the post of the view (for next control action)
[HttpPost]
public ActionResult SomePostAction(MyModel myModel) //(and in post action)
Is it both ??
3, it's both!
It is called model binding. A feature of ASP.NET which makes it trivial to bind a mode to a view. Hence the name 'view model', which those models are usually called.
Assigning a model to your view gives you a so-called strongly typed view, which fully exposes the power over the Razor syntax.
The model binder is capable of binding the values of every input field back to the model when posting the form, as long as the name attribute of the form matches the name of the property on the view model. Html helpers such as Html.EditorFor(m => m.SomeProperty) makes this a trivial task.
As Mystere Man mentions, it's also possible to do this without an actual model in your view. For instance, this works:
Html (I omitted the form tag and submit button):
<input type="text" name="SomeString" />
with this method in your controller:
[HttpPost]
public ActionResult SomeAction(string someString)
{
// ...
}
The #model declaration at the top of your view is related to the model object you passed to the View() method in your controller (option 1 in your question). The #model declaration is your way of telling the Razor view engine that the view is strongly typed. That means the C# compiler can double check any properties of your view accesses.
Suppose you had the following class
public class MyModel
{
public string Name { get; set; }
}
Without a strongly typed view you might have something like this in your view
<div>
Hello, #Model.Nmae
</div>
Notice that there's a typo in Name. ASP.Net has no idea what your model is so it has to use a dynamic object. You won't find that error until runtime. If you had delcared #model MyModel you would have an error at build time because MyModel doesn't have a Nmae property.
However, it's not uncommon to use the same model type as a parameter of the action. Imagine your page is an HTML form. In that case the model that your view is strongly typed to and the model that's passed to an MVC action could be the same.
so, I've seen this working on a previous project in MVC3, so wondering if a) i've screwed it up, or b) MVC4 is doing something different (can't see it would be).
I have a model bound Razor view which submits to a controller action method (as is the way with MVC)
post action method:
[HttpPost]
[AutoMap(typeof(MyViewModel), typeof(MyDomainObj))]
public void PostAction(MyDomainObj obj)
{... etc.. etc..
The action filter eventually does something like this:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.Controller.ViewData.Model;
NOTE: In Jimmy Bogard's example he used OnActionExecuted which I also tried..
The key issue I'm having is that at the pint where we get the "model" variable from the context, it's null. If I look at the filterContext.ActionParameters whilst debugging I can see a MyDomainObj instance!! which appears to (because it happens to have a prop name in common with the MyViewModel type) have been mapped from my form data!
So.. yes if I had MyViewModel type as the parameter to this method, then the param would be properly populated from the submitted form. But. I don't want to do that, I want to (and have done before based on JB's succinct how-to) converted the view model to domain model as part of the action executed/ing and then been able to just hit save on my domain model.
Summary - why is my ViewData.Model null on the post action filter?
Jimmmy also has/had a couple of ideas on how to implement post actions, along with another guy Matt Honeycutt who shares his view on how to do posts. I also believe Jimmy has gone in the direction of using explicit mapping in his get requests instead of attributes, as it can be hard to inject any extra code you need after mapping.
http://lostechies.com/jimmybogard/2011/06/22/cleaning-up-posts-in-asp-net-mvc/
http://trycatchfail.com/blog/post/Cleaning-up-POSTs-in-ASPNET-MVC-the-Fail-Tracker-Way.aspx
For a post you really want a couple of things imo, the original Entity and the Form Data. You could load the entity like you do for the GET request and do normal model binding to get the form data (remember you can accept a different model for post backs than you spit out in your view) then make the changes in the action.
Of course this would require using AutoMapper in your action, which you seem to be trying to avoid. But unless you write a custom model binder then you're not going to magically get the formdata in a model as the built in one looks at the action paramters to figure out what to bind. For this reason i'd recommend not using a domain model as a parameter for an action, as it may fill out fields that you don't wish it to.
I've also seen Jimmy using a ModelBinder to do something similar to your AutoMapGet, which may be another alternative method, but I'm guessing you've seen that post.
My standard post takes Matt's approach, moving the validation out into a global attribute so it can't be forgotten (there are some downsides to this) then accepting a new view model explicity for the form data, then I explicity load the entity, use automapper to update it and call save. Most the actions only end up being about 5 lines long.
I do avoid using the static methods on AutoMapper and pass a IMapper in via constructor injection. This allows me to make it a bit more testable if I really need to, however the methods are normally so simple the tests don't add all that much value.
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
The 'RenderPartial()' method in ASP.NET MVC offeres a very low level of functionality. It does not provide, nor attempt to provide a true 'sub-controller' model *.
I have an increasing number of controls being rendered via 'RenderPartial()'. They fall into 3 main categories :
1) Controls that are direct
descendants of a specific page that
use that page's model
2) Controls that are direct
descendants of a specific page that
use that page's model with an
additional key of some type.
Think implementation of
'DataRepeater'.
3) Controls that represent unrelated
functionality to the page they appear
on. This could be anything from a
banner rotator, to a feedback form,
store locator, mailing list signup.
The key point being it doesn't care
what page it is put on.
Because of the way the ViewData model works there only exists one model object per request - thats to say anything the subcontrols need must be present in the page model.
Ultimately the MVC team will hopefully come out with a true 'subcontroller' model, but until then I'm just adding anything to the main page model that the child controls also need.
In the case of (3) above this means my model for 'ProductModel' may have to contain a field for 'MailingListSignup' model. Obviously that is not ideal, but i've accepted this at the best compromise with the current framework - and least likely to 'close any doors' to a future subcontroller model.
The controller should be responsible for getting the data for a model because the model should really just be a dumb data structure that doesn't know where it gets its data from. But I don't want the controller to have to create the model in several different places.
What I have begun doing is creating a factory to create me the model. This factory is called by the controller (the model doesn't know about the factory).
public static class JoinMailingListModelFactory {
public static JoinMailingListModel CreateJoinMailingListModel() {
return new JoinMailingListModel()
{
MailingLists = MailingListCache.GetPartnerMailingLists();
};
}
}
So my actual question is how are other people with this same issue actually creating the models. What is going to be the best approach for future compatibility with new MVC features?
NB: There are issues with RenderAction() that I won't go into here - not least that its only in MVCContrib and not going to be in the RTM version of ASP.NET-MVC. Other issues caused sufficent problems that I elected not to use it. So lets pretend for now that only RenderPartial() exists - or at least that thats what I've decided to use.
Instead of adding things like MailingListSignup as a property of your ProductModel, encapsulate both at the same level in a class like ProductViewModel that looks like:
public class ProductViewModel() {
public ProductModel productModel;
public MailingListSignup signup;
}
Then get your View to be strongly-typed to the ProductViewModel class. You can access the ProductModel by calling Model.productModel, and you can access the signup class using Model.signup.
This is a loose interpretation of Fowler's 'Presentation Model' (http://martinfowler.com/eaaDev/PresentationModel.html), but I've seen it used by some Microsoft devs, such as Rob Conery and Stephen Walther.
One approach I've seen for this scenario is to use an action-filter to populate the data for the partial view - i.e. subclass ActionFilterAttribute. In the OnActionExecuting, add the data into the ViewData. Then you just have to decorate the different actions that use that partial view with the filter.
There's a RenderPartial overload I use that let's you specify a new ViewData and Model:
RenderPartial code
If you look at the previous link of the MVC source code, as well as the following (look for RenderPartialInternal method):
RenderPartialInternal code
you can see that if basically copies the viewdata you pass creating a new Dictionary and sets the Model to be used in the control. So the page can have a Model, but then pass a different Model to the sub-control.
If the sub-controls aren't referred directly from the main View Model, you could do the trick Marc Gravell mentions to add your custom logic.
One method I tried was to use a strongly typed partial view with an interface. In most situations an agregated ViewModel is the better way, but I still want to share this.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IMailingListSignup>" %>
The Viewmodel implements the interface
public class ProductViewModel:IMailingListSignup
Thats not perfect at all but solves some issues: You can still easily map properties from your route to the model. I am not shure if you can have a route parameter map to the properties of MailingListSignup otherwise.
You still have the problem of filling the Model. If its not to late I prefer to do it in OnActionExecuted. I dont see how you can fill a Model in OnActionExecuting.