.NET MVC - Map text from TextBoxFor to an Array - c#

My model is set up so the controller can deliver a Json response of data from user input. The 'rgba' property of my model is an array of ints. If a user enters text of say '255, 0, 0, 1' into the TextBoxFor for 'rgba', the text is not mapping into an array (which I thought was supposed to happen automagically). Instead, int[0] is what makes it to the controller.
I've tried all the potential solutions I could find on here, including passing a FormCollection object to controller. I've tried to get the TextBoxFor value using JS/jQuery and manipulate the data, but can't figure out how to pass the manipulated data back to the model (this seems less than ideal, like there should be an easy way to do this in .Net).
Controller:
public class HomeController : Controller
{
public IActionResult NewColor()
{
Rootobject newColor = new Rootobject();
return View(newColor);
}
[HttpPost]
public IActionResult NewColor(Rootobject color)
{
var json = JsonConvert.SerializeObject(color);
return Json(json);
}
}
Model:
public class Rootobject
{
public Color[] colors { get; set; }
}
public class Color
{
public string color { get; set; }
public string category { get; set; }
public string type { get; set; }
public Code code { get; set; }
}
public class Code
{
public int[] rgba { get; set; }
public string hex { get; set; }
}
View:
#model WebAppPlayground.Models.Rootobject
#{
ViewData["Title"] = "New Color";
}
<style>
input:focus {
border-radius: 5px;
}
input {
padding: 2px;
border-radius: 5px;
border-style: ridge;
}
</style>
<h2>New Color</h2>
<h4>Color</h4>
<hr />
<center>
#using (Html.BeginForm("NewColor", "Home", FormMethod.Post, new { id = "form" }))
{
<table style="border-collapse:separate; border-spacing: 5px 5px">
<tbody>
<tr class="form-group" for="Color">
<td>Color</td>
<td>#Html.TextBoxFor(m => m.colors[0].color)</td>
</tr>
<tr class="form-group">
<td class="">Category</td>
<td>#Html.TextBoxFor(m => m.colors[0].category)</td>
</tr>
<tr class="form-group">
<td class="">Type</td>
<td>#Html.TextBoxFor(m => m.colors[0].type)</td>
</tr>
<tr class="form-group">
<td class="">rgba</td>
<td>#Html.TextBoxFor(m => m.colors[0].code.rgba, new { id = "rgba"})</td>
</tr>
<tr class="form-group">
<td class="">Hex</td>
<td>#Html.TextBoxFor(m => m.colors[0].code.hex)</td>
</tr>
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
}
<div>
<a asp-action="Index">Back to List</a>
</div>
</center>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
I would like my Controller to receive an array of Int's via rgba TextBoxFor where user enters text eg 255, 0, 0, 1.
I think there is something (obvious) I am overlooking or not understanding.
****Updated controller post method to remove added 'rgba_str' prop per #i_ll_be_back 's answer and deliver desired Json data:
[HttpPost]
public IActionResult NewColor(Rootobject color)
{
var json = JsonConvert.SerializeObject(color);
JObject jsonObject = JObject.Parse(json);
JObject code = (JObject)jsonObject["colors"][0]["code"];
code.Property("rgba_str").Remove();
return Json(jsonObject);
}

Try the following reshaping the Code class in the following:
public class Code
{
public int[] rgba { get; set; }
public string rgba_str
{
get
{
var res = "";
for(int i = 0; i < rgba.Length; i++) {
res += rgba[i] + (i != rgba.Length - 1 ? "," : "");
}
return res;
}
set
{
var strArray = value.Split(',');
rgba = new int[strArray.Length];
for(int i = 0; i < strArray.Length; i++) {
rgba[i] = int.Parse(strArray[i]);
}
}
}
public string hex { get; set; }
}
Now bind the TextBoxFor with the rgba_str field

Related

How to Bind a List in Model with HTML Form in ASP.NET Core?

