How to handle PartialView with different model than parent View - c#

I'm using VS2012 RC with MVC4, bot for all intents and purposes let's pretend it's MVC3. I would like to know what the standard best practice(s) is on how to handle PartialViews with a form that uses a different model than the parent View.
For example, here is a view that displays a table of all the available Roles and also has a form that allows the user to add more roles.
Main View - Roles.cshtml:
#model IEnumerable<RobotDog.Models.RoleModel>
<table>
#foreach(var role in Model) {
<tr>
<td class="roleRow">#role.Role</td>
</tr>
}
</table>
<div class="modal hide">
#Html.Partial("_AddRolePartial")
</div>
_AddRolePartial.cshtml
#model RobotDog.Models.RoleModel
#using(Html.BeginForm("AddRole","Admin", FormMethod.Post)) {
#Html.TextBoxFor(x => x.Role, new { #class = "input-xlarge", #placeholder = "Role"})
<input type="submit" value="Submit" class="btn btn-primary btn-large"/>
}
Model:
public class RoleModel {
[Required]
[DataType(DataType.Text)]
[Display(Name = "Role")]
public string Role { get; set; }
}
Controller for View:
public ActionResult Roles() {
var model = from r in System.Web.Security.Roles.GetAllRoles()
select new RoleModel {Role = r};
return View(model);
}
Controller for PartialView:
[HttpPost]
public ActionResult AddRole(RoleModel model) {
try {
System.Web.Security.Roles.CreateRole(model.Role);
RedirectToAction("Roles");
} catch(Exception) {
ModelState.AddModelError("", "Role creation unsuccessful.");
}
return ????; // not sure how to pass ModelState back to partialView
}
I thought about creating a ViewModel that held RoleModel and IEnumerable<RoleModel> but it seems like there would be a more stream lined way to accomplish what I wanted without having to create a ViewModel everytime I wanted to use this PartialView.

I think you are asking how to pass a RoleModel to the add RoleModel modal popup. Since you are creating a new Role, I am assuming you are needing an empty model. You can either pass it in like below:
<div class="modal hide">
#Html.Partial("_AddRolePartial", new RoleModel())
</div>
Or just do a #Html.RenderAction("AddRole") with the supporing GET method of the controller to support populating the item.
public ActionResult AddRole() {
var model = new RoleModel();
//populate with any items needed for the Add Role Model View
return View(model);
}

I personally don't like using Partial views with forms, because Partial Views do not render submodels correctly (ie, they don't take into account the hierarchy of the model).
This is why Display and EditorTemplates exist. They work well for rendering specific data types.
However, in your case, since your view doesn't have any forms of its own, and the end result is just a single item of the collection of your parent model, then a Partial View is actually a better approach simply because you CAN pass a different model to it than the views uses.
As others have pointed out, you can easily pass an empty model to the partial as the second parameter. I don't like newing up new objects in the view, but it doesn't look like there's a lot of choice there, as the alternatives would be pretty messy.

How about the form post is changed to an ajax form post with a target update partial id being the div which you will add to the parent view (effectively surrounding Roles.cshtml).
Add a new action public ActionResult _Roles() which will return PartialView("Roles", model)
Next, in the Post Action, Return RedirectToAction(...Roles Partial Action ...) at the end and remove RedirectToAction("Roles") in the try.

Related

PartialView with a ViewModel on _Layout.cshtml

