Cannot submit two dimension `List` from view to controller - c#

Class CreateActivityViewModelwill be passed to View.
public class CreateActivityViewModel
{
public List<List<int>> SelectedDepartmentIds { get; set; }
...
}
In the View, using these code to generate html code:
<div class="form-group">
#Html.Label("报名范围", htmlAttributes: new {#class = "col-xs-12"})
<div>
<button id="repeat" type="button">增加单位范围</button>
</div>
#for (var i = 0; i < Model.MaxDepartmentLevel; i++)
{
<div class="col-xs-6">
#if (i == 0)
{
#Html.Label((i + 1) + "级单位", htmlAttributes: new {#class = "control-label col-md-2"})
#Html.DropDownListFor(x => x.SelectedDepartmentIds[0][i], Model.Departments,
"请选择单位", new {#class = "form-control department"})
}
else
{
#Html.Label((1 + i) + "级单位", htmlAttributes: new {#class = "control-label col-md-2"})
#Html.DropDownListFor(x => x.SelectedDepartmentIds[0][i], Enumerable.Empty<SelectListItem>(),
"所有单位", new {#class = "form-control department"})
}
</div>
}
</div>
Here x => x.SelectedDepartmentIds[0][i] is the two dimention List that cannot be passed back to server. When debuging, createActivityViewModel.SelectedDepartmentIds=null. However, Using Chrome Dev Tools to see what is passing to server, I saw SelectedDepartmentIds do submit:
In fact, I have tried almost the same work in other place, the only difference I think is that the success work is passing one dimension List and now two dimension. Does ASP.NET not support binding two dimension List or what?

This should work as expected. From the comments it seems that you have forgotten to include the property to the [Bind(Include=...)] attribute that your Create action is using. This being said, since you are using a view model you don't need any Bind attributes. The properties that are part of the view model will simply be bound. It is much less error prone and explicit.

Related

ASP.NET Core view only partially post back the ViewModel

I have a ViewModel that includes 4 different classes with a lot of properties.
Now, if I make a submit in my view, only the properties that are bound to an input field are posted back, which is bad, since I have a table based on one of those classes (which are gone after the submit).
I know I could handle it like this:
#Html.HiddenFor(m => m.class.property)
But with the amount of properties, this seems like a very inconvenient approach.
I could also just get my model via query again, but this also seems like an approach that's not right.
Is there a better approach than those I mentioned above?
Edit:
And I tried it this way too:
#foreach (var property in Model.Mandant.GetType().GetProperties())
{
#Html.HiddenFor(p => property)
}
But for some reasons this doesn't work, sadly.
Edit2 for clarification:
I have an viewModel like this:
public class ValidationViewModel
{
public M_IV_INVOICE Invoice { get; set; }
public List<M_IP_INVOICE_POS> Positions { get;}
public S_KR_KREDITOR_ Kreditor { get; set; }
public S_MD_MANDANTEN Mandant { get; set; }
public ValidationViewModel() { }
public ValidationViewModel(int invoiceId)
{
Invoice = CRUD.GetFirstOrDefault(new M_IV_INVOICE(), string.Format(#"WHERE M_IV_ID IN ({0})", invoiceId));
Positions = Invoice != null ? CRUD.GetList(new M_IP_INVOICE_POS(), string.Format(#"WHERE M_IP_INVOICEID IN ({0})", Invoice.M_IV_ID)) : null;
Kreditor = Invoice?.M_IV_KREDITOR != null ? CRUD.GetFirstOrDefault(new S_KR_KREDITOR_(), string.Format(#"WHERE S_KR_KREDITOR IN ({0})", Invoice.M_IV_KREDITOR), "S_KR_KREDITOR") : null;
Mandant = Invoice?.M_IV_MANDANT != null ? CRUD.GetFirstOrDefault(new S_MD_MANDANTEN(), string.Format(#"WHERE S_MD_FIR IN ({0})", Invoice.M_IV_MANDANT)) : null;
}
}
I have a view which looks like this:
#using (Html.BeginForm("Update", "Home"))
{
#Html.ValidationSummary();
<div class="container">
<div class="row">
<div class="form-group col-2">
#*#Html.LabelFor(m => m.Mandant.S_MD_FIR)
#Html.TextBoxFor(m => m.Mandant.S_MD_FIR, string.Format("{0} - {1}", Model.Mandant?.S_MD_FIR, Model.Mandant.S_MD_BEZEICHNUNG), new { #class = "form-control", placeholder = "0" })
#Html.ValidationMessageFor(m => m.Mandant.S_MD_FIR)*#
#Html.Label("Mandant")
<input readonly value="#string.Format("{0} - {1}", Model.Mandant?.S_MD_FIR, Model.Mandant?.S_MD_BEZEICHNUNG)" class="form-control" placeholder="" id="Mandant" />
</div>
<div class="form-group col-2">
#Html.LabelFor(m => m.Invoice.M_IV_KREDITOR)
#Html.TextBoxFor(m => m.Invoice.M_IV_KREDITOR, new { #class = "form-control", placeholder = "0" })
#Html.ValidationMessageFor(m => m.Invoice.M_IV_KREDITOR)
</div>
<div class="form-group col-2">
#Html.LabelFor(m => m.Invoice.M_IV_INVOICEDATE)
#Html.TextBoxFor(m => m.Invoice.M_IV_INVOICEDATE, new { #class = "form-control", placeholder = "0" })
#Html.ValidationMessageFor(m => m.Invoice.M_IV_INVOICEDATE)
</div>
</div>
.....
<button type="submit" class="btn btn-primary">Update</button>
}
Now, if I press the button and call my update method:
[HttpPost]
public IActionResult Update(ValidationViewModel validationViewModel)
{
if (ModelState.IsValid)
{
validationViewModel.Invoice.Update();
// TODO additional logic
}
return View("Index", validationViewModel);
}
Everything that's not bound to an input field or listed with:
#Html.HiddenFor(m => m.Invoice.M_IV_ID)
is nulled.
And now I'm locking for a more convenient way to circumvent this, if even possible.
ASP.NET does not provide the behavior that you're looking for, as far as I know, there is no #Html.HiddenForModel(model => model.child). I guess it's because if you have that level of data complexity on your view, you might want to simplify it and to present only what's necessary for the view. But you can try to "hack" it with #Html.EditorForModel(), wrapped within an invisible container on your view, something like the snippet below:
<div style="display: none">
#Html.EditorForModel()
#Html.EditorFor(model => model.child1)
#Html.EditorFor(model => model.child2)
#Html.EditorFor(model => model.child3)
</div>
To further elaborate on my problem with the List object:
If I do it the way Dorin Raba showed in the answer:
#Html.EditorFor(model => model.child1)
it won't work because, all the textfields are named without an index, so it's not possible to match the values back.
But I can simply do it this way:
#for (int i = 0; i < Model.M_IP_INVOICE_POS.Count; i++)
{
#Html.EditorFor(model => model.M_IP_INVOICE_POS[i])
}
and now every editor field has the correct name and I get the positions back to the controller.
So no need to query my Positions every time I want to Update something.

How do I make the date field #Html.EditorFor receive date coming from a ViewBag

How do I make the date field #Html.EditorFor receive date coming from a ViewBag.
Controller
var searchDateBD = db.Sequence.ToList().Select(l => l.DateFlow).Max();
ViewBag.date = searchDateBD;
View
#Html.EditorFor(model => model.Sequence.DateFlow,
ViewBag.date,
new { htmlAttributes = new { #class = "form-control input-lg" } })
In this way above developed by me is not correct.
How to do so that the field receives the date coming from the viewbag of the controller
You don't need ViewBag here. You can set the model property in the controller action and pass the model object to view back in the line returning View like:
model.Sequence.DateFlow = db.Sequence.Select(l => l.DateFlow).Max();
return View(model);
You also do not need to materialize the result by calling ToList() and leave the Max item to be calculates on the database server.
and in your view it would be like:
#Html.EditorFor(model => model.Sequence.DateFlow,
new { htmlAttributes = new { #class = "form-control input-lg" } })
You can overwrite the value using attribute #Value:
htmlAttributes = new { #class = "form-control input-lg", #Value = ViewBag.date }
Note that #Value is written with an uppercase letter V.

Creating and reading a calculation formula in mvc

I am having a Controller Calculations where I have a property called Formula. The main idea behind the Calculation is to perform operations using objects from other tables in the Database , here for instance SetValues.
What I am trying to achieve
I have a textbox for entering the Formula. I want to make sure that the variables used in the Formula are existing in the database. I can display the names of all the available rows in the table SetValues. I would like to make the values being displayed from the Setvalues click-able so that the value being clicked would be displayed on the textbox. I will keep a table with the mathematical operators as well which would also work in the same way. How can I make the TextBox for Formula the way I want? And how can I make the Names to be entered into the TextBox when clicked.
When the formula has been saved I want to use the formula to perform calculations based on the values of the SetValues(Assume SetValue has properties Name and Values(int)). I understand that I need to parse the Formula to perform the calculation which I have not yet tried, but could find other examples where the same has been performed. I have done something similar but with a Calculation string with numbers alone. I have not done something that would parse a formula and then assign values to it. Is it possible to achieve the parsing?
Calculation View
#foreach(var item in ViewBag.SetValues
{
<p>#item.Name</p>
}
#Html.LabelFor(model => model.CalculationName, htmlAttributes: new { #class = "control-label col-md-2" })
#Html.EditorFor(model => model.CalculationName, new { htmlAttributes = new { #class = "form-control" } })
#Html.LabelFor(model => model.CalculationFormula, htmlAttributes: new { #class = "control-label col-md-2" })
#Html.EditorFor(model => model.CalculationFormula, new { htmlAttributes = new { #class = "form-control" } })
<input type="submit" value="Create" class="btn btn-default" />
In a ViewBag I have passed the SetValue and the elements are displayed.
The Model classes and Controller are not relevant for the output I guess. But if it is required comment. Further details can be added.
Update : adding Wireframe sample
To solve your question regarding to adding the formula to the formula textbox when clicked. Here is some sample code. I use jQuery with a double click event.
HTML
<b>Formulas:</b><br/>
<i>Double click to append the formula.</i></br>
<select id="formulas" size="5">
<option>d_0</option>
<option>v_fix</option>
<option>f_Fc</option>
<option>a_sc</option>
</select>
<select id="operands" size="5">
<option>+</option>
<option>-</option>
<option>*</option>
<option>/</option>
</select>
<br/>
<span id="message" style="color: red"></span>
<hr/>
<b>Formula:</b><br/>
<textarea id="formula"></textarea>
JavaScript
$(document.ready( function() {
var formulas = $("#formulas");
var formula = $("#formula");
var operands = $("#operands");
var message = $("#message");
var expectFormula = true;
formulas.dblclick(function() {
if(expectFormula == true) {
var currentFormula = formula.val();
var appendFormula = formulas.val();
var newFormula = (currentFormula + " " + appendFormula).trim();
formula.val(newFormula);
expectFormula = false;
return;
}
message.html("Operand expected.");
});
operands.dblclick(function() {
if(expectFormula == false) {
var currentFormula = formula.val();
var appendFormula = operands.val();
var newFormula = (currentFormula + " " + appendFormula).trim();
formula.val(newFormula);
expectFormula = true;
return;
}
message.html("Formula expected.");
});
});
JSFiddle
https://jsfiddle.net/L1r3xdvq/1/
I am by no means a JavaScript expert, but this should demonstrate on how to do this.
Do note that you have to double check the user input on the server, even though it is created with JavaScript, it is possible to enter whatever you want and send this as formula.

Setting the default value of an enum dropdown in Razor

I'm trying to create an Item edit screen where the user can set a property of the Item, the ItemType. Ideally, when the user returns to the screen, the dropdown would display the ItemType already associated with the Item.
As it is, regardless of what the item.ItemType is, the dropdown will not reflect that in the dropdown. Is there a way around this?
For reference, my code at the moment is:
<div class="form-group">
#Html.LabelFor(model => model.ItemType, new { #class = "control-label col-xs-4" })
<div class="col-xs-8">
#Html.DropDownListFor(model => model.ItemType, (SelectList)ViewBag.ItemType, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.ItemType, String.Empty, new { #class = "text-danger" })
</div>
</div>
The ViewBag is set with the following:
var ItemType = Enum.GetValues(typeof(ItemType));
ViewBag.ItemType = new SelectList(ItemType);
If you're using ASP.NET MVC 5, try just using the EnumHelper.GetSelectList method. Then you don't need ViewBag.ItemType.
#Html.DropDownListFor(model => model.ItemType, EnumHelper.GetSelectList(typeof(ItemType)), new { #class = "form-control" })
If not, you might need to specify the data value and data text fields of the select list.
var itemTypes = (from ItemType i in Enum.GetValues(typeof(ItemType))
select new SelectListItem { Text = i.ToString(), Value = i.ToString() }).ToList();
ViewBag.ItemType = itemTypes;
Then since it's an IEnumerable<SelectListItem> you'll need to change your cast.
#Html.DropDownListFor(model => model.ItemType, (IEnumerable<SelectListItem>)ViewBag.ItemType, new { #class = "form-control" })
Eventually I found a fix - manual creation of the list.
<select class="form-control valid" data-val="true"
data-val-required="The Item Type field is required." id="ItemType" name="ItemType"
aria-required="true" aria-invalid="false" aria-describedby="ItemType-error">
#foreach(var item in (IEnumerable<SelectListItem>)ViewBag.ItemType)
{
<option value="#item.Value" #(item.Selected ? "selected" : "")>#item.Text</option>
}
</select>
Try to keep as much of the logic outside of the View and in the Controller.
I saw in your self answer that it looks like you have an enum selected from wihin your controller.
I have a DropDownList in one of my apps that contains a list of Enums. It also has a default value selected, but also has specific enums available to the user. The default selection can be set from within the controller.
This example is based on what my needs were, so you'll need to adapt to your case.
In the controller:
public ActionResult Index()
{
ViewBag.NominationStatuses = GetStatusSelectListForProcessView(status)
}
private SelectList GetStatusSelectListForProcessView(string status)
{
var statuses = new List<NominationStatus>(); //NominationStatus is Enum
statuses.Add(NominationStatus.NotQualified);
statuses.Add(NominationStatus.Sanitized);
statuses.Add(NominationStatus.Eligible);
statuses.Add(NominationStatus.Awarded);
var statusesSelectList = statuses
.Select(s => new SelectListItem
{
Value = s.ToString(),
Text = s.ToString()
});
return new SelectList(statusesSelectList, "Value", "Text", status);
}
In the view:
#Html.DropDownList("Status", (SelectList)ViewBag.NominationStatuses)
This approach will automatically set the default item to the enum that was selected in the controller.

System.Random in ViewBag not displaying as string?

All I am trying to do is create a random number in the Controller and pass it to the View. However when I run the application the View only displays 'System.Random' not a generated value.
Here is my controller:
// GET: /Products/Create
public ActionResult Create()
{
Random randomID = new Random(Guid.NewGuid().GetHashCode());
randomID.Next(20, 5000);
ViewBag.random = randomID.ToString();
ViewData["random"] = randomID.ToString();
TempData["random"] = randomID.ToString();
return View();
}
I tried the ViewBag, ViewData, and TempData and they all display 'System.Random.'
Here is my View:
#model application.Models.Product
#{
ViewBag.Title = "Create Product";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.Product_ID, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.Product_ID, new { #readonly = "readonly" })
#Html.TextBox("random", (string)#ViewBag.random, new { #readonly = true })
#ViewBag.random
#ViewData["random"]
#TempData["random"]
#Html.ValidationMessageFor(model => model.Product_ID)
</div>
</div>
I'm sorry the View is a little messy but I tried all the approaches I could find. What am I missing? I really don't want to have the change the Model. I tried Googling this for hours and nothing can solve my problem. Also is this the simplest approach to creating a random ID number for a product? Any help would be appreciated THANK YOU!
Random.Next actually returns a value, and doesn't mutate the Random object at all. Simply calling ToString on a Random object will always return "System.Random" (as it will for every other class that doesn't override ToString.
You need to put the generated value in the ViewBag:
public ActionResult Create()
{
Random random = new Random(Guid.NewGuid().GetHashCode());
int randomID = random.Next(20, 5000);
ViewBag.random = randomID.ToString();
return View();
}
randomId.Next() returns an integer, you need something more like this:
// GET: /Products/Create
public ActionResult Create()
{
Random randomID = new Random(Guid.NewGuid().GetHashCode());
int randomNumber = randomID.Next(20, 5000);
ViewBag.random = randomNumber.ToString();
return View();
}

Categories

Resources