Rendering JSON in a RAZOR view in ASP.NET MVC - asp.net-mvc

I'm working on an ASP.NET MVC app. My app is interacting with a third-party REST service. That service is getting called in the controller of my MVC app. The results from the service look like this:
{
"group#odata.type": "#Collection",
"group": [],
"class#odata.type": "#Collection",
"class":[
{ "total": 111, "value": "A" },
{ "total": 222, "value": "B" },
{ "total": 333, "value": "C" }
],
"status#odata.type": "#Collection",
"status": [
{ "total": 1, "value": "Open" },
{ "total": 20, "value": "Closed" },
{ "total": 51, "value": "Unknown" }
]
}
The results of the service are stored in a JObject property in my model called Results. For every array, I am trying to print out its key name. Then, I want to look through and print out each value and total in the array. In other words, the JSON above would look like this:
group
class
A - 111
B - 222
C - 333
status
Open - 1
Closed - 20
Unknown - 51
In an attempt to do this, I have the following in my View.
foreach (var result in Model.Results)
{
<div></div>
<ul>
#foreach (var subResult in result.?)
{
<li style="padding-left:8px;">#(subResult["value"] + " - " + subResult["total"])</li>
}
</ul>
}
Clearly the above doesn't work. My challenge is, I do not understand how to:
Loop through the key/value pairs in a JObject.
Identify if the value is a JArray or another JObject.
If I use result.Children(), I do not get each key/value pair like I'm expecting. At the same time, result does not have a Keys property like I would expect. I feel very stuck at the moment.
Thank you for any help you can provide. Happy holidays!

According to the documentation on JObject, it should implement IDictionary<string, JToken>. They might have done an explicit implementation of the interface, so you'd need to cast your JObject instance to IDictionary<string, JToken> first.

First of all define some classes that correspond to your Json response:
public class ServiceResponce
{
public IList<Row> Group{get;set;}
public IList<Row> Class{get;set;}
public IList<Row> Status{get;set;}
}
public class Row
{
public int Total {get;set;}
public string Value {get;set;}
}
Than you can use Json.Net or some other library to deserialize your Json in to an object:
JsonConvert.DeserializeObject<ServiceResponce>(json);
So your model will finally have ServiceResponce property:
public ServiceResponce Result{get;set;}
Than you can easily navigate through ServiceResponce properies:
<div></div>
<ul>
foreach (var result in Model.Result.Group)
{
<li style="padding-left:8px;">#(result.Value + " - " + result.Total)</li>
}
<ul>
To make it cleaner write an HtmlHelper that receives a IList<Row> as a parameter and renders it in a required format, so at the end you will end with something like:
#YourHelper(Model.Result.Group)
#YourHelper(Model.Result.Class)
#YourHelper(Model.Result.Status)

Related

Why ModelState returns different result and how to fix it?

