How to update Chosen dropdownlist's items(cascade dropdownlist)? - asp.net-mvc

I use Chosen plugin. I want to refresh Category (Chosen) dropdownlist, when change Section dropdownlist.
Here is view:
#model CategoryInputModel
#Html.DropDownListFor(model => model.SectionId, ViewBag.SectionList as IEnumerable<SelectListItem>, new { id = "SectionId" })
#Html.ListBoxFor(model => model.CategoryIdSet, ViewBag.AllCategoryList as MultiSelectList
, new
{
#class = "chzn-select",
data_placeholder = "Choose Categories...",
#style = "width : 500px;",
id = "CategoryIdSet"
})
CategoryInputModel is like:
public class CategoryInputModel
{
public string Name { get; set; }
public int SectionId{ get; set; }
public List<int> CategoryIdSet{ get; set; }
}
I can create cascade dropdownlist for simple lists, but could not create it for multiple select. I tried this :
<script type="text/javascript">
$(function () {
$("#SectionId").change(
function () {
loadLevelTwo(this);
});
loadLevelTwo($("#SectionId"));
});
function loadLevelTwo(selectList) {
var selectedId = $(selectList).val();
$.ajax(
{
url: "#Url.Action("GetCategoriesBySectionId", "Project")",
type: "GET",
data: { id: selectedId },
success: function (data) {
$("#CategoryIdSet").html($(data).html());
},
error: function (xhr) {
alert("Something went wrong, please try again");
}
});
}
</script>
In controller:
public ActionResult GetCategoriesBySectionId(int id)
{
var result = MyService.GetCategoriesBySectionId(id);
// **its problem to create partial view that return chosen dropdownlist I need**
}
How can I create cascade Chosen dropdownlist?

I think you need to add a little more to your ajax callback. I replaced success method with done. Try this, it works for me:
function loadLevelTwo(selectList) {
var selectedId = $(selectList).val();
$.ajax(
{
url: "#Url.Action("GetCategoriesBySectionId", "Project")",
type: "GET",
data: { id: selectedId },
error: function (xhr) {
alert("Something went wrong, please try again");
}
}).done(function (data) {
$("#CategoryIdSet").children().each(function (index, option) {
$(option).remove();
});
//blank option
var items = "<option selected value=\"\"></option>";
$.each(data,
function () {
items += "<option value=\"" + this[0] + "\">" + this[1] + "</option>";
});
$("#CategoryIdSet").html(items)
$("#CategoryIdSet").trigger("liszt:updated");
$("#CategoryIdSet").change();
});
}
controller action could look like this:
public ActionResult GetCategoriesBySectionId(int id)
{
var result = MyService.GetCategoriesBySectionId(id);
//build JSON.
var modelDataStore = from m in result
select new[] { m.YourValueProperty.ToString(),
m.YourTextProperty };
return Json(modelDataStore, JsonRequestBehavior.AllowGet);
}

Related

JSON object to ASP.NET MVC

I know there are multiple threads around this issue, but I still can't figure mine out. Can someone please help me figure out why my classObject always has null value? I feel like I've tried everything by now.
My class:
public class ClassAB
{
[Required]
[MaxLength(100)]
[DataType(DataType.Text)]
public string A{ get; set; }
[Required]
[MaxLength(100)]
[DataType(DataType.MultilineText)]
public string B{ get; set; }
}
My home controller:
[HttpPost]
public ActionResult MyMethod(ClassAB classObject)
{}
and my Javacript call
let data = {
"A": "A",
"B": "B"
}
await fetch(`https://localhost:44359/Home/MyMethod/`, {
method: "POST",
body: JSON.stringify(data),
contentType:"application/json",
success: (result)=>{
console.log(result)
},
failure: (result) => {
alert(result)
}
});
Found the issue. My contentType should have been in header. Modifying request to
await fetch(`https://localhost:44359/Home/MyMethod/`, {
method: "POST",
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
success: (result)=>{
console.log(result)
},
failure: (result) => {
alert(result)
}
});
fixed the issue
Try this
var data = [{A: 'A',B:'B'}];
await fetch(`https://localhost:44359/Home/MyMethod/`, {
method: "POST",
body: JSON.stringify(data),
contentType:"application/json",
success: (result)=>{
console.log(result)
},
failure: (result) => {
alert(result)
}
});
[HttpPost]
public ActionResult MyMethod(List<ClassAB > classObject)
{}
WebAPI won't know to model bind an object like that. See https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1
Try using the [FromBody] attribute
[HttpPost]
public ActionResult MyMethod([FromBody] ClassAB classObject)
{}
When combining this with a proper javascript post this will work, see image.
Sample js
<script>
var xhr = new XMLHttpRequest();
var url = "https://localhost:5001/api/default/MyMethod";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
console.log(json.email + ", " + json.password);
}
};
var data = JSON.stringify({ "A": "A", "B": "B" });
xhr.send(data);
</script>

