So here's my problem. I've been trying to solve this for weeks and nothing, so I'm biting the bullet and asking for help.
Basically, I'm trying to write a computer encyclopedia-cum-inventory management-cum-auditing program. Seeing that MVC is all the rage these days I decided to step out of my comfort zone of classic .NET and try MVC.
I have a model with partially the following fields:
public class SoundCard
{
public Guid Id { get; set; }
...
public virtual List<SoundChipset>? SoundChipsets { get; set; }
...
public virtual List<MidiSynthChipset>? MidiSynthChipsets { get; set; }
...
}
The model is scaffolded into creating a controller and then a set of view pages. The add view works brilliantly and I could add sound and midi chipsets as needed. The edit view is where my problem lies: I could add new sound chipsets and midi chipsets, but could not remove the added ones.
The partial code for the controller for edit is as follows:
public async Task<IActionResult> Edit(Guid id, [Bind("Id,ModelSeries,ModelName,ModelNumber,ReleaseDate,HasCDROMInterface,HasSCSIPort,HasIDEPort,HasNECCDPort,HasMatsushitaCDPort,HasWaveBlasterPort,HasRAMForWaveTable,RAMSizeKB,HasGamePort,HasMPU401Port,numAudioOutPorts,numAudioInPorts,numAudioBiDirectionalPorts,numCoaxInPorts,numCoaxOutPorts,numOpticalInPorts,numOpticalOutPorts,numMidiInPorts,numMidiOutPorts")] SoundCard soundCard)
{
...
string[] selectedSndChipsets = Request.Form["lbSoundChipsetsSelected"].ToArray();
List<SoundChipset> sndChipset = new List<SoundChipset>();
foreach (string uuid in selectedSndChipsets)
{
sndChipset.Add(Factories.SoundChipsetDDLFactory.getSoundChipsetByUUID(_context, uuid));
}
soundCard.SoundChipsets = sndChipset;
string[] selectedMidChipsets = Request.Form["lbMidiChipsetsSelected"].ToArray();
List<MidiSynthChipset> MidChipsets = new List<MidiSynthChipset>();
foreach (string uuid in selectedMidChipsets)
{
MidChipsets.Add(Factories.MidiSynthChipsetDDLFactory.getMidiSynthChipsetByUUID(_context, uuid));
}
soundCard.MidiSynthChipsets = MidChipsets;
_context.Update(soundCard);
await _context.SaveChangesAsync();
...
So, practically recreating the Sound Chipsets and Midi Chipsets lists from scratch every single time. Problem is, the program treats the list as new objects to add to the existing list, it does not erase the current list despite the list being a new one!
I've tried to apply a Clear() command to the list but instead the program tossed an NullReferenceException which is puzzling because the list is supposed to be populated.
For completeness sake, here's part of the code for the edit frontend. It's partially JS to handle moving items between two boxes:
<label asp-for="SoundChipsets" class="control-label"></label>
<table>
<tr>
<th>Available</th>
<th>↔</th>
<th>Selected</th>
</tr>
<tr>
<td>
#Html.ListBox("lbAllSoundChipsets",(IEnumerable<SelectListItem>)ViewBag.SoundChipsets, new {#id="lbAllSoundChipsets", #style="min-width: 250px;"})
</td>
<td>
<input onclick="Javascript:SwitchListBoxItems('lbAllSoundChipsets', 'lbSoundChipsetsSelected');" type="button" value="→" /><br />
<input onclick="Javascript:SwitchListBoxItems('lbSoundChipsetsSelected', 'lbAllSoundChipsets');" type="button" value="←" />
</td>
<td>
#Html.ListBox("lbSoundChipsetsSelected",(IEnumerable<SelectListItem>)ViewBag.SelectedSoundChipsets, new {#id="lbSoundChipsetsSelected", #style="min-width: 250px;"})
</td>
</tr>
</table>
</div>
<div class="form-group">
<label asp-for="MidiSynthChipsets" class="control-label"></label>
<table>
<tr>
<th>Available</th>
<th>↔</th>
<th>Selected</th>
</tr>
<tr>
<td>
#Html.ListBox("lbAllMidiChipsets",(IEnumerable<SelectListItem>)ViewBag.MidiSynthChipsets, new {#id="lbAllMidiChipsets", #style="min-width: 250px;"})
</td>
<td>
<input onclick="Javascript:SwitchListBoxItems('lbAllMidiChipsets', 'lbMidiChipsetsSelected');" type="button" value="→" /><br />
<input onclick="Javascript:SwitchListBoxItems('lbMidiChipsetsSelected', 'lbAllMidiChipsets');" type="button" value="←" />
</td>
<td>
#Html.ListBox("lbMidiChipsetsSelected",(IEnumerable<SelectListItem>)ViewBag.SelectedMidiSynthChipsets, new {#id="lbMidiChipsetsSelected", #style="min-width: 250px;"})
</td>
</tr>
</table>
And the JS code:
function SwitchListBoxItems(sourceListItem, targetListItem) {
var src = document.getElementById(sourceListItem);
var dest = document.getElementById(targetListItem);
if (dest != null && src != null) {
while (src.options.selectedIndex >= 0) {
var lstItemNew = new Option(); // Create a new instance of ListItem
lstItemNew.text = src.options[src.options.selectedIndex].text;
lstItemNew.value = src.options[src.options.selectedIndex].value;
dest.options[dest.length] = lstItemNew;
src.remove(src.options.selectedIndex);
}
}
So yeah, if someone can point me in the right direction to get the system to delete items.
Thanks in advance.
Related
I have posted the relevant code to this issue below. My problem is, let's say, the database is displaying NA, I want to edit it and put in 1.1, or some number. Instead of updating and saving this new number, it deletes NA and does not update or save anything, so I know it is doing something, but I'm not sure where I have gone wrong. If I change the type in the model to int or object, it gives an error for conversion to string. Can someone help please? Thank you!
Controller:
public ActionResult Edit ()
{
return View ();
}
[HttpPost]
public ActionResult Edit(MyIssue issues)
{
var model = new Test.Models.Tables();
if (ModelState.IsValid)
{
db.Entry(issues).State = EntityState.Modified;
issues.Number = model.Number;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(issues);
}
Model:
namespace Test.Models
{
public class Tables: DbContext
{
public string Number { get; set; }
}
}
View:
<td>
#if (issue.Number != null)
{
#Html.HiddenFor(modelItem => issue.Number)
#Html.DisplayFor(modelItem => issue.Number)
<text>|</text>
<h5 id="editclass">
#Html.ActionLink("Edit", "Page1", new { id = issue.Number })
</h5>
using (Html.BeginForm())
{
#Html.ValidationSummary(true) {
<fieldset>
#Html.HiddenFor(modelItem => issue.Number)
<div class="editor-field">
#Html.EditorFor(modelItem => issue.Number)
#Html.ValidationMessageFor(modelItem => issue.Number)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
}
}
</td>
From your code, I assume that the code you shown is inside a loop where issue is the loop iterator variable. So razor will generate an input field with name "issue.Number". When the form is submitted, model binder cannot bind this form value to the Number property of your MyIssue object ! So it gets the default null value and your code is assigning the null value as the Number property and saving it.
You should generate an input field with name="Number". You may use the Html.TextBox helper method to do so.
#foreach (var issue in SomeCollection)
{
<tr>
<td>
#using (Html.BeginForm())
{
<!-- Other fields also goes here -->
#Html.TextBox("Number",issue.Number)
<input type="submit" />
}
</td>
</tr>
}
I have a table in my view containing 2 columns.
My program looks for the number of files in a folder and populates the 2nd column with the names, one in each row.
The first column contains a check box if the right column is populated.
How can I access these checkboxes and filenames in my controller after HttpPost?
(The number of files is not fixed, so the number of checkboxes is also not fixed)
I need to run a stored procedure afterwards if a particular file is present.
Model
public class ImportFiles
{
public string FileName;
public bool FileSelected { get; set; }
}
Controller
[HttpGet]
public ActionResult ImportFiles()
{
string folderpath = #"C:\Users\uvaish\Documents\Visual Studio 2010\Projects\MVCDemo\MVCDemo\Models"; //path
string filename = "*";
string[] fileList = System.IO.Directory.GetFiles(folderpath,filename);//getting the file names from the folder as an array
List<ImportFiles> inputFiles = new List<ImportFiles>(fileList.Length);//making a list of same number of elements as the number of files
foreach (string str in fileList)
{
ImportFiles inputFile = new ImportFiles();
inputFile.FileName = Path.GetFileName(str);
inputFile.FileSelected = false;
inputFiles.Add(inputFile);//creating a list of files and passing to the view
}
return View (inputFiles);
}
View
<table width="550px" class="mGrid table">
<tr>
<th>
Select
</th>
<th>
File Name
</th>
</tr>
#foreach (ImportFiles importFiles in #Model)
{
<tr>
<td>
#Html.EditorFor(importFile => #importFiles.FileSelected)
</td>
<td>
#importFiles.FileName
</td>
</tr>
}
</table>
</div>
<table>
<tr>
<td>
<div>
<input type="submit" value="Load Selected Files" />
</div>
</td>
<td>
</td>
<td>
<div>
<input type="submit" onclick="importCancel()" value="Cancel" />
</div>
</td>
</tr>
</table>
So , from the view I want to access the files corresponding to the checked checkboxes.
after clicking on the submit button , I should be able to manipulate the files that are selected.
I am not sure how to use the HttpPost method after this , could you please help?
I have enclosed the table in view in :
#using (Html.BeginForm("LoadSelectedFiles", "Admin", FormMethod.Post))
But I don't know how to write the LoadSelectedFiles controller method next, I want to access each of the ticked files now.
I have a MVC4 internet application with a form for creating user accounts. The form validation works but while the input fails validation no error message is displayed. It still prevents submitting until the validation problem is solved but there is no text
Razor View Form
<h2>Create New Account</h2>
<fieldset>
<legend></legend>
#using (Html.BeginForm("CreateUser",null)){
#Html.AntiForgeryToken()
<table class="create">
<tr>
<td colspan="2"><b>New Account</b>
</tr>
<tr>
<td>#Html.DisplayNameFor(model=>model.UserName)</td><td>#Html.TextBoxFor(model=>model.UserName)</td>
<td>#Html.DisplayNameFor(model=>model.EmailAddress)</td><td>#Html.TextBoxFor(model=>model.EmailAddress)</td>
<td><input type="submit" value="Create User" /></td>
</tr>
</table>
}
</fieldset>
#Html.ValidationSummary()
The bundles used include the validation files
bundles.Add(new ScriptBundle("~/bundles/asset").Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/jquery-ui-{version}.js",
"~/Scripts/jquery.validate*",
"~/Scripts/jquery.unobtrusive*"));
The Model used is an entity model, I have added a partial class to annotate the validation requirements
[MetadataType(typeof(UserProfileMetadata))]
public partial class UserProfile
{
//Empty Class just required for adding class level attribute
}
public class UserProfileMetadata
{
//Fields from user profile requiring annotations
[EmailAddress]
[Required]
[Display(Name = "Email Address")]
public string EmailAddress { get; set; }
[Required]
public string UserName { get; set; }
}
The validation working but now showing the message makes me think it must be a markup error but I just can't see it.
Moving the ValidationSummary inside the form will fix it.
<h2>Create New Account</h2>
<fieldset>
<legend></legend>
#using (Html.BeginForm("CreateUser",null)){
#Html.ValidationSummary()
#Html.AntiForgeryToken()
<table class="create">
<tr>
<td colspan="2"><b>New Account</b>
</tr>
<tr>
<td>#Html.DisplayNameFor(model=>model.UserName)</td> <td>#Html.TextBoxFor(model=>model.UserName)</td>
<td>#Html.DisplayNameFor(model=>model.EmailAddress)</td><td>#Html.TextBoxFor(model=>model.EmailAddress)</td>
<td><input type="submit" value="Create User" /></td>
</tr>
</table>
}
</fieldset>
For anyone stumbling across this who does not wish to be restricted to moving the #Html.ValidationSummary into the form, i.e. it lives in _Layout.cshtml, and you like it there, here's a way around that.
The below method is apparently what is used by Microsoft to populate #Html.ValidationSummary. In it's standard form, it looks for data-valmsg-summary-true in $('this'). this in this case is the calling form. Well, my #Html.ValidationSummary lives in the pageBody <div> on _Layout.cshtml to keep it DRY.
function onErrors(event, validator) { // '#pageBody' is the containing element
var container = $('#pageBody').find("[data-valmsg-summary=true]"),
list = container.find("ul"); if (list && list.length && validator.errorList.length) {
list.empty(); container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
So far, I've only changed this:
var container = $('this').find("[data-valmsg-summary=true]")
to this:
var container = $('#pageBody').find("[data-valmsg-summary=true]")
Now, I trigger my validation from a button click. To get onErrors(event, validator) to fire, I used the following jQuery:
$('#btnSave').click(function () {
if ($('form').valid()) {
// Do stuff
} else {
onErrors(event, $('form').data('validator'));
}
});
Voila, #Html.ValidationSummary populates even when using jQuery.unobtrusive.
A big thanks to Leniel Macaferi for pointing me in the right direction here:
http://www.leniel.net/2013/08/customizing-aspnet-mvc-html-validation-summary-with-bootstrap-3-panel.html#sthash.jGRguVuV.qSjYUlhS.dpbs
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]);
}
I have a textarea that represents a description field. The descriptions have commas so when trying to split the field's descriptions the data is not parsed correctly. How can I get each row's description correctly.
var DescList = FormValues["Item.Description"].Split(',').Select(item => item).ToList<string>();
//will not work for obvious reasons. Comma delimited FormCollection has commas to identify separate row data.
It seems like Microsoft designed the FormsCollection without the textarea control in mind. A text area with commas will not work when trying to access each value. What is interesting is that the _entriestables property has it in the perfect format but they chose to make it a private property. Very frustrating.
`
Here is the important part of my viewmodel.
public class TenantViewModel
{
public Tenant Tenant { get; set; }
public Site Site { get; set; }
}
My view is populated like this:
if (Model != null && Model.Tenant != null && Model.Tenant.Site != null && Model.Tenant.Site.Count() > 0)
{<div class="detailsbox_view">
<table id="tblTenantSites">
<tr>
<th>#Html.LabelFor(item => item.Site.Title)</th>
<th>#Html.LabelFor(item => item.Site.Description)</th>
</tr>
#foreach (var Item in Model.Tenant.Sites)
{
<tr>
#Html.HiddenFor(modelItem => Item.SiteId)
<td>
#Html.EditorFor(modelItem => Item.Title)
</td>
<td>
#Html.TextAreaFor(modelItem => Item.Description, new {#width="400" })
</td>
</tr> }
</table>
As you see this site table is a child of Tenant object. This child record does not get automatically updated using this method but the Tenant data does automatically get updated. This is the reason I tried the FormColelction instead.
Is there something I am missing to make this work?
try with this useful function
ValueProviderResult Match=FormCollection.GetValue("ValueProvider");
When you have multiple fields with the same name attribute, they'll come back into your FormCollection as an array. So upon posting a view like this:
<form action="/Home/MyAction">
<textarea id="row_one_description" name="description">
First row's description
</textarea>
<textarea id="row_two_description" name="description">
Second row's description
</textarea>
<input type="submit" value="Submit" />
</form>
you could do something like this in your action
[HttpPost]
public ActionResult MyAction(FormCollection collection)
{
var descriptionArray = collection["description"];
string firstRowDescription = descriptionArray[0];
string secondRowDescription = descriptionArray[1];
}
I must note that this is not the recommended way of dealing with posted data. You should instead be building your view using data from a view model and using strongly typed html helpers to render your controls. That way when you post, your action can take the ViewModel as a parameter. Its properties will be automatically bound and you will have a nice object to play with.
[HttpPost]
public ActionResult MyAction(MyViewModel viewModel)
{
foreach (var row in viewModel.Rows)
{
string description = row.Description;
}
}
EDIT
I'm still assuming a lot about your ViewModel but perhaps try this:
<table id="tblTenantSites">
<tr>
<th>#Html.LabelFor(model => model.Site.Title)</th>
<th>#Html.LabelFor(model => model.Site.Description)</th>
</tr>
#for (var i = i < Model.Tenants.Sites.Count(); i++) {
<tr>
#Html.HiddenFor(model => model.Tenants.Sites[i].SiteId)
<td>
#Html.EditorFor(model => model.Tenants.Sites[i].Title)
</td>
<td>
#Html.TextAreaFor(model => model.Tenants.Sites[i].Description, new { #width="400" } )
</td>
</tr>
}
</table>
You could also try ,
string Match=FormCollection.GetValue("ValueProvider").AttemptedValue;