I want to pass an input model from a partial view to a controller. I'm rather new to MVC so still trying to understand how the default model binder works.
Via AJAX (listBox) a controller passes back a partial view and inserts into table id=searchResults.
#model ViewModels.LocationViewModel
#using (Ajax.BeginForm("ProcessSearch", "SearchR", new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "searchResults",
}))
{
<div>#Html.ListBoxFor(xxx)</div>
<input id="Search" type="submit" value="Search" />
}
Here is the controller and ViewModel that populates the partial view
public class OrderViewModel
{
public string Description { get; set; }
public string Status { get; set; }
}
public ActionResult ProcessSearch(SearchViewModel search)
{
select new OrderViewModel{
Status=f.STATUS,
Description=f.DESCRIPTION}).ToList();
return PartialView(model);
}
In the same main view I have this form that I want I want to bind to yet another view model. I simply don't understand how to implement the default binder from the model of the partial view. I apologize if I didn't explain this correctly. I hope it makes sense.
#using (Html.BeginForm("Testing", "SearchR", FormMethod.Post))
{
<div>#Html.DropDownListFor(yyy)</div>
<input id="Reshop" type="submit" value="Reshop" />
}
<table id="searchResults"></table>
public ActionResult Testing(RSOrderViewModel rOrder)
{
return Content("hey");
}
public class RSOrderViewModel
{
public string yyy { get; set; }
public IEnumerable<OrderViewModel> sovm { get; set; }
}
#model List<ViewModels.OrderViewModel>
#{ViewData.TemplateInfo.HtmlFieldPrefix = "sovm";
}
<table id="searchResults">
<tr>
<th>Order Id</th>
<th>Order Detail</tr>
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => x[i].OrderId)
</td>
<td>
#Html.DisplayFor(x => x[i].OrderDetail)
</td>
</tr>
}
</table>
The table is outside the second form. So when you POST to the Testing action all that is sent to the controller is the value of the dropdown list. If you want to send the collection that's stored in this table you will have to either use AJAX or put the table inside the form:
#using (Html.BeginForm("Testing", "SearchR", FormMethod.Post))
{
<div>#Html.DropDownListFor(yyy)</div>
<table id="searchResults"></table>
<input id="Reshop" type="submit" value="Reshop" />
}
Now of course putting the table inside the form doesn't mean that it will send anything to the server when you submit the form. You need to put input fields (hidden if you don't want them to be visible to the user) that will contain the values that will be POSTed back. Also those input field names must follow the standard convention for binding to a list.
You haven't actually shown how does the partial view look like but here's an example of how it might look so that the convention is respected. For example let's suppose that you have 2 properties in your OrderViewModel that you want to be bound: OrderId and OrderDetail:
#model IEnumerable<OrderViewModel>
#{
// We set the name prefix for input fields to "sovm"
// because inside your RSOrderViewModel the collection
// property you want to bind to this table is called sovm
// and in order to respect the convention the input names
// must be in the following form "sovm[0].OrderId", "sovm[1].OrderId", ...
ViewData.TemplateInfo.HtmlFieldPrefix = "sovm";
}
<thead>
<tr>
<th>Order Id</th>
<th>Order detail</th>
</tr>
</thead>
<tbody>
#Html.EditorForModel()
</tbody>
and then you could have an editor template which will be rendered for each element of the model (~/Views/Shared/EditorTemplates/OrderViewModel.cshtml):
#model OrderViewModel
<tr>
<td>#Html.EditorFor(x => x.OrderId)</td>
<td>#Html.EditorFor(x => x.OrderDetail)</td>
</tr>
The name of the template is the name of the type used in the collection you want to bind to (IEnumerable<OrderViewModel> sovm { get; set; } => OrderViewModel.cshtml). It must also be placed inside the ~/Views/Shared/EditorTemplates folder if it can be reused between multiple controllers or if it is specific to the current controller inside the ~/Views/XXX/EditorTemplates folder where XXX is the current controller.
Related
I have these 2 models:
public class Invoice
{
public string InvoiceID {get; set; }
public List<InvoiceElement> InvoiceElements {get; set;}
[...other fields...]
}
public class InvoiceElement
{
public string InvoiceElementID {get; set; }
[ForeignKey("Invoice")]
public string InvoiceID { get; set; }
public virtual Invoice Invoice { get; set; }
public string Item {get; set;}
[...other fields...]
}
I am unable to make a CREATE view for new Invoices that lets me add InvoiceElements.
I want to have a "CurrentInvoiceElements" table where to dinamically add rows.
Just trying to making it simple. You can use the name attribute (the attribute that asp.net uses for modal binding) and post a list along with other properties of the class. You can use javaScript to append new elements to your form. Using the above modals you've provided, I have written a simple example using simple jQuery functions.
Razor View:
<button class="btn btn-success" id="add_btn">Add Invoice Element</button>
#using (#Html.BeginForm("SaveInvoice", "Invoice", FormMethod.Post))
{
<!--Other modal attributes inputs goes here -->
<!--You can use normal html helper extensions -->
<table id="element_table">
<thead>
<tr>
<td>Element Id</td>
<td>Item</td>
</tr>
</thead>
<tbody>
<tr>
<td><input name="invoice.InvoiceElements[0].Item" id="InvoiceElements[0].Item" /></td>
<td><input name="invoice.InvoiceElements[0].InvoiceElementID" id="InvoiceElements[0].InvoiceElementID" /></td>
</tr>
</tbody>
</table>
<input type="submit" />
}
JavaScript:
<script type="text/javascript">
$("#add_btn").on('click', function (e) {
var table = $("#element_table");
var idx = $(table).find("tbody>tr").length;
var htmlToAppend = `<tr>
<td><input name="invoice.InvoiceElements[${idx}].Item" id="InvoiceElements[${idx}].Item" /></td>
<td><input name="invoice.InvoiceElements[${idx}].InvoiceElementID" id="InvoiceElements[${idx}].InvoiceElementID" /></td>
</tr>`;
$(table).find("tbody").append(htmlToAppend);
});
</script>
Controller / Action:
[HttpPost]
public ActionResult SaveInvoice(Invoice invoice)
{
/* your logic here */
if(ModelState.IsValid)
_invoiceBusiness.SaveInvoice(invoice);
return View();
}
Please make sure the variable name in the parameter of the action method matches the name used in the name attribute of the input tag. i.e. name = "invoice.***" public ActionResult SaveInvoice(Invoice invoice)
I followed this solution: https://github.com/danludwig/BeginCollectionItem some years ago, and worked fine.
If I'm not mistaken, at the time I managed to do it using only the HTML Helper: HtmlPrefixScopeExtensions. Then just make sure the name you give on your View when you do Html.BeginCollectionItem("name") is exactly the same as your collection on your ViewModel.
That's for binding back to the controller.
With this, you can dynamically add rows using AJAX per example.
I hope it's clear enough. If you don't understand it I may make a Github repository with this.
I have a view model derived from a base view model. The base VM has two properties, bool IsDisplayable & int MyValue.
I would like to create a display template that would take those two values and either display MyValue or not depending on the value of IsDisplayable.
This pair of values is part of all my view models and I would like to use one Display Template for all my views that use models derived from my base model.
My question is how would I pass this part of each view model to my Display Template?
#Html.DisplayFor(modelItem => item.MyValue,"MyDisplayTemplate")
My Display Template:
#model WhatModelGoesHere
#if (Model.IsDisplayable)
{
<td>
#Html.DisplayFor(model => model.MyValue)
</td>
}
The difficulty is trying to make the display template generic enough to work with any of my view models.
I suggest you create a common base class for other models to inherit.
Put the two fields IsDisplayable and MyValue that you need to display in the public template in the common base class, so that you only need to reference the common base class in the template.
And I suggest you use partial view to implement Display Template.
Common base class:
public class GenricModel
{
public bool IsDisplayable { get; set; }
public int MyValue { get; set; }
}
View Model to inhert common base class:
public class ViewModel1 : GenricModel
{
public int Id { get; set; }
public string Name { get; set; }
}
In the view to show ViewModel1 data, you can use #Html.Partial("_TemplateView", item) to call Display Template, here is an example based on ViewModel1:
#model IEnumerable<WebApplication_core.Models.ViewModel1>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Index</h1>
<table class="table-bordered">
<tr>
<th>
Id
</th>
<th>
Name
</th>
<th>
MyValue
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#item.Id
</td>
<td>
#item.Name
</td>
#Html.Partial("_TemplateView", item)
</tr>
}
</table>
Then, create a partial view named _TemplateView.cshtml in Views folder.
Generally speaking, partial views are created under Views/Shared path in the project, you can refer to this.
#model WebApplication_core.Models.GenricModel
#if (Model.IsDisplayable)
{
<td>
#Html.DisplayFor(model => Model.MyValue)
</td>
}
Hello I'm trying to pass some database info from my controller to my view, but don't find the best way to do it. I'm populating the model in my controller, but I need to populate those values from database. I have a class called DataAccess which is the one that contains all my queries but not sure where I should put the logic to populate. I would say a for loop in my controller to populate the values, but seems to fail since I'm declaring the SchedulerViewModel there
The idea is having my values next to a radio button, so when selecting a radio button, I can "detect" the value and do something with that option....any suggestion would be appreciated...
My model:
public class SchedulerViewModel
{
public string theValue { get; set; }
public SelectListItem[] Items { get; set; }
}
My Controller:
public ActionResult Scheduler()
{
//DataAccess dataAccess = new DataAccess();
//for loop here???
var model = new SchedulerViewModel
{
Items = new[]
{
new SelectListItem { Value = "U", Text = "USA" }
}
};
return View(model);
}
My view:
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Items.Count(); i++)
{
#Html.RadioButtonFor(x => x. theValue, Model.Items[i].Value, new { id = "item_" + i })
#Html.Label("item_" + i, Model.Items[i].Text)
<br />
}
}
Ideally you would have a service class that handles your database access. You shouldn't directly invoke the data layer from the controller, although nothing prevents you from doing it. For simplicity, I'm just putting calling the data access directly in the controller. The idea is that you need to return a collection of data, here an IEnumerable, in the View at the controller level so that the View can display this data.
Controller:
[HttpGet]
public ActionResult Index()
{
KnowledgeBaseEntities context = new KnowledgeBaseEntities();
IEnumerable<ISSUE> issues = context.ISSUES;
if(issues == null)
{
return HttpNotFound();
}
return View(issues);
}
View:
As you can see I'm referencing the collection of data that I'm expecting from the controller.
#model IEnumerable<ISSUE>
In this case it's an IEnumerable just like I had in the controller. Then you'll notice I'm referencing a Model object when I iterate the model.
#foreach (var item in Model)
Then I'm looping through each row of the model in order to add table rows to the table. Because we're using Model Binding from the Entity Framework. We're using Razor Syntax. You also notice I'm using Action Links for each row in the last column. This allows me to Edit, Delete or provide Details for a row of data. However, I will need to invoke another Controller Action for that. For example, you'd have an Edit controller action method that returns a single ISSUE to an Edit View.
#model IEnumerable<ISSUE>
#{
ViewBag.Title = "Knowledge Base Issues";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2 class="line">All Issues</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="flat">
<tr>
<th>#Html.DisplayNameFor(model => model.KEYWORDS)</th>
<th>#Html.DisplayNameFor(model => model.SUBJECT)</th>
<th>#Html.DisplayNameFor(model => model.DATE_ENTERED)</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>#Html.DisplayFor(modelItem => item.KEYWORDS)</td>
<td>#Html.DisplayFor(modelItem => item.SUBJECT)</td>
<td>#Html.DisplayFor(modelItem => item.DATE_ENTERED)</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.ISSUE_ID }) |
#Html.ActionLink("Details", "Details", new { id=item.ISSUE_ID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.ISSUE_ID })
</td>
</tr>
}
In my code I cannot read a nested object value on form post.
Wrog way to edit List property in one Object:
#{
var contatore = 0;
foreach (var item in Model.Movimenti)
{
var movimento = item;
<tr>
<td align="left">
#*Imposto gli Hidden per tutte le proprietà che devo recuperare al post*#
#Html.HiddenFor(x => movimento.Prodotto.Descrizione, "Movimenti[" + contatore + "].Prodotto.Descrizione")
#Html.DisplayFor(x => movimento.Prodotto.Descrizione, "Movimenti[" + contatore + "].Prodotto.Descrizione")
</td>
<td>#Html.EditorFor(x => movimento.Aum, "CurrencyDisabled", "Movimenti[" + contatore + "].AUM")</td>
</tr>
contatore++;
}
}
This is the correct way to edit List property in one Object:
The code:
#using AI.Business.Models
#model Operazione
#{ ViewBag.Title = "Simulatore"; }
#using (Html.BeginForm("CreaOperazione", "Operativita", FormMethod.Post))
{
// Imposto gli Hidden per tutte le proprietà che devo recuperare al post
#Html.HiddenFor(x => x.RapportoModel.TipoRapportoId)
<table width="100%" class="display" id="Simulatore" cellspacing="0">
<thead>
<tr>
<th class="dt-head-left">Linea</th>
<th>AUM</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(x => x.Movimenti)
</tbody>
</table>
<button id="btnSalva" name="btnSalva" type="submit" style="float: right;">Salva Operazione</button>
}
With the editor assuggested:
#model AI.Business.Models.Movimento
<tr>
<td align="left">
#Html.HiddenFor(x => x.Prodotto.Descrizione)
#Html.DisplayFor(x => x.Prodotto.Descrizione)</td>
<td>#Html.EditorFor(x => x.Aum, "CurrencyDisabled")</td>
And this is my object:
public class Movimento
{
public int Id { get; set; }
public ProdottoModel Prodotto { get; set; }
public decimal Aum { get; set; }
}
And the Object Prodotto:
public class ProdottoModel
{
[Key]
public int ID { get; set; }
public string Descrizione { get; set; }
}
In my Actionresult the property Descrizione is null:
[HttpPost]
public ActionResult CreaOperazione(Operazione operazione)
{
if (ModelState.IsValid)
{
// Do something
}
else
ImpostaErrore(ModelState);
return View("PaginaSimulatore", operazione);
}
Open the images:
At my first access to the page the property Prodotto.Descrizione is populated
When i raise the form post event this property was sent with a null value
I'm not sure how you're getting any of this to work, but it's a total fluke. HiddenFor, for example, has no parameter that let's you specify the name value for the field. Instead, where you're trying to do that, the parameter is actually for htmlAttributes, which expects either an anonymous object or IDictionary. The only reason you aren't getting errors is because string is technically an object, but it will never do anything in this context.
The same goes for the rest of your helper calls. With EditorFor, in particular, the second param where you're passing "CurrencyDisabled", is for specifying the editor template that should be used, and the third param is for additionalViewData, which just appends items to ViewData within the context of the editor template.
Long and short, none of this works how you think it does. Plainly and simply, if you need to work with a collection, you need to use for rather than foreach. The expression that you pass to the *For family of helpers is not just about identifying a property however you can get to it; it must be a bindable expression, i.e. something Razor can use to create a name for the form field that will line up to something on your model on post. In order for that to happen, the names must be something like Movimenti[N].Prodotto.Descrizione, and the only way to get that is to call the helper like:
#Html.HiddenFor(m => m.Movimenti[i].Prodotto.Descrizione)
Where i would be the iterator from your for loop.
Someone please help me return this list properly from my view. I don't see why I'm returning null for my fieldModelList I try to pass to the controller...
Here is my view:
#model List<Regions.SOA.UI.CopyBookSchemaCreator.Models.FieldModel>
<script type="text/javascript" src="~/Scripts/jquery-ui-1.8.11.min.js"></script>
#using (Html.BeginForm("GetResponse", "TestMethods", FormMethod.Post))
{
<table id="tblMethods">
<tr>
<th>
Property Name
</th>
<th>
Request
</th>
</tr>
#foreach (FieldModel fieldModel in Model)
{
<tr>
<td>
#Html.DisplayFor(m => fieldModel.PropertyName)
</td>
<td>
#Html.TextBoxFor(m => fieldModel.PropertyValue)
</td>
</tr>
}
</table>
<div>
<input type="submit"/>
</div>
and here is my controller:
[HttpPost]
public ActionResult GetResponse(List<FieldModel> fieldModelList)
{
return GetResponse(fieldModelList);
}
I am hitting the HttpPost method but if I place a breakpoint just inside it, I am returning null for the fieldModelList right off the bat, which I was hoping would be a list of the values I entered into the texboxes on the view that is of model FieldModel...
I think something is wrong with my logic versus my syntax, or as maybe as well as my syntax, but basically what I want to do is return back a list of type FieldModel with each corresponding PropertyName and PropertyValue to the controller. I noticed I am not passing any kind of id parameter in my BeginForm statement in the view. Do I need one here?
Just in case, here is my model class for FieldModel:
namespace Regions.SOA.UI.CopyBookSchemaCreator.Models
{
public class FieldModel
{
[Display(Name = "Property")]
public string PropertyName { get; set; }
[Display(Name = "Value")]
public string PropertyValue { get; set; }
}
}
Phil Haack wrote an article some time ago explaining how to bind collections (ICollection) to view models. It goes into additional detail about creating an editor template, which you could certainly do as well.
Basically, you need to prefix the HTML elements' name attributes with an index.
<input type="text" name="[0].PropertyName" value="Curious George" />
<input type="text" name="[0].PropertyValue" value="H.A. Rey" />
<input type="text" name="[1].PropertyName" value="Ender's Game" />
<input type="text" name="[1].PropertyValue" value="Orson Scott Card" />
Then, your controller could bind the collection of FieldModel
[HttpPost]
public ActionResult GetResponse(List<FieldModel> fieldModelList)
{
return GetResponse(fieldModelList);
}
I'm not 100% sure the following would name the attributes correctly (I'd recommend using the editor template) but you could easily use the htmlAttributes argument and give it a name using the index.
#for(int i = 0;i < Model.Count;i++)
{
<tr>
<td>
#Html.DisplayFor(m => m[i].PropertyName)
</td>
<td>
#Html.TextBoxFor(m => m[i].PropertyValue)
</td>
</tr>
}
Editor Template
If you wanted to go as far as adding an editor template, add a partial view named FieldModel.ascx to /Views/Shared that is strongly typed to a FieldModel
#model Regions.SOA.UI.CopyBookSchemaCreator.Models.FieldModel
#Html.TextBoxFor(m => m.PropertyName) #* This might be a label? *#
#Html.TextBoxFor(m => m.PropertyValue)
And, then the part of your view responsible for rendering the collection would look like:
#for (int i = 0; i < Model.Count; i++) {
#Html.EditorFor(m => m[i]);
}