I'm unsure what I'm doing wrong when trying to map form values to my model, FieldMappingCollection. I've been able to get the Id and Name back for the model but not the list of mappings, either the previously existing or newly created ones.
The page contains a text box for the mapping name and a table of rows which contains a select list and a text box. The FromField relates to the select list and the ToField relates to the text box.
I'm not super familiar with ASP.NET Core and even less familiar with older versions, so I'm unsure if #Html... is totally phased out or what the proper syntax is for automating binding or how to implement custom binding or HTML generators etc. I'm also not concerned with exactly how I should be handling the select list in particular, it's a bit of temporary code to get the page working and I'll come back to it later.
public class FieldMappingCollection
{
public int Id { get; set; }
public string Name { get; set; } = "";
public List<FieldMapping> FieldMappings { get; set; } = new();
}
public class FieldMapping
{
public int Id { get; set; }
public string FromField { get; set; } = "";
public string ToField { get; set; } = "";
}
public class MyController
{
public static List<string> AvailableFields = new()
{
// predefined field names for select list...
};
private readonly IMappingRepository m_repo;
// constructor, Index, etc...
public IActionResult EditMappingCollection(int id)
{
return View(m_repo.GetById(id));
}
[HttpPost]
public IActionResult EditMappingCollection(FieldMappingCollection model)
{
m_repo.Update(model);
m_repo.Save();
return RedirectToAction(nameof(Index));
}
}
#model FieldMappingCollection
// other required stuff...
<form asp-action="EditMappingCollection" method="post">
<input hidden asp-for="Id" type="number" />
<input type="text" asp-for="Name" />
<table class="table">
<thead>
<tr>
<th scope="col">From Field</th>
<th scope="col">To Field</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.FieldMappings.Count; i++)
{
<tr>
// Nothing to store the mapping Id yet
<td><select asp-for="FieldMappings[#i].FromField" style="width: 100%;" asp-items="#MyController.AvailableFields.Select(field => new SelectListItem(field, field, Model.FieldMappings[i].FromField == field))"></select></td>
<td><input asp-for="FieldMappings[#i].ToField" style="width: 100%;" type="text" placeholder="To Field" value="#Model.FieldMappings[i].ToField" /></td>
</tr>
}
</tbody>
</table>
<input class="btn btn-primary" type="submit" value="Save Changes" />
</form>
You could read the offcial document how to bind collection target:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0
I tried as below:
public class FieldMappingCollection
{
public FieldMappingCollection()
{
Selectlist = new List<SelectListItem>();
}
........
public List<SelectListItem> Selectlist { get; set; }
}
In controller I tried:
public IActionResult EditMappingCollection()
{
var FieldMapping1 = new FieldMapping() { Id = 1, FromField = "fromfield1" };
var FieldMapping2 = new FieldMapping() { Id = 2, FromField = "fromfield2" };
var FieldMappingCollection = new FieldMappingCollection();
FieldMappingCollection.Selectlist = new List<SelectListItem>()
{
new SelectListItem() { Text= FieldMapping1.FromField,Value=FieldMapping1.Id.ToString() },
new SelectListItem() { Text= FieldMapping2.FromField,Value=FieldMapping2.Id.ToString() }
};
return View(FieldMappingCollection);
}
In view:
<table class="table">
<thead>
<tr>
<th scope="col">From Field</th>
<th scope="col">To Field</th>
</tr>
</thead>
<tbody>
<select name=FieldMappings[0].FromField class="form-control" asp-items="#Model.Selectlist"></select>
<input name=FieldMappings[0].ToField value="1"/>
<select name=FieldMappings[1].FromField class="form-control" asp-items="#Model.Selectlist"></select>
<input name=FieldMappings[1].ToField value="2"/>
</tbody>
</table>
<input class="btn btn-primary" type="submit" value="Save Changes" />
Result:

Model is null on post back in ASP.NET MVC

