How to send model object in Html.RenderAction (MVC3) - asp.net-mvc

I'm using MVC3 razor, and I'm trying to pass an object to a partial view, and it's not working.
This works fine without sending the object model to the partial view:
Html.RenderAction("Index", "ViewName");
Trying this doesn't sent the model object, i'm getting nulls instead (the object has data, and the view expects it):'
Html.RenderAction("Index", "ViewName", objectModel);
Is this even possible using RenderAction?
Thanks!
Edit: I found the error, there was an error with the controller's action that didn't pick up the sent object. Thanks for all your help!

You can actually pass an object to a controller method using Action. This can be done on any avaialble view, for instance I have one in a shared library that gets built to project bin folders that reference my shared project (properties - Copy if newer on the view file, in Visual Studio). It is done like so:
Controller:
public class GroovyController : Controller
{
public ActionResult MyTestView(MyModel m)
{
var viewPath = #"~\bin\CommonViews\MyTestView";
return View(viewPath, m);
}
}
MVC page (using Razor syntax):
#Html.Action("MyTestView", "Groovy", new { m = Model })
or using RenderAction method:
#{ Html.RenderAction("MyTestAction", "MyTestController", new { area = "area", m = Model }); }
Note: in the #Html.Action(), the Model object must be of type MyModel and that 3rd parameter must be set to the controller variable name, of which mine is MyModel m. The m is what you must assign to, so I do m = Model.

say you want to pass foo as model, make it first
public class Foo {
public string Name { get; set; }
public int Age { get; set; }
}
now make an ActionResult
public ActionResult FooBar(Foo _foo){
return PartialView(_foo);
}
call it
#Html.RenderAction("FooBar", "Controller", new { Name = "John", Age=20 });

Usually if I have a model already available it makes more sense to use Html.Partial than trying to render an action.
#Html.Partial("Foo", Model.FooModel)
Where Foo.cshtml is a view file (perhaps in your Shared folder) strongly typed with with #model FooProject.Models.FooModel or whatever your model is called. This can be as complex a model as you need it to be. Model is your page's main model into which you must set FooModel - or just omit this parameter if the Foo view uses the same model as the parent page.
RenderAction is generally better when you have just simple parameters, because you're just simulating a request to a regular action which has routing/query string parameters - and then dumping that response into your page. It works well if you need to put something in a Layout that isn't available in your page's model such as an element in a side bar.

Related

How to post a custom model to a controller in Umbraco 7.5.8?