I have a layout page which has a partial view. The partial view needs to loop through a property on the view model to show a list of categories. When a category is displayed I need to show a list of documents in that category. /Home/Index works, but when I try to view /Documents/Category/{id}, I get an error:
Additional information: The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ViewModels.DocumentViewModel]', but this dictionary requires a model item of type 'ViewModels.HomeViewModel'.
_Layout.cshtml
...
<body>
#Html.Partial("_CategoryViewModel")
<div class="content">
#RenderBody()
</div>
HomeViewModel.cs
public class HomeViewModel {
...
public ICollection<DocumentCategory> Categories { get; set; }
public ICollection<Documents> Documents { get; set; }
...
}
_CategoryViewModel.cshtml (this should show a list of all categories)
#model ViewModels.HomeViewModel
...
#foreach (DocumentCategory item in Model.Categories)
{
<li>
<a href="#Url.Action("Category", "Documents", new { #id = #item.CategoryId })" title="View documents in the #item.Name category">
<span class="fa fa-files-o"></span> #item.Name
</a>
</li>
}
DocumentsController.cs
public ActionResult Category(int id)
{
var thisCategory = _ctx.Categories.Get(c => c.CategoryId == id).FirstOrDefault();
IEnumerable<DocumentViewModel> docs = null;
if(thisCategory == null)
{
TempData.Add("errorMessage", "Invalid category");
} else {
docs = thisCategory.Documents.ToList();
}
return View("Category", docs);
}
What's happening kind of makes sense - the PartialView on the Layout page needs to enumerate over a collection which isn't present in the ViewModel I'm using. I have no idea how to achieve this - the only way would seem to be to add a Categories property to every ViewModel in my site.
By default, using #Html.Partial() will pass the current model to the partial view, and because your Category.cshtml view uses #model List<DocumentViewModel>, then List<DocumentViewModel> is passed to a partial that expects HomeViewModel.
If you want to render a partial view for HomeViewModel on every page, then use #Html.Action() to call a ChildActionOnly method that returns the partial
[ChildActionOnly]
public ActionResult Categories
{
var model = new HomeViewModel()
{
.... // initialize properties
}
return PartialView("_CategoryViewModel", model)
}
and in the layout
#Html.Action("Categories", yourControllerName)
// or
#{ Html.RenderAction("Categories", yourControllerName); }
As I see it you have a few different alternatives.
1. Use Html.Action and create an Action that returns your view.
#Html.Action("Index", "Category") // Or your controller name.
I believe that there are some performance draw-backs with this approach because the whole MVC lifecycle will run again in order to render the result of the action. But then you can render the result of an action without having the correct model in the view that called it.
One may also argue that this breaks the MVC pattern, but it might be worth it.
2. Use a generic model (or an interface) in your _Layout.cshtml, and let your viewmodels inherit from that model.
In your _Layout.cshtml:
#model IBaseViewModel
And let all your viewmodels implement this interface.
public interface IBaseViewModel
{
ICollection<DocumentCategory> Categories { get; set; }
}
public interface IBaseViewModel<T> : IBaseViewModel
{
T ViewModel {get; set;}
}
Since you're placing #Html.Partial("_CategoryViewModel") in _Layout.cshtml I assume that it should be visible in all pages, so I think it's logical that all the controllers that are using _Layout.cshtml make sure that it gets the information it needs, and thus adding Categories to the model.
I use this approach all the time for stuff like breadcrumbs and menu-information (stuff that is used in all pages). Then I have a basecontroller that makes sure that Categories is populated with the correct info.

ASP Net MVC - Send different model on POST

is it possible to send an object from a strongly typed view to the Controller via Http-POST that does not equal the type of the original model.
For example:
I have a ViewModel like this:
public class PersonsViewModel
{
List<PersonViewModel> persons { get; set; }
PersonsViewModel() { }
}
public class PersonViewModel
{
//some properties
Person() { }
}
Now i have this View:
#model PersonsViewModel
<div>
#for(int i = 0; i > Model.persons.Count; i++)
{
#Html.EditorFor(Model.persons[i])
}
</div>
The editor could look like this:
#model PersonViewModel
<div>
#using (Html.Beginform("Postaction","Controller", FormMethod.Post)){
<div>
<!-- properties and textboxes here + submit button -->
</div>
}
<div>
The controller action
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Postaction(PersonViewModel model)
{
//do something
}
}
This doesn't work because it seems the Controller is expecting a PersonsViewModel object. My workaround so far is to make a "big" Form that contains all PersonViewModel and send the complete PersonsViewModel to the controller.
Is it somehow possible to pass only one PersonViewModel to the Controller although the view is strongly typed?
Kind regards,
Martin
It could be done:
When used with collections Html.EditorFor is smart enough to generate input names that contain index so ModelBinder could successfully create a model as a collection of objects. In your case since you want to have a separate form per PersonViewModel object, you could create a partial view as a template for editing PersonViewModel and use Html.RenderPartial helper:
Assuming you have _PersonViewModel.cshtml partial view
#for(int i = 0; i > Model.persons.Count; i++)
{
Html.RenderPartial("_PersonViewModel", Model.persons[i]);
}
in the _PersonViewModel.cshtml you can not use neither one of editor helpers such as Html.EditorFor, Html.TextboxFor because they are going to generate identical ids for the same properties so you will have to manually create html inputs:
#model PersonViewModel
<div>
#using (Html.Beginform("Postaction","Controller", FormMethod.Post)){
<div>
#*Nottice the usage of Html.NameFor(m=>m.FirstName) for generating a name property value *#
<input type="text" name="#Html.NameFor(m=>m.FirstName)" value="#Model.FirstName">
</div>
}
<div>
This way you can post a single PersonViewModel object to the controller action

Passing Data from View to Model in MVC in c#

I am new to ASP.NET MVC. Is it possible to pass data from view to model in mvc? this question was asked in interview.Please anyone give me right answer.
Thanks in Advance
To pass data to controller through model you should wrap all the inputs (checkboxes, textboxes, radio etc.) with <form ...> tag. You could do it using HTML tag or with help of ASP.NET MVC helper #Html.BeginForm(...). Once you submit your form, all the input data will be sent to a controller action and mapped to a targeted model. Please see an example:
Model:
public class UserModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
View:
#model UserModel
#using (Html.BeginForm("Search", "Events"))
{
#Html.TextBoxFor(m => m.FirstName)
#Html.TextBoxFor(m => m.LastName)
<input type="submit" value="Search" />
}
Controller:
public class EventsController: Controller
{
public ActionResult Search(UserModel model)
{
//do something
return View(); //return "Search" view to the user
//return View(model); //You can also return view with the model to the user
//return View("SpecificView"); //You can specify a concrete view name as well
}
}
No, we do not pass any information from view to model directly, both the view and model are different module. we can pass data, value or any information from view to model via controller.