I am trying to generate a list of checkboxes and post the selected item to post method of my controller but my view model is null.
Please see below code and help.
The view get invoke with the following button on another page -
<button class="btn btn-primary" id="historicalrecords"
onclick="location.href='#Url.Action("HistoricalWorkSubmissions", "Main", new {id= #Model.MessageIdsCombined.FirstOrDefault()})'">View Historical Works</button>
Model WorkSubmissions.cs:
public class HistoricalWorkSubmission
{
public string Society { get; set; }
public string Rsa { get; set; }
public DateTime ProcessingTime { get; set; }
public bool isSelected { get; set; }
}
public class HistoricalWorkSubmisssionViewModel
{
public List<HistoricalWorkSubmission> Submissions { get; set; }
}
Get method in MainController:
[HttpGet]
public async Task<ActionResult> HistoricalWorkSubmissions(string id)
{
WorkSearchViewModel workSearchViewModel = new WorkSearchViewModel
{
MessageId = id
};
var workSubmissions = await _swvManager.SearchAllWorkSubmissionsAsync(workSearchViewModel).ConfigureAwait(true);
return View("~/Views/Main/HistoricalWorkSubmissions.cshtml", workSubmissions);
}
HistoricalWorkSubmissions.cshtml:
#model SWV.WorkPicture.UI.Models.HistoricalWorkSubmisssionViewModel
#{
ViewBag.Title = "HistoricalSubmissions";
}
<h2>HistoricalSubmissions</h2>
#using (Html.BeginForm("HistoricalWorkSubmissions", "Main", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken();
<fieldset>
<div>
<table class="table-bordered">
#*#foreach (var submission in Model.Submissions)*#
#for (int i=0; i < Model.Submissions.Count(); i++)
{
var bmiWorks = Model.Submissions[i].Society + Model.Submissions[i].Rsa + " " + Model.Submissions[i].ProcessingTime;
<tr>
<td>
#Html.CheckBoxFor(m => Model.Submissions[i].isSelected)
#Html.Label(bmiWorks)
#Html.HiddenFor(m => Model.Submissions[i])
</td>
</tr>
}
</table>
<input class="button btn-primary" type="submit" value="Save"/>
</div>
</fieldset>
}
And finally post method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> HistoricalWorkSubmissions(HistoricalWorkSubmisssionViewModel workSubmissions)
{
WorkSearchViewModel workSearchViewModel = new WorkSearchViewModel();
workSearchViewModel.SwvId = "5124cfb4-afe8-4783-ab97-b9fbaaf6737d";
var workPicturesx = await _swvManager.SearchAllWorkSubmissionsAsync(workSearchViewModel).ConfigureAwait(true);
return View("~/Views/Main/HistoricalWorks.cshtml");
}
POST-
The value of abc is null in debugger. Please help.
Make sure the field names in the post match the model. The Label is not an issue, but CheckBoxFor and HiddenFor would generate Html input tags in the form posted to server.
#Html.CheckBoxFor(m => Model.Submissions[i].isSelected)
#Html.Label(bmiWorks)
#Html.HiddenFor(m => Model.Submissions[i].Society)
#Html.HiddenFor(m => Model.Submissions[i].Rsa)
#Html.HiddenFor(m => Model.Submissions[i].ProcessingTime)
Also I've removed the hidden field for submission because it's an object in your model, while we need to generate tags for each property instead.

Can't pass an enumerable model to a controller?