Asp.net core 3.1 WebApi.
I have a model with required properties.
1.If model is not valid, then the response contains data
like :
{
"errors": {
"Name": [
"Update model can't have all properties as null."
],
"Color": [
"Update model can't have all properties as null."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|f032f1c9-4c36d1e62aa60ead."
}
And this looks good for me.
But if I add some custom validation to modelState.AddModelError("statusId", "Invalid order status id.") then it returns different structure:
[
{
"childNodes": null,
"children": null,
"key": "statusId",
"subKey": {
"buffer": "statusId",
"offset": 0,
"length": 8,
"value": "statusId",
"hasValue": true
},
"isContainerNode": false,
"rawValue": "11202",
"attemptedValue": "11202",
"errors": [
{
"exception": null,
"errorMessage": "Invalid order status id."
}
],
"validationState": 1
}
]
Also looks like ModelState.IsValid is actual no more for controller, because the bad request is returned before it even enters the controller with invalid mode. Or is there some flag with global validation via ModelSate?
Why the structure is different?
How to make it the same?
How to force to get to ModelState.IsValid method inside of api controller as it worked in MVC?
Update:
[Route("....")]
[Authorize]
[ApiController]
public class StatusesController : ApiControllerBase
{
[HttpPut, Route("{statusId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[Produces("application/json")]
public async Task<ObjectResult> UpdateStatusAsync(int statusId, [FromBody] StatusUpdateDto orderStatusUpdateDto)
{
int companyId = User.Identity.GetClaimValue<int>(ClaimTypes.CompanyId);
const string errorNotFound = "There is not order status with this id for such company";
if (statusId <= 0)
{
Logger.LogError(errorNotFound);
ModelState.AddErrorModel(nameof(statusId), "Invalid order status id")
throw new NotFound(ModelState);
}
if (orderStatusUpdateDto == null)
{
const string error = "Invalid (null) order status can't be added";
Logger.LogError(error);
throw new ArgumentNullException(error);
}
if (ModelState.IsValid == false) // also this code is always true or returns 400 before this line
{
return BadRequest(ModelState);
}
....
return result;
}
}
The ApiController attribute adds some specific opinionated behaviors to a controller. One of them is to return a 400 error if the model is not valid.
This behavior can be disabled, but only on a global level.
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
I think you have the following options:
Disable this behavior and check ModelState.IsValid yourself. Use ValidationProblem method to produce the same response
Add this check to the validator of the model
Keep everything as it is. But use ValidationProblem inside the controller method to return the validation errors.
See https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#automatic-http-400-responses for information

Difference with the previous value. kslqDB

I was trying to make an example with ksqlDB
Based on two properties, in this case "sensor_id" and "user_id".
I calculated the variation of the "value" field, when both attributes match.
// TOPIC KAFKA
{"timestamp": 1000000000, "user_id": "AAAAA", "sensor_id": "sensor_1111", "value": 10000}
{"timestamp": 1555550000, "user_id": "AAAAA", "sensor_id": "sensor_1111", "value": 22000}
{"timestamp": 1666660000, "user_id": "AAAAA", "sensor_id": "sensor_2222", "value": 22000}
{"timestamp": 1777770000, "user_id": "AAAAA", "sensor_id": "sensor_2222", "value": 25000}
{"timestamp": 1666660000, "user_id": "BBBBB", "sensor_id": "sensor_2222", "value": 30000}
{"timestamp": 1777770000, "user_id": "BBBBB", "sensor_id": "sensor_2222", "value": 40000}
When that two values coincide, I calculated the difference with the previous one.
And the elapsed time.
// RESULT
{"timestamp": 1555550000, "last_timestamp": 1000000000, "user_id": "AAAAA", "sensor_id": "sensor_1111", "value": 12000}
{"timestamp": 1777770000, "last_timestamp": 1666660000, "user_id": "AAAAA", "sensor_id": "sensor_2222", "value": 3000}
{"timestamp": 1777770000, "last_timestamp": 1666660000, "user_id": "BBBBB", "sensor_id": "sensor_2222", "value": 10000}
Something like this should be possible using a custom user defined aggregate function or UDAF.
If you were to write a Compare_to_previous UDAF which stores the first value it receives and compares it to the second value, then you could achieve what you want with:
CREATE TABLE DIFF AS SELECT
user_id,
sensor_id,
Compare_to_previous(ROWTIME) as last_timestamp,
Compare_to_previous(value) as value
GROUP BY user_id, sensor_id
HAVING Compare_to_previous(ROWTIME) IS NOT NULL;
Where compare_to_previous might be something like (not tested):
#UdafDescription(
name = "COMPARE_TO_PREVIOUS",
description = CompareToPrevious.DESCRIPTION
)
public final class CompareToPrevious {
static final String DESCRIPTION = "Blah blah";
// UDAF version for BIGINT:
private static Udaf<Long, List<Long>, Long> compareToPrevious() {
return new Udaf<Long, List<Long>, Long>() {
#Override
public List<Long> initialize() {
// Initial value is empty() meaning no previous.
return ImmutableList.of();
}
#Override
public List<Long> aggregate(final Long current, final List<Long> previous) {
if (current == null) {
// Ignore null values? You're choice!
return previous;
}
switch (previous.size()) {
case 0:
// 1st value seen for this key:
return ImmutableList.of(current);
case 1:
// 2nd value seen for this key:
return ImmutableList.of(previous.get(0), current);
default:
// Already seen two values - what to do?
// Do you want to compare pairs
// i.e. compare 2nd with 1st, then 4th with 3rd, etc.
// return ImmutableList.of()
// or
// Or each subsequent row with previous,
// i.e. you'd compare 2nd with 1st, then 3rd with 2nd etc.
return ImmutableList.of(previous.get(0), current);
}
}
#Override
public List<Long> merge(final List<Long> aggOne, final List<Long> aggTwo) {
throw new UnsupportedOperationException(
"Compare_to_previous won't work with session windowed data");
}
// Call to convert the intermediate List<Long> into an output value.
#Override
public Long map(final List<Long> agg) {
if (agg.size() != 2) {
// If we don't have two values to compare we output NULL.
// Use HAVING Compare_To_Previous(COL) IS NOT NULL in the query to exclude these null rows.
return null;
}
// We have two values - calculate the diff:
return ((long) agg.get(1)) - agg.get(0);
}
};
}
}
Such an aggregator won't be able to handle out-of-order data, but it will be suitable for many use cases.

How to create dynamic menu using tree

I have an Angular project but this is not directly related to Angular and I just need the logic of create dynamic menu using tree that can also be similar as in ASP.NET MVC project. So, your suggestion for ASP.NET MVC, etc. will also be helpfu for me.
I use PrimeNG Tree and want to obtain menu from a table in MSSQL database:
Menu Table (the data was changed for example usage):
Id | Order | ParentId | Name |
1 1 0 Documents
2 1 1 Work
3 1 2 Expenses.doc
4 2 2 Resume.doc
5 2 1 Home
6 1 5 Invoices.txt
...
In order to populate the menu items, I need to generate a JSON string as shown below:
{
"data":
[
{
"label": "Documents",
"data": "Documents Folder",
"expandedIcon": "fa-folder-open",
"collapsedIcon": "fa-folder",
"children": [{
"label": "Work",
"data": "Work Folder",
"expandedIcon": "fa-folder-open",
"collapsedIcon": "fa-folder",
"children": [{"label": "Expenses.doc", "icon": "fa-file-word-o", "data": "Expenses Document"}, {"label": "Resume.doc", "icon": "fa-file-word-o", "data": "Resume Document"}]
},
{
"label": "Home",
"data": "Home Folder",
"expandedIcon": "fa-folder-open",
"collapsedIcon": "fa-folder",
"children": [{"label": "Invoices.txt", "icon": "fa-file-word-o", "data": "Invoices for this month"}]
}]
},
... //omitted for brevity
]
}
So, I have really no idea about the logic and database table design (menus). Should I generate the JSON above on the Controller or another place? Could you please post suggestions and sample approaches regarding to this issue?
Your database Menu table is fine to generate the treeview using the PrimeNG Tree plugin except that you may want to include an additional property for the data property if you want. I would however suggest you make the ParentId property nullable so that your top level item (Documents) has a null value rather that 0.
In order to pass json in that format, your model need to be
public class MenuVM
{
public int Id { get; set; } // this is only used for grouping
public string label { get; set; }
public string expandedIcon { get; set; }
public string collapsedIcon { get; set; }
public string icon { get; set; }
public IEnumerable<MenuVM> children { get; set; }
}
You might also include other properties such as
public string data { get; set; }
to match the properties in the api
You also need a parent model for the data property
public class TreeVM
{
public IEnumerable<MenuVM> data { get; set; }
}
To generate the model, you controller code would be (note this is based on the ParentId field being null for the top level item as noted above)
// Sort and group the menu items
var groups = db.Menus
.OrderBy(x => x.ParentId).ThenBy(x => x.Order)
.ToLookup(x => x.ParentId, x => new MenuVM
{
Id = x.Id,
label = x.Name
});
// Assign children
foreach (var item in groups.SelectMany(x => x))
{
item.children = groups[item.Id].ToList();
if (item.children.Any())
{
.... // apply some logic if there a child items, for example setting
// the expandedIcon and collapsedIcon properties
}
else
{
.... // apply some logic if there are no child items, for example setting
// the icon properties - e.g. item.icon = "fa-file-word-o";
}
}
// Initialize model to be passed to the view
TreeVM model = new TreeVM
{
data = groups[null].ToList();
}
return Json(model, JsonRequestBehavior.AllowGet);
For your icons, you should consider some const values or an enum rather than hard-coding strings.

Posting json array with strings to Asp.Net MVC action controller is always null

I cannot get Asp.Net MVC to bind a list of strings sent as an json object unless I wrap them in a class. I have a hard time believing that it is a must to wrap the list like that. Been looking at various examples and what not for a few hours, but cannot get any other solution to work. What am I missing? Working and non-working code below.
$.ajax({
type: "POST",
traditional: true,
url: "keyapi/activate.json",
data: JSON.stringify({ "keys": [ "1", "2" ] }),
contentType: "application/json; charset=utf-8",
success: function (data) {
alert(data);
}
});
When using an action controller such as the one below "keys" will be null.
[HttpPost]
public Response<List<string>> ActivateKeys(List<string> keys)
{
return KeyService.ActivateKeys(test.Keys);
}
If I add a class to wrap the list it works. Am I forced to do so, or am I missing something here? The code below works, using the same jquery post code.
[HttpPost]
public Response<List<string>> ActivateKeys(Test test)
{
return KeyService.ActivateKeys(test.Keys);
}
public class Test
{
List<string> Keys { get; set; }
}
I'd suggest two things
1) Replace
data: JSON.stringify({ "keys": [ "1", "2" ] }),
by
data: JSON.stringify([ "1", "2" ] ), // <-- Send the array directly
doing this you'll be passing an array as parameter instead of an object containing an array propery named keys
2) If previous suggestion alone doesn't work, replace
ActivateKeys(List<string> keys)
by
ActivateKeys(IList<string> keys) // <-- Use interface IList<T>
as not 100% sure if mvc engine would be able to resolve to List<string>

Custom Web API Formatting

I've been playing around with asp.net web api, and I noticed that default generated returned json doesn't include the object level key. I was also wondering how to customize the output of the json string. For example:
Getting a User usually returns
{
"Name": "ChaoticLoki",
"Age": 22,
"Sex": "Male"
}
I was hoping I could return something like:
{
"data": {
"Name": "ChaoticLoki",
"Age": 22,
"Sex": "Male",
},
"success": true
}
You can then create a class wrapping the data and status like this
public class JsonResult{
public object data { get; set;}
public boolean success { get; set;}
}
And then in your Web Api methods, you can do
var data = ... //getting from repo
return Json(new JsonResult{data = data, success = true});

Resources