.NET Core 3.1 - Ajax OnGet and return objects using NEW System.Text.Json - asp.net-core-3.0

I stumbled across this issue today for the first time when trying to return an array of objects from a LINQ query that contains a foreign key field. The move by Microsoft away Newtonsoft JSON Library for .NET Core 3+ has left me not knowing how to perform the equivalent method show below:
Example below used to work in Core 2.2 but now throws an error 'System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.'
public async Task<IActionResult> OnGetSelectAllReportTemplatesAsync()
{
// Get the currently logged in user.
var currentlyLoggedInUser = await _userManager.GetUserAsync(User);
// Note - ApplicationUserId is a foreign key field in the DB Table...
var reportTemplates = _context.ReportTemplate
.Where(s => s.Private == true & s.ApplicationUserId == currentlyLoggedInUser.Id)
.OrderBy(s => s.Name).ToListAsync();
return new JsonResult(reportTemplates);
}
Ajax Call below:
$.ajax({
type: "GET",
url: "/Reports/ListView?handler=SelectAllReportTemplates",
contentType: "json",
dataType: "json",
success: function (data) {
data.forEach(function (element) {
$("#editReportTemplateSelect").append('<option value="' + element.id + '">' + element.name + '</option>');
reportTemplatesArray.push({ id: '' + element.id + '', name: '' + element.name + '', description: '' + element.description + '', timePeriod: '' + element.timePeriod + '', startDate: '' + element.startDate + '', startTimeHours: '' + element.startTimeHours + '', startTimeMinutes: '' + element.startTimeMinutes + '', endDate: '' + element.endDate + '', endTimeHours: '' + element.endTimeHours + '', endTimeMinutes: '' + element.endTimeMinutes + '', logLevel: '' + element.logLevel + '', eventCategory: '' + element.eventCategory + '', eventType: '' + element.eventType + '', eventSource: '' + element.eventSource + '', userAccount: '' + element.userAccount + '', private: '' + element.private + '', });
});
$("#reportTemplateNameInput").val(''); // Clear the department name input field.
$('#reportTemplateDescriptionTextarea').val(''); // Clear the department description textarea.
},
error: function () {
$("#editreportTemplateSelectMessageBar").html("Error while making Ajax call!");
setTimeout(function () { $("#editreportTemplateSelectMessageBar").html(''); }, 5000);
}
});
I tried marking the foreign key field in the model class with attribute '[JsonIgnore]' as i actually don't need to read nested properties from other tables, all i need is the ApplicationUserId string value itself. The only reason I have the tables using a relationship is for cascade delete.
I cant find any code examples which explains how to use the newer system?

OK, so I got my wires crossed a little here, after installing the former JSON functionality (Microsoft.AspNetCore.Mvc.NewtonsoftJson patch) as a NuGet back into the project, i was still getting an object cycle error.
My solution was to set the foreign key field in the model class with the attribute [JsonIgnore], I then updated the OnGet method as below. The method now uses a more simplified LINQ query which passed without errors, I then do the filtering from the selection manually afterwords, creating a list that could then be returned back to the Ajax GET function.
Model Class:
public class ReportTemplate
{
public int Id { get; set; } // Primary Key
other parameter object here...
[Required]
[JsonIgnore]
public string ApplicationUserId { get; set; }
[JsonIgnore]
public ApplicationUser ApplicationUser { get; set; }
}
Razor Page Model:
public async Task<IActionResult> OnGetSelectAllReportTemplates()
{
// Get the currently logged in user.
var currentlyLoggedInUser = await _userManager.GetUserAsync(User);
// Create a List
List<ReportTemplate> reportTemplates = new List<ReportTemplate>();
// Run a simple get all LINQ query
var reports = _context.ReportTemplate.OrderBy(s => s.Name);
// Do the filtering, manually from the query collection
foreach (var report in reports)
{
if (report.Private == false)
{
reportTemplates.Add(report);
}
if (report.Private == true & report.ApplicationUserId == currentlyLoggedInUser.Id)
{
reportTemplates.Add(report);
}
}
return new JsonResult(reportTemplates);
}

Related

How to Update relational Table in Asp.net core Web Api