Return to same View if Data is not Found and Display Not Found Message in the same View

I have Search View Where user enters Employee ID, Upon Clicking Button an Action Method is Executed and user is Redirected to Employee Details View.
If Employee Id not Matches I want to Retain the Same Search View and display a Label with Employee Details not Found Message in MVC,
Please help on doing the above Fucntionality in my Controller.
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
// ???
}
return View(empDetails);
}
It is good practice to have ViewModel classes. Make one, and use it to transfer domain objects and messages to your view. Like this:
class EmpDetailsViewModel
{
public EmpDetail Emp { get;set; }
public string Message { get;set; }
}
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var vm = new EmpDetailsViewModel();
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
vm.Message = "Employee not found (or whatever)"
}
else
{
vm.Emp = empDetails;
}
return View(vm);
}
Then, in your view, just do
if (Model.Emp == null)
{
<span>#Model.Message</span>
}
else
{
<div>Emp details stuff </div>
}
What I understand is you want to return a message when Employee is not found.
Try this
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
return Content("Employee not found");
}
return View(empDetails);
}
In view extract the message from the response.
--Edit
You can do an Ajax call like below to this action method
<script type="text/javascript">
$(function () {
$("#btnGetDetails").click(function () {
$.ajax({
type: "GET",
url: "/Employee/Details",
data: '{firstName: "' + $("#txtSearch").val() + '" }',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
alert(response.responseText);
},
failure: function (response) {
alert(response.responseText);
},
error: function (response) {
alert(response.responseText);
}
});
});
});
</script>
success call back will be triggered and the message will be available in the view. (Didn't test the script)

KendoValidation with Remote validation

This is almost killing me. I've never wasted so much time on an issue.
I've got a field that needs to be validated with a call to a REST API since it needs to check the presence of some data on the DB. The problem I've is that calling the validator.validate returns true even if the call to json api returns false.
Here's the code (I use the Kendo MVC helpers but it's not related to that the problem)
<div id="divValidator1">
<div class="form-group">
#Html.LabelFor(m => m.Email)
#(Html.Kendo().TextBoxFor(m => m.Email)
.HtmlAttributes(new { placeholder = "you#domain.com", type = "email", #class = "k-textbox required",data_bind="value: XXX" })
)
</div>
<footer class="col-xs-12 form-group text-right">
#(Html.Kendo().Button()
.Name("Next1")
.Content("Next")
.HtmlAttributes(new { #class = "k-primary", data_bind = "enabled: isEnabled" })
.Events(ev => ev.Click("onNextClick")))
</footer>
var kendoValid = $("#divValidator1").kendoValidator({
validateOnBlur: false,
rules: {
remote: function (input) {
if (input.val() == "" || !input.attr("data-val-remote-url")) {
return true;
}
if (input.attr("data-val-remote-recieved")) {
input.attr("data-val-remote-recieved", "");
return !(input.attr("data-val-remote"));
}
var url = input.attr("data-val-remote-url");
var postData = {};
postData[input.attr("data-val-remote-additionalfields").split(".")[1]] = input.val();
var validator = this;
var currentInput = input;
input.attr("data-val-remote-requested", true);
$.ajax({
url: url,
type: "POST",
data: JSON.stringify(postData),
dataType: "json",
traditional: true,
async:false,
contentType: "application/json; charset=utf-8",
success: function (data) {
if (data == true) {
input.attr("data-val-remote", "");
}
else {
input.attr("data-val-remote", false);
}
input.attr("data-val-remote-recieved", true);
// validator.validateInput(currentInput);
},
error: function () {
input.attr("data-val-remote-recieved", true);
validator.validateInput(currentInput);
}
});
return true;
}
},
messages: {
remote: function (input) {
return input.attr("data-val-remote");
}
}
}).data("kendoValidator");
var viewModel1 = kendo.observable({
XXX:null,
isEnabled: function () {
var self = this;
self.get("XXX");
var x = $("#divValidator1").data("kendoValidator");
console.log(x.validate());
}
});
kendo.bind($("#divValidator1"), viewModel1);
and here's the controller
public ActionResult IsEmailJustPresent(string email)
{
var res = email == "something";
return Json(res, JsonRequestBehavior.AllowGet);
}
And the ViewModel
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
[Remote("IsEmailJustPresent", "Home")]
public string Email { get; set; }
}
I've also used a kendo.observable so that I can enable/disable the next button based on the validation value.
In the isEnabled function you see I do var x = $("#divValidator1").data("kendoValidator"); but it returns always true (seems to only check for the required attribute).
To reach that solution I started from here
If you want there's a more complex example I've created on git that includes the wizard I'll need in my final project
Could this commented line be the problem?
// validator.validateInput(currentInput);
Is the attribute "data-val-remote" correctly set?

How to post json object with multiple values to the Controller from Ajax

I have this Ajax post working for a single value but I need it to work with multiple values. What am I missing?
I have already tried to make the public class 'Value' a List AND Guid[]. I have tried to adjust the method parameter to List AND Value[]. Not sure what else to try.
Class:
public class Value
{
public Guid TimeId { get; set; }
}
Method:
public IActionResult ApproveAllTimesheets([FromBody]Value information)
View JS:
function SubAll() {
var selectedValues = $('#timesheet').DataTable().column(0).checkboxes.selected().toArray();
var instructions = {};
for (var TimeId in selectedValues) {
instructions[TimeId] = { TimeId: selectedValues[TimeId] };
}
var inst = JSON.stringify(instructions);
$.ajax({
url: "/Admin/ApproveAllTimesheets",
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: inst,
success: function (result) {
alert(result);
},
error: function (xhr, textStatus) {
if (xhr.status == 401) { alert("Session Expired!"); window.location = "/Account"; }
else {
alert('Content load failed!', "info");
}
}
});
};
If I send through this object it works but I need to send through multiple values like my ajax post will do.
var instructions = { TimeId: "13246578-1234-7894-4562-456789123456" };
UPDATE #1
I found a structure that works for me by extending the class, now I just need to figure out how to create the correct object and array combination.
New Classes:
public class ValueContainer
{
public List<Value> MasterIds { get; set; }
}
public class Value
{
public Guid TimeId { get; set; }
}
Method:
public IActionResult ApproveAllTimesheets([FromBody]ValueContainer information)
Structure I need now (this works hard coded):
var jsonObject = {
"MasterIds": [{ TimeId: "13246578-1234-7894-4562-456789123450" }, { TimeId: "13246578-1234-7894-4562-456789123451" }, { TimeId: "13246578-1234-7894-4562-456789123452" }]
};
I'm still new to this stuff but what I see is that jsonObject is an object with a Key 'MasterIds' and the corresponding values are an array of objects with the key 'TimeId'...is this a correct evaluation?...and how to create it in code please?
You neec to create an array of objects and then set it in the container object :
var instructions = []; // an array
for (var i = 0; i < selectedValues.length; i++) {
instructions.push({ TimeId: selectedValues[i] };
}
var Value = {TimeId: instructions}; // creating object with property TimeId as array of guid
var inst = JSON.stringify(Value);
.......
....... your ajax code
and your class property should also be of type array:
public Guid[] TimeId { get; set; }

jquery autocomplete passing parameters

I need help to pass parameters through JQuery's autocomplete. I have an input:
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" name="searchName" id="searchName" placeholder="Nom et/ou prénom" />
inside a form. When you type, an jquery autocomplete function launches a search in Active Directory and shows result in the drop down list:
$(document).ready(function () {
$("#searchName").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Home/SearchUserWhileTyping",
type: "GET",
data: { name: $("#searchName").val() },
contentType: "application/json;charset=utf-8",
dataType: "json",
success: function (data) {
$("#searchName").html(''),
response($.map(data, function (item) {
return {
label: item
}
}));
}
});
},
minLength: 4
})
});
$(document).ready(function(){
$('#searchName').on('autocompletechange change', function () {
$('#searchValue').html('You selected: ' + this.value);
}).change()});
For now I could only do it after a form validation: form is validated -> I load the users found and their unique id -> click on one link and it shows the user info thanks to their unique id passed through. What I want to do is: If you click on one of the autocomplete choices, it directly shows the information of your user.
Here is the code for the search while you type:
[HttpGet]
public ActionResult SearchUserWhileTyping(string name)
{
ADManager adManager = new ADManager();
List<string> lastName = adManager.SearchUserByName(name);
List<string> splitList = new List<string>();
if (lastName != null)
{
if (lastName.Count <= 10)
{
int inc = 0;
foreach(string splitter in lastName)
{
if (inc % 2 == 1)
{
splitList.Add(splitter);
}
inc++;
}
return Json(splitList, JsonRequestBehavior.AllowGet);
}
}
return null;
}
I use a splitter because another function allows me to search AD and returns several parameters (which will then be useful to immediately find a user by its unique id, that's my difficulty).
This calls the following function:
public List<string> SearchUserByName(string name)
{
try
{
DirectoryEntry ldapConnection = createDirectoryEntry();
DirectorySearcher search = new DirectorySearcher(ldapConnection);
var sidInBytes=new byte[0];
//anr permet de chercher tout utilisateur contenant "name"
search.Filter = "(&(objectClass=user)(anr=" + name + "))";
//search.Filter = "(&(objectClass=User) (name=" + name + "*))";
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("distinguishedName");
resultCollection = search.FindAll();
if (resultCollection.Count == 0)
{
return null;
}
else
{
foreach(SearchResult sResult in resultCollection)
{
if (sResult.Properties["distinguishedName"][0].Equals(null) ||
sResult.Properties["displayName"][0].Equals(null))
continue;
displayName.Add(sResult.Properties["distinguishedName"][0].ToString());
displayName.Add(sResult.Properties["displayName"][0].ToString());
}
}
ldapConnection.Close();
ldapConnection.Dispose();
search.Dispose();
return displayName;
}
catch (Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
return null;
}
Finally, when I have my list of users, I can click on their link and I load info about the user using this function:
public List<KeyValuePair<string,string>> GetUserInfoBySAMAN(string sAMAccountName)
{
try
{
DirectoryEntry ldapConnection = createDirectoryEntry();
DirectorySearcher search = new DirectorySearcher(ldapConnection);
search.Filter = "(sAMAccountName=" + sAMAccountName + ")";
search.PropertiesToLoad.Add("objectSID");
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("memberOf");
search.PropertiesToLoad.Add("description");
search.PropertiesToLoad.Add("accountExpires");
search.PropertiesToLoad.Add("sAMAccountName");
result = search.FindOne();
///Conversion du SID en chaine de caractères
var sidInBytes = (byte[])result.Properties["objectSID"][0];
var sid = new SecurityIdentifier(sidInBytes, 0);
String time;
if (result.Properties["accountExpires"][0].ToString().Equals("0")|| result.Properties["accountExpires"][0].ToString().Equals("9223372036854775807"))
{
time = "Jamais";
}
else
{
///Conversion de la date en entier puis en date
DateTime dt = new DateTime(1601, 01, 02).AddTicks((Int64)result.Properties["accountExpires"][0]);
time = dt.ToString();
}
string desc="";
if (result.Properties.Contains("description"))
{
desc = result.Properties["description"][0].ToString();
}
else
{
desc = "Pas de description disponible";
}
userInfo = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("displayName",result.Properties["displayName"][0].ToString()),
new KeyValuePair<string, string>("memberOf", result.Properties["memberOf"][0].ToString()),
new KeyValuePair<string, string>("accountExpires",time),
new KeyValuePair<string, string>("description",desc),
new KeyValuePair<string, string>("sid",sid.ToString()),
new KeyValuePair<string, string>("sAMAccountName",result.Properties["sAMAccountName"][0].ToString())
/*lastName.Add(result.Properties["displayName"][0].ToString());
lastName.Add(result.Properties["memberOf"][0].ToString());
lastName.Add(sid.ToString());
lastName.Add(result.Properties["accountExpires"][0].ToString());
lastName.Add(result.Properties["description"][0].ToString());*/
};
return userInfo;
}
catch(Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
return null;
}
That last function doesn't work if I change sAMAccountName by distinguishedName because apparently this attribute cannot be used like that. I want to use distinguishedName and immediately have my object.
So what I need is to search while I type, and if I select one of the proposed choices, validating the form immediately send me to user info page.
Thanks for your help, hope it is clear enough
Edit I added a 2nd script that can get the value of selected item, but I need the data passed through the controller
If I understood right, autocomplete method has select event.
$(document).ready(function () {
$("#searchName").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Home/SearchUserWhileTyping",
type: "GET",
data: { name: $("#searchName").val() },
contentType: "application/json;charset=utf-8",
dataType: "json",
success: function (data) {
$("#searchName").html(''),
response($.map(data, function (item) {
return {
label: item
}
}));
},
select: function(e, ui)
{
//Just Example
$.get("UserController", {ID: ui.Id}).done(
function(data){
});
//Write your ajax post or get method
//that fetches user data directly as soon as
//the item in list clicked
}
});
},
minLength: 4
})
});
Edit: I saw your edit, so that you can use your GetUserInfoBySAMAN function for ajax get request inside select event (instead where I wrote "UserController"), and you have work for binding return data to the inputs or labels as well.

Resources