ASP Net MVC - Send different model on POST - c#

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

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.

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.

My partial view is not passing the values to the model correctly

I have a huge problem that I'm extremely confused on.
I'll give you a little background first.
I have two models, one that contains the other like
public class AccountViewModel
{
public ChargeViewModel chargeViewModel { get; set; }
other public values
}
Now I have a view, that references the partial view like so.
#model MyModels.AccountViewModel
[[Block_Header]]
#{ Html.RenderPartial("Header"); }
[[/Block_Header]]
[[ValidationSummary]]
#Html.ValidationSummary(false)
[[/ValidationSummary]]
[[Block_ReloadOptions]]
<div data-role="fieldcontain">
<fieldset data-role="controlgroup" data-type="horizontal" class="amount-bar">
<legend>Select an amount</legend>
#{
List<int> PresetValues = Main.Denominations.Split(',').Select(x => Convert.ToInt32(x.Trim())).ToList();
}
#if (PresetValues.Count > 0){
int i = 1;
foreach (int value in PresetValues) {
<input type="radio" name="amount" id="amount#(i)" value="#value" #(Model.chargeViewModel.ChargeAmount == value ? "checked" : "")>
<label for="amount#(i)">$#value</label>
i++;
}
}
<label for="select-amount">Select Amount</label>
<select name="select-amount" id="select-amount" class="select-amount" data-role="select">
<option value="">Other</option>
#if (Model.chargeViewModel.OtherValues.Count > 0){
foreach (int value in Model.chargeViewModel.OtherValues)
{
<option value="#value" #(Model.chargeViewModel.ChargeAmount == value ? "selected" : "") >$#value</option>
}
}
</select>
#Html.HiddenFor(m => m.chargeViewModel.ChargeAmount, new { id="hiddenAmount", value="0" })
</fieldset>
</div>
Some more forms here that arent relevant and then my partial view at the bottom as follows
#Html.Partial("MobileModelView", Model.chargeViewModel)
Now my partial view looks something like
#model VirtualNext.Web.Models.ChargeViewModel
<div class="ui-block-a">
<fieldset data-role="controlgroup" data-type="horizontal" class="field-margin validate expiry-select">
<label for="select-expiry-month" class="ui-hidden-accessible">Month</label>
#Html.DropDownListFor(m => m.ExpMonth, Model.ddlMonths, new { id = "select-expiry-month", #class= "validate"})
<label for="select-expiry-year" class="ui-hidden-accessible">Year</label>
#Html.DropDownListFor(m => m.ExpYear, Model.ddlYears, new { id = "select-expiry-year", #class= "validate validateExpiry validateYear" })
</fieldset>
</div>
So now I'm having a problem passing in the values of m.ExpMonth and m.ExpYear to my model in the controller. I have checked my Request.Form value and the values are populated, but not to the post method in my controller.
My controller looks like
public ActionResult MyAction(AccountViewModel model, ChargeViewModel chargeViewModel)
{}
Now I have all the data that I pass in directly through my main view, but nothing from the partial view is populating the model like I want it to. Now this is part of my mobile site I'm working on, and I actually have something extremely similar on my desktop side, so I'm really confused as to why it wont work. Any ideas/suggestions would be greatly appreciated.
You have wrong binding. You dont need 2 models in controller. You can bind you submodel with main model like
partial view
#model VirtualNext.Web.Models.ChargeViewModel
#{
ViewData.TemplateInfo.HtmlFieldPrefix = "AccountViewModel"; //bind to main model
}
controller with 1 model
public ActionResult MyAction(AccountViewModel model)
Additionaly i recomend you to pass the Prefix by ViewData to make your view more flexible.
#model VirtualNext.Web.Models.ChargeViewModel
#{
ViewData.TemplateInfo.HtmlFieldPrefix = ViewData["htmlprefix"];
}
With this option you can use this view in many controller actions. just pass the prefix and bind your submodel to any model you need.
Put your partial view in the Views/Shared/EditorTemplates folder.
Then, replace Html.Partial line in your View with this:
#Html.EditorFor(model => model.ChargeViewModel, "MobileModelView")
And, replace your Action with this:
public ActionResult MyAction(AccountViewModel model)
{
// Some code...
}

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 handle PartialView with different model than parent View

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.

Categories