I create two table Project or Member and i create relational table of project and member table named as project member i want to Update data of that relational table i use angularjs as frontend
This is put method to update the table
[Route("api/updateProjectData")]
[HttpPut]
public IActionResult UpdateProjectData(Project project, ProjectMember projectMember)
{
// query
if (project.ProjectId != project.ProjectId)
{
return BadRequest("Id Mismatched");
}
try
{
_context.Entry(project).State = EntityState.Modified;
_context.SaveChanges();
var memberDetails = _context.ProjectMembers.FirstOrDefault(e => e.ProjectId == project.ProjectId);
_context.Entry(projectMember).State = EntityState.Modified;
_context.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!ProjectExists(project.ProjectId))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
private bool ProjectExists(int id)
{
return _context.Projects.Any(e => e.ProjectId == id);
}
This is My frontend Api
$scope.UpdateProjectInfo = (ProjectID) => {
console.log($scope.ProjectID);
console.log($scope.ProjectName);
console.log($scope.ProjectStartDate);
console.log($scope.ProjectEndDate);
console.log($scope.UserStartDate);
$http({
method: 'PUT',
url: 'https://localhost:44307/api/updateProjectData?ProjectId=' + $scope.ProjectID + "&ProjectName=" + $scope.ProjectName + "&Startdate=" + $scope.ProjectStartDate + "&Enddate=" + $scope.ProjectEndDate + "&Status=&UserId=" + $scope.ddlUser + "&RoleId=" + $scope.ddlRole + "&UserStartdate=" + $scope.UserStartDate + "",
// headers: {
// 'Content-type': 'application/json;charset=utf-8'
// }
})
.then(function (response) {
console.log('ResponseUpdated', response.data);
$scope.ProjectName = response.data[0].projectName;
$scope.ddlUser = response.data[0].firstName + " " + response.data[0].lastName;
$scope.ddlRole = response.data[0].roleName;
$scope.ProjectStartDate = response.data[0].startdate;
$scope.ProjectEndDate = response.data[0].enddate;
$scope.UserStartDate = response.data[0].userStartdate;
notify('success', 'Record Updated Successfully.', '');
$scope.closeAddProjectPopup();
}, function (rejection) {
notify('error', rejection.data, '');
});
I successfully update one table But i confused how i update the second one pls tell me how i update

React-Admin with .net .The response to 'getList' must be like { data : [{ id: 123, ...}, ...] }, but the received data items do not have an 'id' key

I have an ASP.NET Core Web API and a React client. I'm trying to build admin dashboard with React-Admin. My problem is when I receive the data from server, my object are with property Id (uppercase), then in console I'm getting an error
The response to 'getList' must be like { data : [{ id: 123, ...}, ...] }, but the received data items do not have an 'id' key
I tried making new test class with property id (lowercase) in my server and then the problem is gone.
How can I fix this issue?
This is my test class and its working.
public class CityModel
{
public string id { get; set; }
public string Name { get; set; }
}
[HttpGet("Cities")]
public CityModel[] GetCities()
{
var city1 = new CityModel()
{
id = "ahsxge",
Name = "Berlin"
};
var city2 = new CityModel()
{
id = "axhdagw",
Name = "London"
};
var list = new List<CityModel>();
list.Add(city1);
list.Add(city2);
Response.Headers.Add("Access-Control-Expose-Headers", "X-Total-Count");
Response.Headers.Add("X-Total-Count", list.Count.ToString());
return list.ToArray();
}
This is my component in react :
const AppAdmin = () => {
const jwt = localStorage.getItem("jwt");
const httpClient = (url, options = {}) => {
options.user = {
authenticated: true,
token: 'Bearer ' + jwt
};
return fetchUtils.fetchJson(url, options);
};
const dataProvider = jsonServerProvider('https://localhost:44366/api', httpClient);
dataProvider.getList('Cities/Cities', {
pagination: { page: 1, perPage: 15 },
sort: { field: 'Name', order: 'ASC' },
})
.then(response => console.log(response));
return (
<Admin dataProvider={dataProvider}>
<Resource name='Cities/Cities' list={CitiesList} />
</Admin>
)
}
export default AppAdmin
You can configure the json converter to use camelCase serialization int the ConfigureServices method in the Startup.cs file the following way:
services
.AddControllers()
.AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
})
This way you can use PascalCase properties in your c# code (which you should do), but your client will recieve camelCase json properties.

Trying To Put Current User Name Into Notification Email

