I am building a small ASP.NET Core 3.1 web application.
When application loads, a default View is presented with a set of records. On the View, I have a panel with search filters. When user selects an option from one of the DropDown and clicks "Search", the new set of rows should be loaded.
I have added the entire functionality as below but the View is not getting updated with the new result.
[HTML Button]
<table style="margin-left:2%;margin-top:2%">
<tr style="width:200px">
<td style="width:100px">Location</td>
<td style="width:300px">
#Html.DropDownList("ddlLocation", new List<SelectListItem>
{
new SelectListItem{ Text="-- Select --", Value = "0" },
new SelectListItem{ Text="North", Value = "1" },
new SelectListItem{ Text="South", Value = "2" },
new SelectListItem{ Text="West", Value = "3" }
}, new { #id = "ddlLocation" })
</td>
</tr>
</table>
<div style="margin: 0;position: relative;top: 30%;left:45%;right:45%">
<input type="button" id="btnSearch" value="Filter Rows" onClick="searchRows()" />
</div>
[Javascript Code]
function searchRows(TemplateData) {
var e = document.getElementById('ddlLocation');
var FilterText = e.options[e.selectedIndex].text;
#*var data = "#Newtonsoft.Json.JsonConvert.SerializeObject(Model)";*#
$.ajax({
url: '/Search/' + FilterText,
type: 'GET',
data: TemplateData,
success: function (TemplateData) {
$("#divData").html(TemplateData);
alert(TemplateData);
}
});
}
[Controller Action Method]
[HttpGet]
[Route("~/Search/{FilterText}")]
public IActionResult Search(string FilterText)
{
ModelState.Clear();
List<TemplateData> templateList = new List<TemplateData>();
templateList = ImportExcel(FilterText);
return View(templateList.AsEnumerable());
}
Please note that the 'templateList' in the above action method is fetching correct set and invoking View but the View is not updating.
I read somewhere that one Nuget Package for Runtime Compilation needs to be added. I tried to add it but it throws error: "Not compatible with ASP.NET Core 3.1".
Related
I'm currently receiving an error whilst trying to fill a cascading select list who's javascript function is triggered by the on change of the previous list however the every time the controller returns the json object back to Ajax callback, Google Chrome DevTools is throwing the error:
DevTools was from the page
Once page is reloaded, DevTools will automatically reconnect
I have tried to enable logs and run Chrome using the flags however nothing is ever logged, nothing is saved in the logs in the Network tab either.
I have tested populating the select tag through adding a submit button to the first dropdown and calling/ populating the second dropdown that way and that seems to work fine. Is there any reason that using onchange on the Html.DropDownListFor() would cause DevTools to throw this error?
The PartialView containing the code (I have added both techniques I have used):
<div class="form-group">
<label>Select Country</label>
<!-- This works in filling the second dropdownlist. -->
#Html.DropDownListFor(m => m.SelectedCountry, new SelectList(Model.Country, "FieldID", "Value"), new { #id = "_ddlCountries", #class = "fr-select" })
<button type="submit" onclick="javascript: GetCities()">Test</button>
<!-- ********************************************** -->
<!-- This does not work in filling the second dropdown -->
#Html.DropDownListFor(m => m.SelectedCountry, new SelectList(Model.Country, "FieldID", "Value"), new { #id = "_ddlCountries", #class = "fr-select", onchange="javascript: GetCities()" })
<!-- ********************************************** -->
</div>
<div class="form-group">
<label>Select City</label>
<select class="fr-select" id="_cities"></select>
</div>
Both get into the javascript function below but the onchange throws the DevTools message/ error:
function GetCities() {
debugger;
var ddlText = $('#_ddlCountries option:selected').text();
$.ajax({
url: '#Url.Action("GetCities", "Home")',
type: 'POST',
data: JSON.stringify(ddlText),
contentType: 'Application/json',
success: function (result) {
debugger;
//Create a markup for a select
var markup = "";
//Populate the markup
for (var i = 0; i < result.length; i++) {
markup += "<option Value=" + result[i].FieldID + ">" + result[i].Value + "</option>";
}
//Populate dropdown with value
$("#_cities").html(markup).show();
}
})
}
Controller action:
public ActionResult GetCities([FromBody] string ddlText)
{
List<FieldMap> cities = new List<FieldMap>();
cities.Add(new FieldMap
{
FieldID = 0,
Value = "-- Please Select a City --"
});
foreach (var city in _uploadedFileModel.cities.Where(x => x.name == ddlText).OrderBy(x => x.city.name))
{
cities.Add(new FieldMap
{
FieldID = cities.Select(x => x.FieldID).Max() + 1,
Value = city.name
});
}
return Json(cities);
}
This works using MVC framework but for some reason it doesn't with Core. Is there any reason why the onchange would be triggering this error?
I am still very new to MVC, JavaScript, and jQuery so please bear with me.
I have a webgrid that contains different terms and their translations. The list of terms is dependent on the 'VMID' chosen from the drop down list above the grid. (Or at least it would be, if it were working correctly.)
The left-most column has an edit link for each term that leads to a Boostrap modal, which is populated with all the values assigned to the ID chosen in that drop down list. I need the terms in the grid to also depend on the value chosen from that list.
The approach I am currently trying goes like this (only pasting the bits relevant to the question) -
Main view (strongly typed with model reference, not included):
<div class="container-fluid">
<div style=" margin-bottom: 1.4%;">
<table>
<tr>
<td style="font-size: medium; margin-bottom: 5px">
#Model.lblVMID:
</td>
<td>
#Html.DropDownListFor(model => model.VMID, new System.Web.Mvc.SelectList(Model.locations, "Key", "Value"), new { #class = "form-control", id = "ddlVMID", onchange = "RepopGrid()" })
</td>
</tr>
</table>
</div>
<div class="table-scrollable well" id="termGrid">
#{Html.RenderPartial("_TermGrid", Model);}
</div>
</div>
<script type="text/javascript">
function RepopGrid() {
VMID = $("#ddlVMID").val();
ShowLoadingDialog();
$.ajax({
url: URLPrefix() + "/Terminology/RepopGrid/" + VMID,
type: "POST",
success: function () {
HideLoadingDialog();
},
error: function (jqXHR, textStatus, errorThrown) {
HideLoadingDialog();
ShowAlert(false, 'Failed to change location\r\n' + errorThrown);
}
})
}
</script>
Partial view (strongly typed with model reference, not included. Same model that the main view uses):
#Model.grid.GetHtml(columns: Model.columns, alternatingRowStyle: "info", nextText: "Next",
previousText: "Previous", tableStyle: "table")
Controller:
public ActionResult Index()
{
TerminologyModel model = new TerminologyModel(clsUtils.PreferredVMID());
return View(model);
}
[HttpPost]
public ActionResult RepopGrid(int VMID)
{
TerminologyModel model = new TerminologyModel(VMID);
return PartialView("_TermGrid", model);
}
The model accepts an 'int VMID' and uses that to retrieve the list of terms from the database, then a foreach runs through each term and assigns them to the grid. This works fine, so I didn't feel a need to post it here (it's a bit long, because there are some special columns that need extra work to get set up).
We have a route configuration file that maps URLS to their corresponding actions in the controllers, in this case:
routes.MapRoute(
name: "TerminologyRepopGrid",
url: "Terminology/{action}/{VMID}",
defaults: new { controller = "Terminology", action = "RepopGrid", VMID = UrlParameter.Optional }
);
I'm not familiar with Ajax, so I'm probably using it completely wrong.
This approach is based on a few places where I've read to put the grid in a partial view, so that's what I've done here.
After I choose a new option, I can see that a whole new grid is being returned in Chrome's element inspector, but that grid is not being applied on top of the existing one.
Again, I have been searching and trying and reading and experimenting and I just can't figure out why mine won't work.
I moved the drop down list to the partial view where the grid is, wrapped everything in an Ajax Form, removed the "RepopGrid" JavaScript and controller actions, and added a parameter to the Index action for a VMID. If the VMID is null or empty (when the page is first loaded or refreshed), it uses the default VMID to generate the model. If a valid VMID is received, then it uses that number to generate the model instead.
Here is the new code for those who might be looking for a similar solution (like last time, only the relevant parts):
Index.cshtml -
<div class="table-scrollable well" id="termGrid">
#Html.Partial("_TermGrid", Model)
</div>
<div class="modal fade" id="editTerm" tabindex="-1" role="dialog" aria-labelledby="editTerm-label" aria-hidden="true">
<div class="modal-dialog" style="width: 290px">
<div class="modal-content" style="width: 290px">
<div class="modal-header" style="border-bottom: none; padding-bottom: 0px;">
<h4 id="lblParamName" align="center"></h4>
</div>
<div class="modal-body" id="editTermBody" style="padding: 8px">
</div>
</div>
</div>
</div>
Partial View -
#{
var ajaxOptions = new AjaxOptions()
{
OnSuccess = "OnSuccess",
OnFailure = "OnFailure",
OnBegin = "OnBegin",
HttpMethod = "Post"
};
using (Ajax.BeginForm("Index", "Terminology", ajaxOptions))
{
<div class="container-fluid" id="termGrid">
<div style=" margin-bottom: 1.4%;">
<table>
<tr>
<td style="font-size: medium; margin-bottom: 5px">
#Model.lblVMID<label>: </label>
</td>
<td>
#Html.DropDownListFor(model => model.VMID, new System.Web.Mvc.SelectList(Model.locations, "Key", "Value"), new { #class = "form-control", id = "ddlVMID", onchange = "this.form.submit()" })
</td>
</tr>
</table>
</div>
</div>
}
}
#Model.grid.GetHtml(columns: Model.columns, alternatingRowStyle: "info", nextText: "Next",
previousText: "Previous", tableStyle: "table")
<script type="text/javascript">
function OnSuccess(data, textStatus, jqXHR) {
HideLoadingDialog();
}
function OnFailure(data, textStatus, jqXHR) {
HideLoadingDialog();
ShowAlert(false, "Oops! Something went wrong. :(");
}
function OnBegin() {
ShowLoadingDialog();
VMID = $("#ddlVMID").val()
ShowLoadingDialog();
$.ajax({
url: URLPrefix() + "/Terminology/Index/" + VMID,
type: "POST",
success: function () {
HideLoadingDialog();
},
error: function (jqXHR, textStatus, errorThrown) {
HideLoadingDialog();
ShowAlert(false, 'Failed to change location\r\n' + errorThrown);
}
})
}
</script>
Controller -
public ActionResult Index(string VMID)
{
if (string.IsNullOrEmpty(VMID))
{
TerminologyModel model = new TerminologyModel(clsUtils.PreferredVMID());
return View(model);
}
else
{
TerminologyModel model = new TerminologyModel(int.Parse(VMID));
return View(model);
}
}
The model's code has not changed since the question was originally asked.
I am facing problems accessing the ActionResult [Post] from my View.
View:
#using (Html.BeginForm()){
<form id="edit-order-form" action="#Href("~/Orders/Edit")">///EDIT:
....
<div class="row">
<span class="label"><label for="ShipPostalCode">PostalCode:</label></span>
<input type="text" id="txtShipPostalCode" name="ShipPostalCode" value="#ViewBag.ShipPostalCode" />
</div>
<div class="row">
<span class="label"> </span>
<input type="submit" id="btnSave" name="submit" value="Save" />
</div>
</fieldset>
</form>
<script type="text/javascript">
$("#btnSave").live("click", saveRecord);
function saveRecord() {
$.ajax(
{ type: "Post" ,
url: '#Url.Action("Save", "OrdersList")',
data: {
OrderID: $("#hdnOrderID").val(),
ShipName: $("#txtShipName").val(),
ShipAddress: $("#ShipAddress").val(),
RequiredDate: $("#RequiredDate").val(),
ShipPostalCode: $("#ShipPostalCode").val(),
},
dataType: "html" ,
success: function (data){
alert ('saved');
}
}).....
Controller:
[HttpPost]
//[ValidateAntiForgeryToken]
public ActionResult Save(int orderId = 0, string ShipName = "", string ShipAddress = "", string ShipPostalCode = "", DateTime? RequiredDate = null)
{
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString);
using (SqlCommand cmd = new SqlCommand("GetOrders", conn))
{
conn.Open();
//SqlCommand cmd = new SqlCommand( "GetOrders", "connection string");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#ID", orderId);
cmd.Parameters.AddWithValue("#ShipName", ShipName);
cmd.Parameters.AddWithValue("#ShipAddress", ShipAddress);
SqlParameter paramDate = cmd.Parameters.Add("#RequiredDate",
System.Data.SqlDbType.DateTime);
paramDate.Value = RequiredDate;
//cmd.Parameters.AddWithValue("#RequiredDate", RequiredDate);
cmd.Parameters.AddWithValue("#ShipPostalCode", ShipPostalCode);
//SqlParameter Total = cmd.Parameters.Add("#Total", SqlDbType.Int);
//Total.Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
conn.Close();
return View();
}
}
The controller action doesn't get called. Probably the javascript function neither.
First what you need to do is change the 'type' attribute of the 'btnSave' input element to 'button' so it doesn't post the page when clicked. 'Input' elements with the type of 'submit' will actually post the page, which is not what you want when you want to execute javascript when a button is clicked.
Next what you'll need to do is use either IE or Chrome and pull up the Developer tools, 'F12'. In Chrome, click the 'Sources' tab, then open the 'Navigator' by clicking the 'boxed arrow' below the 'Elements' tab. Find the file which holds your javascript and breakpoint the line which has the following syntax, $.ajax(. Then go to your page and click the 'Submit' button. From there, you should see exceptions that are most likely causing your javascript to fail.
Also, you may want to open 'Fiddler' and watch to see if the 'Post' to your RESTful service is kicking off.
Have you tried using document.forms[0].submit(); instead? To see if it's a problem with the posting or your javascript?
Also .live is deprecated, you should be using .on instead.
example code:
$("#btnSave").on("click", saveRecord);
//or $("#btnSave").click( function(){
// document.forms[0].submit();
//});
function saveRecord() {
document.forms[0].submit();
})
And change your Save action result's attributes to [HttpPost] and below that [ActionName("Edit")]
ANSWER:
Replacing this line:
self.identifiers.push(new Identifier(self.identifierToAdd(), self.selectedIdentifierTypeValue()));
With this line:
self.identifiers.push({ Key: self.identifierToAdd(), Value: self.selectedIdentifierTypeValue()});
The post requests is now sending the collection data correctly. However that doesn't solve the fact that the MVC action is not receiving it but this question is big enough already.
I can't seem to get the data from collection property of my model in knockout into my MVC model when posting to an action. If I alert ko.toJSON my identifiers() property from below it properly shows all the data, but when I try and submit that data via a normal postback (the action just takes the EquipmentCreateModel below), it looks like this:
Identifiers is empty and when I look at the ModelState error for Identifiers, it says that it cannot convert String to Dictionary<Guid, string>. What am I doing wrong? I thought MVC3 automatically converts JSON into objects if it can, as it did with the BuildingCode and Room properties?
Also why does my string data in the above picture have escaped quotes?
EDITS:
If I look at the post data, identifiers is shown as an empty array (identifiers: [{}]). I tried jsoning identifiers in the save method like so:
self.identifiers = ko.toJSON(self.identifiers());
This causes the request data to not be empty and look like this:
identifiers:"[{\"Value\":\"sdfsd\",\"Key\":\"4554f477-5a58-4e81-a6b9-7fc24d081def\"}]"
However, the same problem occurs when I debug the action. I also tried jsoning the entire model (as outlined in knockoutjs submit with ko.utils.postJson issue):
ko.utils.postJson($("form")[0], ko.toJSON(self));
But this gives a .NET error that says Operation is not valid due to the current state of the object. Which from looking at the request data it looks like it's being JSON-ified twice because each letter or character is it's own value in the HttpCollection and this is because .NET only allows 1000 max by default ('Operation is not valid due to the current state of the object' error during postback).
Using the $.ajax method to submit the data, everything works fine:
$.ajax({
url: location.href,
type: "POST",
data: ko.toJSON(viewModel),
datatype: "json",
contentType: "application/json charset=utf-8",
success: function (data) { alert("success"); },
error: function (data) { alert("error"); }
});
But due to other reasons I cannot use the $.ajax method for this, so I need it working in the normal post. Why can I toJSON the entire viewModel in the ajax request and it works, but in the normal postback it splits it up, and when I don't, all quotes are escaped in the sent JSON.
Here is my ViewModel:
public class EquipmentCreateModel
{
//used to populate form drop downs
public ICollection<Building> Buildings { get; set; }
public ICollection<IdentifierType> IdentifierTypes { get; set; }
[Required]
[Display(Name = "Building")]
public string BuildingCode { get; set; }
[Required]
public string Room { get; set; }
[Required]
[Range(1, 100, ErrorMessage = "You must add at least one identifier.")]
public int IdentifiersCount { get; set; } //used as a hidden field to validate the list
public string IdentifierValue { get; set; } //used only for knockout viewmodel binding
public IDictionary<Guid, string> Identifiers { get; set; }
}
Then my knock-out script/ViewModel:
<script type="text/javascript">
// Class to represent an identifier
function Identifier(value, identifierType) {
var self = this;
self.Value = ko.observable(value);
self.Key = ko.observable(identifierType);
}
// Overall viewmodel for this screen, along with initial state
function AutoclaveCreateViewModel() {
var self = this;
//MVC properties
self.BuildingCode = ko.observable();
self.room = ko.observable("55");
self.identifiers = ko.observableArray();
self.identiferTypes = #Html.Raw(Json.Encode(Model.IdentifierTypes));
self.identifiersCount = ko.observable();
//ko-only properties
self.selectedIdentifierTypeValue = ko.observable();
self.identifierToAdd = ko.observable("");
//functionality
self.addIdentifier = function() {
if ((self.identifierToAdd() != "") && (self.identifiers.indexOf(self.identifierToAdd()) < 0)) // Prevent blanks and duplicates
{
self.identifiers.push(new Identifier(self.identifierToAdd(), self.selectedIdentifierTypeValue()));
alert(ko.toJSON(self.identifiers()));
}
self.identifierToAdd(""); // Clear the text box
};
self.removeIdentifier = function (identifier) {
self.identifiers.remove(identifier);
alert(JSON.stringify(self.identifiers()));
};
self.save = function(form) {
self.identifiersCount = self.identifiers().length;
ko.utils.postJson($("form")[0], self);
};
}
var viewModel = new EquipmentCreateViewModel();
ko.applyBindings(viewModel);
$.validator.unobtrusive.parse("#equipmentCreation");
$("#equipmentCreation").data("validator").settings.submitHandler = viewModel.save;
View:
#using (Html.BeginForm("Create", "Equipment", FormMethod.Post, new { id="equipmentCreation"}))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Location</legend>
<div class="editor-label">
#Html.LabelFor(model => model.BuildingCode)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.BuildingCode, new SelectList(Model.Buildings, "BuildingCode", "BuildingName", "1091"), "-- Select a Building --", new { data_bind = "value:BuildingCode"})
#Html.ValidationMessageFor(model => model.BuildingCode)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Room)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Room, new { #class = "inline width-7", data_bind="value:room"})
#Html.ValidationMessageFor(model => model.Room)
</div>
</fieldset>
<fieldset>
<legend>Identifiers</legend>
<p>Designate any unique properties for identifying this autoclave.</p>
<div class="editor-field">
Add Identifier
#Html.DropDownList("identifiers-drop-down", new SelectList(Model.IdentifierTypes, "Id", "Name"), new { data_bind = "value:selectedIdentifierTypeValue"})
#Html.TextBox("identifier-value", null, new { #class = "inline width-15", data_bind = "value:identifierToAdd, valueUpdate: 'afterkeydown'" })
<button type="submit" class="add-button" data-bind="enable: identifierToAdd().length > 0, click: addIdentifier">Add</button>
</div>
<div class="editor-field">
<table>
<thead>
<tr>
<th>Identifier Type</th>
<th>Value</th>
<th></th>
</tr>
</thead>
<!-- ko if: identifiers().length > 0 -->
<tbody data-bind="foreach: identifiers">
<tr>
<td>
<select data-bind="options: $root.identiferTypes,
optionsText: 'Name', optionsValue: 'Id', value: Key">
</select>
</td>
<td><input type="text" data-bind="value: Value"/></td>
<td>Remove</td>
</tr>
</tbody>
<!-- /ko -->
<!-- ko if: identifiers().length < 1 -->
<tbody>
<tr>
<td colspan="3"> No identifiers added.</td>
</tr>
</tbody>
<!-- /ko -->
</table>
#Html.HiddenFor(x => x.IdentifiersCount, new { data_bind = "value:identifiers().length" })<span data-bind="text:identifiers"></span>
#Html.ValidationMessageFor(x => x.IdentifiersCount)
</div>
</fieldset>
<p>
<input type="submit" value="Create" />
</p>
}
I think I've found the issue or at least narrowed down the problem. The editable grid example uses simple js objects to represent gifts. You are using Identifier objects with sub observables. It seems that if we update the grid example to use more complex types it too breaks in the same way as your example. This is either by design or a bug.
http://jsfiddle.net/SZzVW/9/
I think the only solution is to write your own mapping function to submit the form.
Hope this helps.
I have list page to show all images with its name from database in asp.net mvc list action (PhotoList - get).. in that view page (PhotoList.aspx), I have created checkbox to delete multiple rows. I want scenario like following
First page shows the list with in first column checkbox and in second column PhotoName and on the down page one button for delete selected rows .
when selects checkboxes and clicks the delete button, according to selection the rows will be deleted from database and return the same list page..
I don't understand where to write code for delete and how?
<% foreach (var item in Model) { %>
<tr>
<td>
<input type="checkbox" name="deleteImage" value="<%= item.PhotoId %>"/>
</td>
<td>
<%= Html.ActionLink("Edit", "Edit", new { id=item.PhotoId }) %>
</td>
<td>
<%= Html.Encode(item.PhotoName) %>
</td>
</tr>
<% } %>
<input type="button" name="Delete" value="Delete Selected items"/>
The Code for delete will be written in the HttpPost action for delete. Something like below should work if you are using myModel
[HttpPost]
public ActionResult Delete(myModel deleteEntries) //This is the post-version of your Action that rendered the view..If it's Edit, then change the name to Edit
{
var deleteList = db.deleteEntries.where(d => d.checkBox == true).ToList();
foreach (myList my in deleteList)
{
db.myList.Remove(my); // remember db should be your DbContext instace
}
db.SaveChanges();
}
UPDATE
You will first need to make a ViewModel because otherwise you cannot recognize which entries are checked for deletion with the help of checkbox.
Make a ViewMode class like following
using pratice3.Models;
public class MyPhotoViewModel
{
public UserManagementDbEntities.tblPhoto TablePhoto { get; set; }
public bool checkBox { get; set; }
}
Return this to your view
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult PhotosList()
{
var viewModel = _datamodel.tblPhoto.Select(g => new MyPhotoViewModel
{
TablePhoto = g;
checkBox = false;
}).ToList();
return View(viewModel);
}
In View, change the using statement to reflect IEnumerable<MyPhotoViewModel> and make appropriate changes accordingly.
Then, define your Post Action like following
[HttpPost]
public ActionResult PhotosList(IEnumerable<MyPhotoViewModel> myPhotoList)
{
var deleteList = myPhotoList.where(d => d.checkBox == true).ToList();
foreach (var deletePhoto in deleteList)
{
_datamodel.tblPhoto.DeleteObject(deletePhoto.TablePhoto);
}
db.SaveChanges();
}
Using jQuery you can do this.
On button click get all the Ids of photos, something like this
var selected = new Array();
$('name="deleteImage" input:checked').each(function () {
selected.push($(this).attr('id')));
});
var selectedIds = selected.join(',');
Now on button click, make ajax call to some function on server side which will accept these ids and will delete from DB or so.
$.ajax({
url: '#Url.Action("DeleteRecord", "UserManage")',
data: 'ids=' + selectedIds + '&time=' + new Date(), //Date makes each call unique
success: function (data) {
//You can reload page
},
error: function (data) {
//You can show error
}
});
Hope this helps.