I'm a bit confused because I thought this a very straight-forward thing, it's possibly something simple tripping me up.
I have a view:
#model IEnumerable<CarViewModel>
#using (Html.BeginForm("SummarySaveAll", "VroomVroom", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
<table>
<thead>
<tr>
<th width="1">
#Html.DisplayNameFor(model => model.Driver)
</th>
<th width="1">
#Html.DisplayNameFor(model => model.Colour.Name)
</th>
</tr>
</thead>
<tbody>
#foreach (var element in Model)
{
<tr>
<td width="1">
#Html.DisplayFor(m => element.Driver)
</td>
<td width="1">
#Html.DropDownListFor(m => element.Colour, element.Colours, "Unknown")
</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save Changes" class="btn" />
#Html.ActionLink("Cancel Changes", "Index", null, new { #class = "btn" })
</div>
</div>
}
and the list/enumerable of CarViewModel is supposed to bounce back to the VroomVroom controller, action SummarySaveAll which it does - but the viewmodel on the page doesn't get passed back to it:
[HttpPost]
public ActionResult SummarySaveAll(IEnumerable<CarViewModel> summaries)
{
// Want to do stuff with summaries but it's always null
return View();
}
I tried to encapsulate the List in another ViewModel and cycle through elements using a for i loop but that wouldn't pass back to the controller either.
Surely it's possible to send a List or IEnumerable of models back to a controller?
My CarVM:
public class CarViewModel
{
[MaxLength(150)]
[Display(AutoGenerateField = true, Name = "Entered By")]
public string Driver { get; set; }
[Display(AutoGenerateField = true)]
public Colour Colour { get; set; }
[Key]
[Display(AutoGenerateField = false)]
public int Id { get; set; }
[Display(AutoGenerateField = false)]
public bool IsDeleted { get; set; } = false;
public IEnumerable<SelectListItem> Colours { get; set; }
public CarViewModel() { }
public CarViewModel(Model CarModel summaryModel, CarPropertyCollection propertyCollection)
{
Driver = summaryModel.Driver;
Id = summaryModel.Id;
IsDeleted = summaryModel.IsDeleted;
Colour = summaryModel.Colour == null ? null :
propertyCollection.Colours.Where(x => x.Id == summaryModel.Colour.Id).FirstOrDefault();
Colours = propertyCollection.Colours.Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.Name });
}
}
}
Must stress that Colour is a custom class but only has Id and Name properties
Colours doesn't relate to a specific car, it relates to cars in general, so rather than using a collection as your view model, create a wrapper:
class EditCarsViewModel
{
public IEnumerable<SelectListItem> Colours { get; set; }
public IList<CarViewModel> Cars { get; set; }
}
Then your view:
#model EditCarsViewModel
#for (int i = 0; i < Model.Cars.Length; i++)
{
<td>
#Html.DropDownListFor(m => Model.Cars[i].Colour, Model.Colours, "Unknown")
</td>
}
Any other CarViewModel properties will need their own input as well. HiddenFor can be used if they should be readonly:
#model EditCarsViewModel
#for (int i = 0; i < Model.Cars.Length; i++)
{
#Html.HiddenFor(m => Model.Cars[i].Id)
#Html.HiddenFor(m => Model.Cars[i].Driver)
<!-- etc. -->
<td>
#Html.DropDownListFor(m => Model.Cars[i].Colour.Id, Model.Colours, "Unknown")
</td>
}
And your controller:
[HttpPost]
public ActionResult SummarySaveAll(EditCarViewModel model)
{
// model.Cars should be populated
return View();
}
Note that an indexable collection, such as IList<T> should be used, as the form field names need to include the index to differentiate the items.
Edit by OP
The Colour class consists of a [Key] int Id property and a string Name property. For DropDownList items I had to make sure the Id property was specified on the m => Model.Cars[i].Colour.Id line otherwise that particular prop was coming back as null even though other items were coming through fine.
try
[HttpPost]
public ActionResult SummarySaveAll(IList<CarViewModel> summaries)
{
// Want to do stuff with summaries but it's always null
return View(summaries);
}
I've also added this model as a param for your view
This how you do it:
First my View which posts back to a controller named Home and an action named ListView:
#model List<MyModel>
#{
ViewData["Title"] = "Using a list as model";
}
<h1>#ViewData["Title"]</h1>
#using (Html.BeginForm("ListView", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
<table>
<thead>
<tr>
<th width="1">
Name
</th>
<th width="1">
Description
</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td width="1">
#Html.DisplayFor(m => Model[i].Name)
</td>
<td width="1">
#Html.TextBoxFor(m => Model[i].Description)
</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save Changes" class="btn" />
#Html.ActionLink("Cancel Changes", "Index", null, new { #class = "btn" })
</div>
</div>
}
Notice how I used an indexer to render the controls [i]
This is my model:
public class MyModel
{
public string Name { get; set; }
public string Description { get; set; }
}
This is my controller action:
[HttpPost]
public IActionResult ListView(IEnumerable<MyModel> model)
{
return View(model);
}
And this is the result:

aspnet core how to post data from View, if model complicated

