Custom Web API Formatting - asp.net-mvc

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});

Related

Displaying enum values together with a referenced class in Swagger using Swashbuckle

I am using Swashbuckle to generate Swagger output. On my model I the following property:
public FieldValue? MyProperty { get; set; }
FieldValue is a simple class that can hold an ID and a name:
public class FieldValue
{
/// <summary>
/// The numerical represenation of the value.
/// </summary>
/// <example>1</example>
public string Id { get; set; }
/// <summary>
/// The textual represenation of the value.
/// </summary>
/// <example>SomeValue</example>
public string Name { get; set; }
}
Via some business logic in our code, a set of possible key-value pairs (ID and name) are mapped to this property.
Now, I would like to show all the possible values for MyProperty in the enum tag in my Swagger output. Using an implementation of ISchemaFilter, a custom attribute on the MyProperty property and using allOf instead of a simple ref, I have managed to add the values in my JSON output, see below. The reason for using allOf instead of just ref is that ref will overwrite all properties on the referencing schema. So, when I use allOf it keeps my description and enum fields in the document.
{
"components": {
"schemas": {
"FieldValue": {
"type": "object",
"properties": {
"Id": {
"type": "string",
"description": "The numerical represenation of the value.",
"nullable": true,
},
"Name": {
"type": "string",
"description": "The textual represenation of the value.",
"nullable": true,
}
}
},
"MyProperty": {
"enum": [
"1: EnumValue1",
"2: EnumValue2",
"3: EnumValue3"
],
"allOf": [{
"$ref": "#/components/schemas/FieldValue"
}
],
"description": "Some description",
"nullable": true
}
}
}
This does not show the enum-values in the Swagger UI, though. It does show the description.
Is this Swagger doc valid? If not, how can I make it valid and display the enums for MyProperty?
The correct representation of your use case is:
"MyProperty": {
"enum": [
{"Id": "1", "Name": "EnumValue1"},
{"Id": "2", "Name": "EnumValue2"},
{"Id": "3", "Name": "EnumValue3"}
],
"allOf": [
{ "$ref": "#/components/schemas/FieldValue" }
],
"description": "Some description",
"nullable": true
}
However, there's little to no tooling support for enums containing object literals. I suggest mentioning the possible Id and Name values in the description of the MyProperty property.

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

Json.Net custom serialization

[
{
"articles": [
[
"2016-03-04",
6
],
[
"2016-03-05",
10
],
[
"2016-03-06",
11
]
],
"accession": "00000446-201301000-00018"
},
{
"articles": [
[
"2016-03-04",
1
],
[
"2016-03-08",
17
],
[
"2016-03-09",
10
]
],
"accession": "00000446-201301000-00019"
}]
List is input { "Accession"= "00000446-201301000-00018", "Date"= "635926464000000000","Rank" =2},{ "Accession" = "00000446-201301000-00019", "Date" = "635931648000000000","Rank" = 2}
I want json data exactly like this,data is coming from list and list is dynamically growing.Form list, I want all dates and rank club to gether for the same accession number.
I want to use newtonsoft json custom serlization to convert my list to json.
What you want to do does not need any Custom Formatter, you just have to re-structure your data, Here is an example for restructuring a list based on Entity class to the new required one.
class Entity
{
public string Accession { get; set; }
public string Date { get; set; }
public int Rank { get; set; }
}
Add this line if you need to read the list from Json
var list = JsonConvert.DeserializeObject<List<Entity>>(input);
Here is the code for changing the structure of data to array based articles.
var translatedAsArray = list.GroupBy(e => e.Accession)
.Select(g =>
new {
//change new 'object[]' to 'new' to make the article an object
//you can convert i.Date, i.Rank and g.Key to anything you want here
Articles = g.Select(i => new object[] { i.Date , i.Rank }),
Accessing = g.Key
}
);
var json = JsonConvert.SerializeObject(translatedAsArray, Formatting.Indented);
Model
public class FinalList
{
public string accession {get;set;}
public List<ArticlesDetails> Articles{get;set;}
}
public class ArticlesDetails
{
public DateTime Date{get;set;}
public int number{get;set;}
}
Use ObjectContent instead of StringContent so we let the formatter (i.e. JsonMediaTypeFormatter) to deal with the serialization and deserialization
In config
config.Formatters.Insert(0, new YourCustomFormatter());
config.Formatters.Insert(1, new JsonMediaTypeFormatter());
and disable post in your custom formatter so that JsonMediaTypeFormatter desrializes the complex data

Breeze# executeQuery returns empty objects

I have Breeze.Sharp application which communicates with legacy WebAPI which doesn't provide metadata.
Query seems to be executed properly - expected number of objects is returned but all of them are empty. Modifying query parameters also works - number of returned objects changes as expected. I was playing around with EntityManager.MetadataStore but nothing helped.
Here is the final code I'm currently using to communicate with WebAPI.
public class DokumentModelBreeze: BaseEntity
{
public string id { get; set; }
public string numer { get; set; }
public decimal suma { get; set; }
}
...
Configuration.Instance.ProbeAssemblies(typeof(DokumentModelBreeze).Assembly);
var manager = new EntityManager("http://localhost:52357/api/");
manager.DataService.HasServerMetadata = false;
var store = manager.MetadataStore;
store.SetResourceName("dokumenty", typeof(DokumentModelBreeze), true);
store.NamingConvention = new CamelCasePropertiesNamingConvention();
var builder = new EntityTypeBuilder<DokumentModelBreeze>(store);
builder.DataProperty(d => d.id).IsPartOfKey();
using(TextWriter writer = File.CreateText("C:/metadata.txt")) {
store.ExportMetadata(writer);
var query = new EntityQuery<DokumentModelBreeze>("dokumenty");
query = query.WithParameter("nrFirmy", 1).Where(p => p.numer=="123");
var results = await manager.ExecuteQuery<DokumentModelBreeze>(query);
List<DokumentModelBreeze> Dokumenty = new List<DokumentModelBreeze>();
foreach(var item in results)
Dokumenty.Add(item);
In the last foreach loop every item is of type DokumentModelBreeze but each member property equals to null or 0 respectively.
I have saved MetadataStore to file, it is included below:
{
"metadataVersion": "1.0.3",
"namingConvention": {
"clientServerNamespaceMap": {},
"name": "camelCaseProperties"
},
"structuralTypes": [
{
"shortName": "BaseEntity",
"namespace": "Breeze.Sharp",
"baseTypeName": "",
"autoGeneratedKeyType": "None"
},
{
"shortName": "DokumentModelBreeze",
"namespace": "BRuNETWPF.ViewModels",
"baseTypeName": "BaseEntity:#Breeze.Sharp",
"autoGeneratedKeyType": "None",
"defaultResourceName": "dokumenty",
"dataProperties": [
{
"name": "id",
"dataType": "String",
"isNullable": false,
"defaultValue": "",
"isPartOfKey": true
},
{
"name": "numer",
"dataType": "String",
"isNullable": false,
"defaultValue": ""
},
{
"name": "suma",
"dataType": "Decimal",
"isNullable": false,
"defaultValue": 0.0
}
]
}
],
"resourceEntityTypeMap": {
"dokumenty": "DokumentModelBreeze:#BRuNETWPF.ViewModels"
}
}
Am I missing something here or perhaps Breeze# doesn't allow to query WebAPI without metadata ?
The same code executed against test WebAPI with exposed metadata works well.
Your GetValue and SetValue properties must be defined like so:
public string id
{
get { return GetValue<string>("id"); }
set { SetValue(value); }
}
It's a pain, I know, but this fixed it for me after one of our awesome tech leads pointed it out :)

Can't get "likes" our of Facebook JSON data using MVC

I'm trying to display the # of "likes" of a user selected entity that has a facebook site. The url that's passed in is the entities facebook name:
public int GetLikes(string url)
{
string jsonString = new WebClient().DownloadString("http://graph.facebook.com/?ids=" + url);
Dictionary<string, dynamic> values = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(jsonString);
int keyValues = values.Count;
int likes = values["likes"];
return likes;
}
I get an error that "likes" is not found in the value. But it's clearly there. Below is a sample of the JSON data that's returned from facebook:
{
"disney": {
"about": "\"It's kind of fun to do the impossible.\" - Walt Disney",
"category": "Company",
"checkins": 26,
"description": "This Page is a place for our Fans. However, we do need to have certain rules. Please be aware that we do not accept or consider unsolicited idea submissions and, also, we must reserve the right to remove any posting or other material that we find off-topic, inappropriate or objectionable.",
"founded": "1923",
"is_published": true,
"talking_about_count": 543991,
"username": "Disney",
"website": "http://disney.com",
"were_here_count": 0,
"id": "11784025953",
"name": "Disney",
"link": "http://www.facebook.com/Disney",
"likes": 46622418,
"cover": {
"cover_id": "10152010506970954",
"source": "http://sphotos-d.ak.fbcdn.net/hphotos-ak-prn1/t1.0-9/s720x720/1960125_10152010506970954_1312297640_n.png",
"offset_y": 0,
"offset_x": 0
}
}
}
I have tried many different things. Getting and deserializing the JSON data isn't a problem but I haven't been able to find a way to get any of the elements. Any help is appreciated.
First Create a class that mimics your JSON string (object) structure:
public class JSONobject
{
public Disney = new Disney();
}
public class Disney
{
public string about { get; set; }
public string category{get;set;}
public int checkins = {get;set;}
........
public int likes = {get;set;}
........
public Cover = new Cover();
}
public class Cover
{
public int cover_id { get;set; }
........
}
Then, initialize the object as well as the serializer:
JSONobject jsonOb = new JSONobject();
JavaScriptSerializer serializer = new JavaScriptSerializer();
Finally, parse the jsonString into your defined class:
try
{
jsonOb = serializer.Deserialize<JSONobject>(jsonString);
//ViewBag.jsondecoded = "Yes";
}
catch (Exception e)
{
//ViewBag.jsonDecoded = "No" + ", Exception: " + e.Message.ToString();
}
Retrieving variables:
string about = jsonOb.Disney.about;
string category = jsonOb.Disney.category;
int checkins = jsonOb.Disney.checkins;
int likes = jsonOb.Disney.likes;
Cover cover = jsonOb.Disney.Cover;
Hope this helps.

Resources