I currently have a Page Fragment with the following code that creates an entry for a datasource and sends out an email (code below) notifying everyone.
Button:
newSalesEmailMessage(widget);
widget.datasource.createItem();
app.closeDialog();
Client Script Email notification code:
/**
* Calls a server method to send an email.
* #param {Widget} sendButton - widget that triggered the action.
*/
function newSalesEmailMessage(sendButton) {
var pageWidgets = sendButton.root.descendants;
var fullName = app.datasources.Directory.item.FullName;
var htmlbody = '<b><font size="3">' + fullName + '</font></b>' + ' has created a new sales entry for: ' +
'<h1><span style="color:#2196F3">' +pageWidgets.ProjectName.value + '</h1>' +
'<p>Contact: <b>' + pageWidgets.Contact.value + '</b>' +
'<p>Sales Person: <b>' + pageWidgets.SalesPerson.value + '</b>' +
'<p>Notes: <b>' + pageWidgets.Notes.value + '</b>';
google.script.run
.withSuccessHandler(function() {
})
.withFailureHandler(function(err) {
console.error(JSON.stringify(err));
})
.sendEmailCreate(
'test#email.com',
'New Sales Entry for: ' + pageWidgets.ProjectName.value,
htmlbody);
}
onCreate code for the Model:
// onCreate
var email = Session.getActiveUser().getEmail();
var directoryQuery = app.models.Directory.newQuery();
directoryQuery.filters.PrimaryEmail._equals = email;
var reporter = directoryQuery.run()[0];
record.reported_by = email;
record.reported_full_name = reporter.FullName;
record.Date = new Date();
Everything works except for the fullName option. It keeps pulling my name even when another user creates an entry (maybe because I am an admin?). I have a Directory Model setup and that seems to work for when I am displaying the full name for a users's comments.
I would like to have fullName = the name of the person currently creating the entry.
Thank you for your help!
App Startup Script:
// App startup script
// CurrentUser - assuming that it is Directory model's datasource
// configured to load record for current user.
loader.suspendLoad();
app.datasources.Directory.load({
success: function() {
loader.resumeLoad();
},
failure: function(error) {
// TODO: Handle error
}
});
You need to filter your Directory model datasource. If you are planning to use it for different purposes, then I'll recommend to create dedicated datasource for current user. You can filter it on server (preferable) or client side:
Filter on server, load on client:
// Directory model's Server Script for Current User datasource
var query = app.models.Directory.newQuery();
query.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
return query.run();
// ------------------------
// Your startup script will remain almost the same:
loader.suspendLoad();
app.datasources.CurrentUser.load({
success: function() {
loader.resumeLoad();
},
failure: function(error) {
// TODO: Handle error
}
});
Client-only:
var currentUserDs = app.datasources.CurrentUser;
currentUserDs.query.filters.PrimaryEmail._equals = app.user.email;
loader.suspendLoad();
currentUserDs.load({
success: function() {
loader.resumeLoad();
},
failure: function(error) {
// TODO: Handle error
}
});
Thank you to Pavel. Everything worked, but it took me a few to understand exactly what I needed to do. For those who want to try and replicate what I did here were the steps.
First I had to create a Directory Model.
Then under the App Settings section for the app itself (click the gear) I put the following code under the App Startup Script - Client Script section:
loader.suspendLoad();
app.datasources.CurrentUser.load({
success: function() {
loader.resumeLoad();
},
failure: function(error) {
// TODO: Handle error
}
});
Next I went under the Datasources section for the Directory model and added a datasource called CurrentUser.
In the Query - Server Script section I put:
var query = app.models.Directory.newQuery();
query.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
return query.run();
This filters the datasource so that the only entry in there is the current user. Then I adjusted my "var fullName" in the email Client Script to point to the new datasource:
/**
* Calls a server method to send an email.
* #param {Widget} sendButton - widget that triggered the action.
*/
function newSalesEmailMessage(sendButton) {
var pageWidgets = sendButton.root.descendants;
var fullName = app.datasources.CurrentUser.item.FullName;
var htmlbody = '<b><font size="3">' + fullName + '</font></b>' + ' has created a new sales entry for: ' +
'<h1><span style="color:#2196F3">' +pageWidgets.ProjectName.value + '</h1>' +
'<p>Contact: <b>' + pageWidgets.Contact.value + '</b>' +
'<p>Sales Person: <b>' + pageWidgets.SalesPerson.value + '</b>' +
'<p>Notes: <b>' + pageWidgets.Notes.value + '</b>';
google.script.run
.withSuccessHandler(function() {
})
.withFailureHandler(function(err) {
console.error(JSON.stringify(err));
})
.sendEmailCreate(
'test#email.com',
'New Sales Entry for: ' + pageWidgets.ProjectName.value,
htmlbody);
}