I have some probles with using Post methods in controllers ASP.NET Core MVC. Maybe i using wrong architecture.
I have 2 Models from DB.
public class RecipeTable
{
public int Id { get; set; }
public string MetrologyRecipe { get; set; }
public string MetrologyTool { get; set; }
//other properties
}
public class ParamTable
{
public int AupId { get; set; }
public string ParamName{ get; set; }
public string RecipeName { get; set; }
public int? ParamOrderAuto { get; set; }
//other properties
}
And box for them. Because one entry in RecipeTable is associated with several entres from ParamTable.
public class FullModel
{
public List<ParamTable> ParamRows ;
public RecipeTable RecipeRow { set; get; }
public FullModel()
{
ParamRows = new List<ParamTable> ();
}
}
For [Get]"Edit" method this is work great.
[HttpGet]
public async Task<IActionResult> Edit(int? id, string recipeName)
{
var fullModel = new FullModel();
if (id == null) return NotFound();
fullModel.RecipeRow = await
_context.RecipeTable.SingleOrDefaultAsync(m => m.Id == id);
foreach (var row in _context.ParamTable)
if (recipeName == row.RecipeName)
fullModel.ParamRows.Add(row);
if (fullModel.RecipeRow.MetrologyRecipe == null) return NotFound();
return View(fullModel);
}
But for [Post]"Edit" this is does not work, of course.
Only Recipe part updated. I dont understand how post method get data from View. How work with this complicated models, when you can't change database and can't
specify connection directly in database designer.
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, FullModel fullModel)
{
if (id != fullModel.RecipeRow.Id) return NotFound();
if (ModelState.IsValid)
{
try
{
//**Here fullModel.ParamRows.Count = 0**
_context.Update(fullModel.RecipeRow);
await _context.SaveChangesAsync();
foreach (var elem in fullModel.ParamRows)
{
_context.Update(elem);
await _context.SaveChangesAsync();
}
}
catch (DbUpdateConcurrencyException)
{
if (!RecipeTableExists(fullModel.RecipeRow.Id))
return NotFound();
throw;
}
return RedirectToAction(nameof(Index));
}
return View(fullModel);
View part look like this:
#model FullModel
#{
ViewData["Title"] = "Edit";
}
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<table class="table" style="margin-left: -50px">
<thead>
<tr>
<th>Order</th>
<th>Param</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.ParamRows.Count; i++)
{
<tr>
<td>
<div class="form-group">
<input asp-for="#Model.ParamRows[i].ParamOrderAuto" type="text" class="form-control" />
</div>
</td>
<td>
<div class="form-group">
<input asp-for="#Model.ParamRows[i].ParamName" class="form-control" />
</div>
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
You can't use a foreach to iterate over the ParamRows. In order for the modelbinder to be able to bind this back to a list on post, the fields need to have names like ParamRows[N].ParamName. Using a foreach, you'll end up with names like row.ParamName instead, which the modelbinder will not recognize and will simply discard.
Instead, use a standard for loop:
#for (var i = 0; i < Model.ParamRows.Count; i++)
{
...
<input asp-for="ParamRows[i].ParamName" />
...
}
Alternatively, you can create an editor template for a ParamTable, i.e. ~/Views/Shared/EditorTemplates/ParamTable.cshtml, and put all the HTML (fields, labels, etc.) for editing a ParamTable instance in that. Then, instead of iterating over the items in ParamRows, you can simply do:
#Html.EditorFor(m => m.ParamRows)
Razor will automatically iterate over each item and render you editor template for each one, giving each field a correct name for binding.
You have a small problem with the FullModel, need to add get; set; methods. Just correct like this:
public class FullModel
{
public List<ParamTable> ParamRows { get; set; };
public RecipeTable RecipeRow { set; get; }
...

Pass data from Model to View

I have a model with a list of data and my View accesses the model properties and renders the database status. There are several boxes in my view which displays color according to their respective values in the database. And each div corresponds to one item in the database.
I know I haven't coded my View in a correct way but it is what I want my view to look like (each div ID corresponding to the respective boxID in the database and displaying its respective color). And I want 500 database values represented in the View.
#model List<My.Namespace.BoxStatusModel>
<table>
<tr class="first">
<td class="dclass1">
<div id="#boxStatus.boxID" class="top #boxStatus.Color">
<div id="#boxStatus.boxID" class="top #boxStatus.Color">
<div id="#boxStatus.boxID" class="bottom #boxStatus.Color">
<div id="#boxStatus.boxID" class="bottom #boxStatus.Color">
</td>
<td class="dclass2">
<div id="#boxStatus.boxID" class="top #boxStatus.Color">
<div id="#boxStatus.boxID" class="top #boxStatus.Color">
<div id="#boxStatus.boxID" class="bottom #boxStatus.Color">
<div id="#boxStatus.boxID" class="bottom #boxStatus.Color">
</td>
...
</tr>
<tr class="second">
...
</tr>
</table>
My Controller
public class HomeController : Controller
{
public ActionResult Index()
{
MySQL msql = new MySQL();
List<List<string>> results = msql.SelectTable("Select boxID, boxStatus from BoxStatusModel");
List<BoxStatusModel> model = new List<BoxStatusModel>();
for (int i = 0; i < results.Count; i++)
{
model.Add(new BoxStatusModel()
{
slotID = results[i][0],
currentStatus = results[i][1]
});
}
return View(model);
}
}
My ViewModel
namespace My.ViewModels
{
public class BoxStatusModel
{
public int ID { get; set; }
public string boxID { get; set; }
public string boxStatus { get; set; }
public string Color
{
get
{
switch (boxStatus)
{
case "busy":
return "red";
case "available":
return "green";
default:
return "";
}
}
}
}
}

Categories

Resources