Render Partial View only after HttpPost ActionHandler

I am making WCF service call using MyViewRequest view fields inside HttpPost ActionHandler. The goal is to show response using partial view, MyViewResponse
In brief I need to achieve these two items-
Disable load of partial view on first load.
Display Response (along with Request) after service call.
MyViewRequest.cshtml
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
//html code
}
</div>
<div id="dvResponse">
#Html.Partial("MyViewResponse");
</div>
Partial view: MyViewResponse.cshtml
#model MvcApplication3.Models.MyModel
#{
ViewBag.Title = "MyViewResponse";
}
<h2>MyView</h2>
#Html.Label(Model.MyName, "My Name")
This was pretty straight forward in Asp.Net using userControl, But stuck up here, How can we achieve this in MVC3.
I think the best way is to transfer your data using ViewModels. Let's assume you want to have an app something like stackoverflow where you have a question and user can post an answer and it will be shown after the post along with the question.
public class PostViewModel
{
public int ID { set;get;}
public string Text { set;get;}
public List<PostViewModel> Answers { set;get;}
public string NewAnswer { set;get;}
}
in your GET action, you show the question. Get the id from the url and get the Question details from your service/repositary.
public ActionResult Show(int id)
{
var post=new PostViewModel();
post=yourService.GetQuestionFromID(id);
post.Answers=yourService.GetAnswersFromQuestionID(id);
return View(post);
}
Assuming yourService.GetQuestionFromID method returns an object of PostViewModel with the propety values filled. The data can be fetched from your database or via a WCF service call. It is up to you. Also yourService.GetAnswersFromQuestionID method returns a list of PostViewModel to represent the Answers for that question. You may put both these into a single method called GetQuestionWithAnswers. I wrote 2 methods to make it more clear.
Now in your Show view
#model PostViewModel
#Html.LabelFor(x=>x.Text);
#using(Html.Beginform())
{
#Html.HiddenFor(x=>x.ID);
#Html.TextBoxFor(x=>x.NewAnswer)
<input type="submit" />
}
<h3>Answers</h3>
#if(Model.Answers!=null)
{
#Html.Partial("Responses",Model.Answers)
}
And your Partial view will be strongly typed to a collection of PostViewModel
#model List<PostViewModel>
#foreach(var item in Model)
{
<div> #item.Text </div>
}
Handling the postback is simple (HttpPost)
[HttpPost]
public ActionResult Show(PostViewModel model)
{
if(ModelState.IsValid)
{
//get your data from model.NewAnswer property and save to your data base
//or call WCF method to save it.
//After saving, Let's redirect to the GET action (PRG pattern)
return RedirectToAction("Show",new { #id=model.ID});
}
}

How to use multiple form elements in ASP.NET MVC

So I am new to ASP.NET MVC and I would like to create a view with a text box for each item in a collection. How do I do this, and how do I capture the information when it POSTs back? I have used forms and form elements to build static forms for a model, but never dynamically generated form elements based on a variable size collection.
I want to do something like this in mvc 3:
#foreach (Guest guest in Model.Guests)
{
<div>
First Name:<br />
#Html.TextBoxFor(???) #* I can't do x => x.FirstName here because
the model is of custom type Invite, and the
lambda wants to expose properties for that
type, and not the Guest in the foreach loop. *#
</div>
}
How do I do a text box for each guest? And how do I capture them in the action method that it posts back to?
Thanks for any help.
Definitely a job for an editor template. So in your view you put this single line:
#Html.EditorFor(x => x.Guests)
and inside the corresponding editor template (~/Views/Shared/EditorTemplates/Guest.cshtml)
#model AppName.Models.Guest
<div>
First Name:<br />
#Html.TextBoxFor(x => x.FirstName)
</div>
And that's about all.
Now the following actions will work out of the box:
public ActionResult Index(int id)
{
SomeViewModel model = ...
return View(model);
}
[HttpPost]
public ActionResult Index(SomeViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// TODO: do something with the model your got from the view
return RedirectToAction("Success");
}
Note that the name of the editor template is important. If the property in your view model is:
public IEnumerable<Guest> Guests { get; set; }
the editor template should be called Guest.cshtml. It will automatically be invoked for each element of the Guests collection and it will take care of properly generating ids and names of your inputs so that when you POST back everything works automatically.
Conclusion: everytime you write a loop (for or foreach) inside an ASP.NET MVC view you should know that you are doing it wrong and that there is a better way.
You can do this:
#for (int i = 0; i < Model.Guests.Count; i++) {
#Html.TextBoxFor(m => m.Guests.ToList()[i].FirstName)
}
There are more examples and details on this post by Haacked.
UPDATE: The controller post action should look like this:
[HttpPost]
public ActionResult Index(Room room)
{
return View();
}
In this example I'm considering that you have a Room class like this:
public class Room
{
public List<Guest> Guests { get; set; }
}
That's all, on the post action, you should have the Guests list correctly populated.

Categories