I have a Document type, Template, and a page in the CMS content tree which uses these for a Contact page. The document type has no CMS data properties, because it doesn't need any. I use Models Builder for other pages with no issue, but for this page I've created my own custom model within my MVC project.
I've read every tutorial I can find, and looked at every forum post and issue on the Umbraco forums and Stackoverflow, and for the life of me I can't figure out what I'm doing wrong. The model name and namespace do not conflict with the autogenerated Models builder one.
My understanding is for posting forms a SurfaceController is the way to go - a RenderController is intended more for presenting stuff. So my controller extends SurfaceController. uses Umbraco.BeginUmbracoForm(etc)
I've tried every combination of SurfaceController and RenderController with UmbracoTemplatePage, UmbracoViewPage and every way of changing my model to extend both RenderModel and IPublishedContent to test each. When trying RenderController I've overridden default Index method with RenderModel parameter to create an instance of my model with the renderModel parameter.
Usually the error I get is "Cannot bind source type Umbraco.Web.Models.RenderModel to model type xxx". Sometimes combinations I've attempted allow the Get to succeed, then give this error on Post.
I've even tried to remove the page from the CMS and use a standard MVC controller and route - this allows me to display the page, and even using a standard Html.BeginForm on my view, I get an error when trying to post the form (despite a breakpoint in the code in controller being hit) which also states it "Cannot bind source type Umbraco.Web.Models.RenderModel to model type xxx"
This CANNOT be this difficult. I'm ready to throw laptop out window at this stage.
What am I doing wrong???? Or without seeing my code at least can anyone tell me how this is supposed to be done? How do you post a custom model form to an Umbraco 7.5 controller, with no CMS published content properties required?
As it stands, my View looks like this:
#inherits UmbracoViewPage<Models.Contact>
...
using (Html.BeginUmbracoForm<ContactController>("Contact", FormMethod.Post
My Controller looks like this:
public class ContactController : SurfaceController
{
public ActionResult Contact()
{
return View("~/Views/Contact.cshtml");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(Contact contactForm)
{
...
}
And my model looks like this:
public class Contact : RenderModel
{
public Contact() : base(UmbracoContext.Current.PublishedContentRequest.PublishedContent, UmbracoContext.Current.PublishedContentRequest.Culture)
{
}
public Contact(IPublishedContent content) : base(content, CultureInfo.CurrentUICulture)
{
}
[Display(Name = "First Name", Prompt = "First Name")]
public string FirstName { get; set; }
...
Update: If I use the model for my CMS page created automatically by models builder, the Get and Post work ok. However when I customise the model (i.e. I put a partial class of the same name in ~/App_Data/Models and regenerate models on Developer tab), the custom properties in my posted model are always null.
I can populate these manually from the request form variables, however this seems wrong and messy. What's going on here?
public class ContactPageController : SurfaceController
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactPage contactForm)
{
try
{
contactForm.FirstName = Request.Form["FirstName"];
contactForm.LastName = Request.Form["LastName"];
contactForm.EmailAddress = Request.Form["EmailAddress"];
contactForm.Telephone = Request.Form["Telephone"];
contactForm.Message = Request.Form["Message"];
var captchaIsValid = ReCaptcha.Validate(ConfigurationManager.AppSettings["ReCaptcha:SecretKey"]);
if (ModelState.IsValid && captchaIsValid)
{
// Do what you need
TempData["EmailSent"] = true;
return RedirectToCurrentUmbracoPage();
}
if (!captchaIsValid)
{
ModelState.AddModelError("ReCaptchaError", "Captcha validation failed - Please try again.");
}
return RedirectToCurrentUmbracoPage();
}
catch (Exception ex)
{
LogHelper.Error(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType, null, ex);
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
}
Further Info: Robert's approach first time, thanks for that. I had tried one approach using PartialViews and ChildActions but clearly I didn't do it correctly. Would I be right in saying the reason this approach is required at all (i.e. why you can't add the custom properties to the model the main view is bound to) is because I am using Models Builder?
So among the strange errors I received was one about 2 classes both wanting to represent the contact page, and another about it expecting one type of class in a dictionary (ContactPage) but receiving another (Contact) - even though I had made no reference to ContactPage in either view or controller. This suggests to me ModelsBuilder adds a mapping for document types to models on app startup behind the scenes? Which is maybe why you're better to take this approach of letting ModelsBuilder do its thing, and build your own model on top of that with a partial view in this way?
I've found the quality of documentation on this topic very poor indeed. Not sure if maybe the good stuff is behind closed doors, i.e. requires a paid Umbraco membership? For a supposedly open source system, that feels kinda shady to me.
Easy when you know how!!
For your situation, SurfaceController is the most likely candidate as you've surmised, however think of the actions in that Controller as applying to partial views, not the full view used by the page.
Don't try to use the ModelsBuilder ContactPage model, but rather create your own as you were originally (the original Contact model) - think of it as a Data Transfer Object perhaps if you do need to apply any of the properties back to the ContactPage model for any reason.
I've found I've had the greatest success with SurfaceController with the following conditions:
The Page Template does not inherit from the model intended for the Form; it inherits directly from the standard Umbraco PublishedContentModel or a ModelsBuilder generated model.
Implement a Partial View for the action defined in your SurfaceController - this view inherits from Umbraco.Web.Mvc.UmbracoViewPage<Contact> in your example.
The Partial View utilises BeginUmbracoForm<ContactController>, specifying POST action as one of the parameters (depending on which signature you're using)
You shouldn't need to populate any model properties using Request.Form like this.
For example, the code in one of my projects looks something like this:
SurfaceController
public class FormsController : SurfaceController
{
[ChildActionOnly]
public ActionResult ContactUs()
{
return PartialView(new ContactForm ());
}
[HttpPost]
public async Task<ActionResult> HandleContactUs(ContactForm model)
{
if (ModelState.IsValid)
{
if (!await model.SendMail(Umbraco)) // Do something with the model.
{
}
}
return RedirectToCurrentUmbracoPage(); // Send us back to the page our partial view is on
}
}
Partial View:
#inherits Umbraco.Web.Mvc.UmbracoViewPage<ContactForm>
#using Digitalsmith.ReCaptcha
#using (Html.BeginUmbracoForm<FormsController>("HandleContactUs"))
{
...
}
Contact Page Template:
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage<ContactPage>
#{
Layout = "_Layout.cshtml";
}
#Html.Action("ContactUs", "Forms")

Custom Class in Razor View?

Within my Controller I have a class called "ObjectData" that contains an ID and a string:
public class ObjectData
{
public int ObjectId { get; set; }
public string Name { get; set; }
}
I'm trying to pass a List of these to the view via ViewBag, but I don't know how to loop through the items in the array since the classtype isn't normal. I'm doing it this way because I don't want to pass a bunch of Objects and their data to the view, when I only need the ID and Name (is this a valid concern?).
I'm thinking of looping through like this:
foreach (ObjectData i in ViewBag.ParentSetIds)
{
#Html.ActionLink(i.Name, "Detail", new { objectId = i.ObjectId }, null)
}
But Razor doesn't recognize that class type. How can this be accomplished?
You must fully qualify your typename on the line:
foreach (Put.Your.Namespaces.Here.ObjectData i in ViewBag.ParentSetIds)
Razor do not use the same using declaration as your controllers. You may use web.config in the View directory to add such namespaces not to fully qualify it everytime.
Regarding the question if you should be concerned about passing such objects to view. No, there is no need to worry about it. I suggest to move the object ObjectData from controller to the folder next to the controllers folder named ModelView or ViewModel and create the class here. This is something like publicly accepted "hack" to have models which represents just another view on some "real" model. It is same like when you generate MVC3 project it creates for you file AccountModels.cs which contains exactly the same kind of models. But you find it in Model folder, while it may be discussed if it should be rather in ViewModel folder. Also, pass this data as Model not as the part ViewBag if it is not really just helping data.
You could use:
foreach (var i in ViewBag.ParentSetIds)
And let the compiler determine the namespace based on the ViewBag.ParentSetIds
Within my Controller I have a class called "ObjectData" that contains
an ID and a string:
Wait, what? Why do you have a class in your controller?
I'm trying to pass a List of these to the view via ViewBag,
Just use a view model. If you are, you can make List<ObjectData> part of it. In your controller, you load up that list (lets call it ObjectDataList), and send it to your view.
In the view (razor), you'd have something like:
#model MyProject.MyModel
#foreach(var i in Model.ObjectDataList)
{
#Html.ActionLink(i.Name, "Detail", new { objectId = i.ObjectId }, null)
}
Edit:
For clarification, your view model could be:
public class MyModel
{
public string Title {get;set;}
public List<ObjectData> ObjectDataList {get;set;}
}

MVC sending data from View to Controller

I am quite new to MVC 3.
I know how to send a strongly typed object from a Controller to a View. What I have now, is a View which contains a table/form which consists of that data.
The user can change that data whilst they're are in that View (html page).
When they click on "Save", how do I send the data from the View back to the Controller so that I can update my database.
Do I overload the Controller method so that it accepts a parameter of the model type? Can you please provide some source code.
(Please do not show code of persisting data to a database, I know how to do that part).
Thank you very much for helping me.
I would also prefer using #Html.BeginForm()
I like creating an action method made for my post data. So let's say you have a UserViewModel:
public class UserViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Then a UserController:
public class UserController
{
[HttpGet]
public ActionResult Edit(int id)
{
// Create your UserViewModel with the passed in Id. Get stuff from the db, etc...
var userViewModel = new UserViewModel();
// ...
return View(userViewModel);
}
[HttpPost]
public ActionResult Edit(UserViewModel userViewModel)
{
// This is the post method. MVC will bind the data from your
// view's form and put that data in the UserViewModel that is sent
// to this method.
// Validate the data and save to the database.
// Redirect to where the user needs to be.
}
}
I'm assuming you have a form in your view already. You'll want to make sure that the form posts the data to the correct action method. In my example, you'd create the form like so:
#model UserViewModel
#using (Html.BeginForm("Edit", "User", FormMethod.Post))
{
#Html.TextBoxFor(m => m.Name)
#Html.HiddenFor(m => m.Id)
}
The key to all this is the model binding that MVC does. Make use of the HTML helpers, like the Html.TextBoxFor I used. Also, you'll notice the top line of the view code I added. The #model tells the view you'll be sending it a UserViewModel. Let the engine do work for you.
Edit: Good call, did that all in Notepad, forgot a HiddenFor for the Id!
In MVC, the act of scraping out data from POST or GET HttpRequests is referred to as Model Binding - there are plenty of SO questions relating to this.
Out of the box, MVC will bind your Get and Post variables based on convention, e.g. a form field with the name 'FormName' will be bound back to a parameter on your controller with the same name.
Model binding also works for objects - MVC will instantiate an object for your controller, and set the properties with the same name as your form.

MVC 3/4 - Form data posted from paartial views and editor templates not reaching the Controller's action

I am working on an MVC 4 site that makes extensive use of partial views. On one page, however, I am using the the same partial view within nested partial views and my Model is nested as well. I checked it out in Fiddler, and the data is being posted as part of the form. When it hits my break point that I've set up in the action method of the controller those nested view models are coming in as null. I've tried using editor templates instead of partial views, but I had no luck on that one.
Has anyone experienced this behavior before, and is so, do you have any ideas as to what might be causing it?
I have had this same problem before because I accidentally passed a nested ViewModel property into my partial page.
If you are nesting partials you need to be careful about how you are passing in your model, for example:
Lets say this is your ViewModel:
public class Person
{
public string Name {get; set;}
public Address Address {get; set;}
}
public class Address
{
public string Line1 {get; set;}
//etc
}
And your controller action:
public ActionResult UpdatePerson(Person p)
{
}
If you have a separate view to display an Address make sure you do it like this:
#Html.RenderPartial("Address", Model)
And not like this:
#Html.RenderPartial("Address", Model.Address)
If you do the second example the name of the "TextboxFor" inputs will be named incorrectly for the model binder to be able to understand.
Another option would be to call out address specifically in your controller action like this to allow the model binder to see the address properly:
public ActionResult UpdatePerson(Person p, Address addr)
{
}

Can you remove the HTML Field Prefix from strongly typed models in MVC 3?

I have a view model like this:
public class EditVM
{
public Media.Domain.Entities.Movie Movie { get; set; }
public IEnumerable<Genre> Genres { get; set; }
}
Movie is the real entity I wish to edit. Genres is simply present to populate a drop down. I would prefer that when I call:
#Html.TextBoxFor(m => m.Movie.Title)
inside my strongly typed view that the input control have a name = "Title" instead of "Movie.Title"
I do not wish to split my view into partial views or lose my strongly typed view by using ViewData or the like.
Is there a way to express to the View that I do not wish to have the Movie. prefix? I noticed that you can set:
ViewData.TemplateInfo.HtmlFieldPrefix = "x";
in the controller, but unfortunately it seems only to allow adding an additional prefix. Setting it to "" does nothing.
Is there any work around for this? Or am I stuck with the unfortunate prefix that isn't really necessary in this case if I wish to keep strongly typed views and lambdas?
Thanks for any help.
Update:
Here's the controller actions to maybe make things a bit clearer.
public ActionResult Edit(int? id)
{
var vm = new EditVM
{
Movie = id.HasValue ? _movieSvc.Find(id.Value) : new Movie(),
Genres = AppData.ListGenres()
};
return View(vm);
}
[HttpPost]
public void Edit([Bind(Prefix = "Movie")]Movie m)
{
_movieSvc.AddOrUpdateMovie(m); //Exceptions handled elsewhere
}
No, in order to do what you want you would have to rewrite the Html helpers, and then you would have to write your own model binder. Seems like a lot of work for minimal gain.
The only choice is a Partial view in which you pass the Movie object as the model. However, this would require you to write your own model binder to have it be recognized.
The reason you have to do m.Movie.Title is so that the ID has the correct name, so the model binder can recognize it as a member of your model.
Based on your update:
Your options are:
Use non-strongly typed helpers.
Use a partial view.
Rewrite the stronly typed helpers
Don't use the helpers at all, and write the values to the HTML
Personally, i'd just use 1 or 2, probably 2.
EDIT:
Based on your update above. Change your code to this (note, Genres does not get posted back to the server, so m.Genres will just be null on postback):
[HttpPost]
public void Edit(EditVM m)
{
_movieSvc.AddOrUpdateMovie(m.Movie); //Exceptions handled elsewhere
}
EDIT:
I did just think of an alternative to this. You could simply do this:
#{ var Movie = Model.Movie; }
#Html.TextBoxFor(m => Movie.Title)
However, if there was a validation error, you would have to recreate your EditVM.
I have a view model like this
I think that you might have some misunderstanding about what a view model is. A view model shouldn't contain any reference to your domain models which is what those Movie and Genre classes seem to be. I mean creating a new class that you suffix with VM and in which you stuff all your domain models as properties is not really a view model. A view model is a class that is specifically designed to meet the requirements of your view.
A much more correct view model would looks like this:
public class EditVM
{
public string MovieTitle { get; set; }
public IEnumerable<GenreViewModel> Genres { get; set; }
}
and in your view you would have:
#Html.EditorFor(x => x.MovieTitle)
#Html.EditorFor(x => x.Genres)
Another option is to either use the TextBox(string name, object value) overload instead of the TextBoxFor:
#Html.TextBox("Title", Model.Movie.Title)
You could also specify the input tag HTML instead of using a helper.
Another option is to take EditVM as your postback parameter. This is what I would do. My post action parameter is always the same type of the .cshtml model. Yes there will be properties like lists that are null, but you just ignore those. It also allows you to gracefully handle post errors as well because if there is an error you'll need to return an instance of that view model anyhow, and have the values they submitted included. I usually have private methods or DB layer that handles retrieving the various lists that go into the ViewModel, since those will be empty on postback and will need to be repopulated, while not touching the properties that were in the post.
With your post method as it is now, if you need to return the same view, you've gotta create a new EditVM and then copy any posted values into it, and still populate the lists. With my method, you eliminate one of those mapping steps. If you are posting more than one thing, are you going to have umpteen different parameters on your post action? Just let them all come naturally into a single parameter typed to the EditVM of the View. While maybe having those null properties in the VM during the postback feels icky, you get a nice predictable consistency between View and postback IMO. You don't have to spend alot of time thinking about what combination of parameters on your post method will get you all the pieces of data from the form.

Resources