Persisting json data in jstree through postback via asp:hiddenfield

I've been pouring over this for hours and I've yet to make much headway so I was hoping one of the wonderful denizens of SO could help me out. Here's the problem...
I'm implementing a tree via the jstree plugin for jQuery. I'm pulling the data with which I populate the tree programatically from our webapp via json dumped into an asp:HiddenField, basically like this:
JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(Items);
json = json.ToLower();
data.Value = json;
Then, the tree pulls the json from the hidden field to build itself. This works perfectly fine up until I try to persist data for which nodes are selected/opened. To simplify my problem I've hardcoded some json data into the tree and attempted to use the cookie plugin to persist the tree state data. This does not work for whatever reason. I've seen other issues where people need to load the plugins in a specific order, etc, this did not solve my issue. I tried the same setup with html_data and it works perfectly. With this working persistence I converted the cookie plugin to persist the data in a different asp:hiddenfield (we can't use cookies for this type of thing in our application.)
essentially the cookie operations are identical, it just saves the array of nodes as the value of a hidden field. This works with the html_data, still not with the json and I have yet to be able to put my finger on where it's failing.
This is the jQuery.cookie.js replacement:
jQuery.persist = function(name, value) {
if (typeof value != 'undefined') { // name and value given, set persist
if (value === null) {
value = '';
}
jQuery('#' + name).attr('value', value);
} else { // only name given, get value
var persistValue = null;
persistValue = jQuery('#' + name).attr('value');
return persistValue;
}
};
The jstree.cookie.js code is identical save for a few variable name changes.
And this is my tree:
$(function() {
$("#demo1").jstree({
"json_data": {
"data" : [
{
"data" : "A node",
"children" : [ "Child 1", "Child 2" ]
},
{
"attr": { "id": "li.node.id" },
"data" : {
"title": "li.node.id",
"attr": { "href": "#" }
},
"children": ["Child 1", "Child 2"]
}
]
},
"persistence": {
"save_opened": "<%= open.ClientID %>",
"save_selected": "<%= select.ClientID %>",
"auto_save": true
},
"plugins": ["themes", "ui", "persistence", "json_data"]
});
});
The data -is- being stored appropriately in the hiddenfields, the problem occurs on a postback, it does not reopen the nodes. Any help would be greatly appreciated.
After looking through this some more, I just wanted to explain that it appears to me that the issue is that the tree has not yet been built from the JSON_data when the persistence operations are being attempted. Is there any way to postpone these actions until after the tree is fully loaded?
If anyone is still attempting to perform the same type of operation on a jsTree version 3.0+ there is an easier way to accomplish the same type of functionality, without editing any of the jsTree's core JavaScript, and without relying on the "state" plugin (Version 1.0 - "Persistence"):
var jsTreeControl = $("#jsTreeControl");
//Can be a "asp:HiddenField"
var stateJSONControl = $("#stateJSONControl");
var url = "exampleURL";
jsTreeControl.jstree({
'core': {
"data": function (node, cb) {
var thisVar = this;
//On the initial load, if the "state" already exists in the hidden value
//then simply use that rather than make a AJAX call
if (stateJSONControl.val() !== "" && node.id === "#") {
cb.call(thisVar, { d: JSON.parse(stateJSONControl.val()) });
}
else {
$.ajax({
type: "POST",
url: url,
async: true,
success: function (json) {
cb.call(thisVar, json);
},
contentType: "application/json; charset=utf-8",
dataType: "json"
}).responseText;
}
}
}
});
//If the user changes the jsTree, save the full JSON of the jsTree into the hidden value,
//this will then be restored on postback by the "data" function in the jsTree decleration
jsTreeControl.on("changed.jstree", function (e, data) {
if (typeof (data.node) != 'undefined') {
stateJSONControl.val(JSON.stringify(jsTreeControl.jstree(true).get_json()));
}
});
This code will create a jsTree and save it's "state" into a hidden value, then upon postback when the jsTree is recreated, it will use its old "state" restored from the "HiddenField" rather than make a new AJAX call and lose the expansions/selections that the user has made.
Got it working properly with JSON data. I had to edit the "reopen" and "reselect" functions inside jstree itself.
Here's the new functioning reopen function for anyone who needs it.
reopen: function(is_callback) {
var _this = this,
done = true,
current = [],
remaining = [];
if (!is_callback) { this.data.core.reopen = false; this.data.core.refreshing = true; }
if (this.data.core.to_open.length) {
$.each(this.data.core.to_open, function(i, val) {
val = val.replace(/^#/, "")
if (val == "#") { return true; }
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
else { remaining.push(val); }
});
if (current.length) {
this.data.core.to_open = remaining;
$.each(current, function(i, val) {
_this.open_node(val, function() { _this.reopen(true); }, true);
});
done = false;
}
}
if (done) {
// TODO: find a more elegant approach to syncronizing returning requests
if (this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
this.data.core.reopen = setTimeout(function() { _this.__callback({}, _this); }, 50);
this.data.core.refreshing = false;
}
},
The problem was that it was trying to find the element by a custom attribute. It was just pushing these strings into the array to search when it was expecting node objects. Using this line
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
instead of
if ($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
was all it took. Using a similar process I was able to persist the selected nodes this way as well.
Hope this is of help to someone.

Capture data from FORM using jQuery/Ajax/JSON

I have a few textboxes on a form and when the user submits I want to capture the data and insert it into a database.
Here is what my code looks like
// Called just before the form is submitted.
beforeSubmit: function(data)
{
var item = $("[id$='item']");
var category = $("[id$='category']");
var record = $("[id$='record']");
var json = "{'ItemName':'" + escape(item.val()) +
"','CategoryID':'" + category.val() + "','RecordID':'" + record.val() + "'}";
//This page is where data is to be retrieved and processed.
var ajaxPage = "DataProcessor.aspx?Save=1";
var options =
{
type: "POST",
url: ajaxPage,
data: json,
contentType: "application/json;charset=utf-8",
dataType: "json",
async: false,
success: function(response)
{
alert("success: " + response);
},
error: function(msg)
{
alert("failed: " + msg);
}
};
//Execute the Ajax call and get a response.
var returnText = $.ajax(options).responseText;
if (returnText == 1) {
record.html(returnText);
$("#divMsg").html("<font color=blue>Record saved successfully.</font>");
}
else
{
record.html(returnText);
$("#divMsg").html("<font color=red>Record not saved successfully.</font>");
}
// $("#data").html("<font color=blue>Data sent to the server :</font> <br />" + $.param(data));
},
Here is what data is sent to the server: if I uncomment the following line.
// $("#data").html("<font color=blue>Data sent to the server :</font> <br />" + $.param(data));
__VIEWSTATE=%2FwEPDwULLTE4ODM1ODM4NDFkZOFEQfA7cHuTisEwOQmIaj1nYR23&__EVENTVALIDATION=%2FwEWDwLuksaHBgLniKOABAKV8o75BgLlosbxAgKUjpHvCALf9YLVCgLCtfnhAQKyqcC9BQL357nNAQLW9%2FeuDQKvpuq2CALyveCRDwKgoPWXDAKhwImNCwKiwImN &day_fi=12&month_fi=12&year_fi=1234&lastFour_fi=777&countryPrefix_fi=1&areaCode_fi=555-555&phoneNumber_fi=5555&email_fi=nisardotnet%40gmail.com&username=nisarkhan&password=123456&retypePassword=123456
Nisardotnet - are you working in C#? You've got way too much going on here. You can cut down your code by half using web methods - also, get rid of viewstate - you don't need it (remove the form from the page)
If your working in C# let me know and I can help.
Rob
****APPEND****
Ok - I built a simple "grab input values and update DB" thing - it's on my site here.
Give me a shout if you've got any questions.
Rob
****APPEND****
Ok, so in your class file you could have
internal static void updateDB(String values)
{
// do something with 'values'
}
Then in your aspx page you can call it like so...
[WebMethod]
public static void updateDB(String values)
{
yourClass.updateDB(values);
}
That should work.
You should be able to pull it all out of the Request.Form.

Resources