Using .Net Core Web API with JsonPatchDocument - asp.net-core

I am using JsonPatchDocument to update my entities, this works well if the JSON looks like the following
[
{ "op": "replace", "path": "/leadStatus", "value": "2" },
]
When i create the object it converts it with the Operations node
var patchDoc = new JsonPatchDocument<LeadTransDetail>();
patchDoc.Replace("leadStatus", statusId);
{
"Operations": [
{
"value": 2,
"path": "/leadStatus",
"op": "replace",
"from": "string"
}
]
}
if the JSON object looks like that the Patch does not work. I believe that i need to convert it using
public static void ConfigureApis(HttpConfiguration config)
{
config.Formatters.Add(new JsonPatchFormatter());
}
And that should sort it out, the problem is i am using .net core so not 100% sure where to add the JsonPatchFormatter

I created the following sample controller using the version 1.0 of ASP.NET Core. If I send your JSON-Patch-Request
[
{ "op": "replace", "path": "/leadStatus", "value": "2" },
]
then after calling ApplyTo the property leadStatus will be changed. No need to configure JsonPatchFormatter. A good blog post by Ben Foster helped me a lot in gaining a more solid understanding - http://benfoster.io/blog/aspnet-core-json-patch-partial-api-updates
public class PatchController : Controller
{
[HttpPatch]
public IActionResult Patch([FromBody] JsonPatchDocument<LeadTransDetail> patchDocument)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var leadTransDetail = new LeadTransDetail
{
LeadStatus = 5
};
patchDocument.ApplyTo(leadTransDetail, ModelState);
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
return Ok(leadTransDetail);
}
}
public class LeadTransDetail
{
public int LeadStatus { get; set; }
}
Hope this helps.

Related

SQL Server stored procedure in .NET Core 6 Web API to produce JSON data used in Angular app

I have a SQL Server stored procedure that has an ID parameter and returns a string in JSON format that is needed in the Angular app.
Here is a sample of the JSON needed:
[
{
"type": "date",
"name": "asofdate",
"ui":
{
"label": "As Of Date",
"placeholder": "Enter a date"
},
"validators": { "required": "true" }
},
{
"type": "select",
"name": "scope",
"ui": { "label": "Scope", "placeholder": "Select a scope" },
"validators": { "required": "true" },
"source": [
{ "value": 1, "text": "ABC" },
{ "value": 2, "text": "CDE" },
{ "value": 3, "text": "FGI" }
]
}
]
Here is a what the result of running the stored procedure looks like:
When I run the Web API passing the ID parameter to the stored procedure, I would like to capture the response as a JSON object to be used in the Angular app.
But the Web API is returning this:
[
{
"jsonResponse": "[
{
\"type\":\"date\",
\"name\":\"asofdate\",
\"ui\":{\"label\":\"As Of Date\",\"placeholder\":\"Enter a date\"},
\"validators\":{\"required\":\"true\"}
}
,
{
\"type\":\"select\",
\"name\":\"scope\",
\"ui\":{\"label\":\"Scope\",\"placeholder\":\"Select a scope\"},
\"validators\":{\"required\":\"true\"},
\"source\":[{\"value\":1,\"text\":\"ABC\"},{\"value\":2,\"text\":\"DEF\"},{\"value\":3,\"text\":\"GHI\"}]}
}
]
Is there a way to get the JSON response from the Web API without all the "\" and without:
{
"jsonResponse": "
so that it matches the sample above?
Here is the code from the Web API:
[HttpGet("{ReportID}")]
public async Task<ActionResult<IEnumerable<usp_ReportParameterResult>>> GetReportParameters(int ReportID)
{
if (_context.usp_ReportParameterAsync == null)
{
return NotFound();
}
var op = new OutputParameter<int>();
var JSONresponse = await _context.usp_ReportParameterAsync(ReportID, op);
if (JSONresponse == null)
{
return NotFound();
}
return JSONresponse;
}
The stored procedure uses JSON_QUERY and JSON PATH to create the needed nested arrays.
So, in the angular code I have the following hard-coded:
TESTDATA:any[] = [
{
type:'text',
name:'firstName',
validators:{
required:true
},
ui:{label:'First Name',placeholder:'Enter Your First Name'}
}
,
{
"type":"date",
"name":"asofdate",
"ui":{"label":"****As Of Date","placeholder":"Enter a date","class":["date-picker-wrapper"]},
"validators":{"required":"true"}
}
]
What I need is instead of this data being hrad-coded it is being dynamically generated from a Web API.
The hard-coded data looks like the following from browser debug:
[![enter image description here][2]][2]
From the web api data looks like the following:
It is not an array like the TESTDATA. Is the a way to get response from web api into an array format as required?
Actually, easiest solution was to remove the backlashes in the Angular app by simply doing the following:
for (let item of this.formattedJSON) {
item.ui = JSON.parse(item.ui);
item.validators = JSON.parse(item.validators);
}

Return string from Web API .NET Core get operation

I have a get operation that I want to return a string from. An example would be
"000875"
When I return this string from a controller in my Web API Controller in full .NET, it formats it like this:
{
"Property": "000875"
}
When I return the string in my converted .NET Core Controller it returns this:
{
"$id": "1",
"$type": "System.Net.Http.HttpResponseMessage, System.Net.Http",
"Version": "1.1",
"Content": {
"$id": "2",
"$type": "System.Net.Http.StringContent, System.Net.Http",
"Headers": [
{
"Key": "Content-Type",
"Value": [
"application/json; charset=utf-8"
]
}
]
},
"StatusCode": "OK",
"ReasonPhrase": "OK",
"Headers": [],
"TrailingHeaders": [],
"RequestMessage": null,
"IsSuccessStatusCode": true
}
It is interesting to note that the value is not even in there!
I am running some interesting JSON Serialization to make BreezeJs work with .NET Core. It is possible that it is the cause of this weirdness:
.AddNewtonsoftJson(opt =>
{
// Let Breeze say how we serialize. This adds in the Type and Id options the way breeze expects
var jsonSerializerSettings = JsonSerializationFns.UpdateWithDefaults(opt.SerializerSettings);
......
I am hoping for a way to get strings through without all this mess. Can that be done?
I get the impression that the subject action definition returns HttpResponseMessage.
public HttpResponseMessage MyAction(....
HttpRequestMessage is no longer a first class citizen in asp.net-core framework and will be treated as a normal model and serialized.
That explains the JSON you are seeing with your controller
The syntax needs to be updated to return IActionResult derived responses
public IActionResult MyAction() {
//...
return Ok("000875");
}
ActionResult<T>
public ActionResult<string> MyAction() {
//...
if(somecondition)
return NotFound();
return "000875";
}
or the model itself.
public string MyAction() {
//...
return "000875";
}
Reference Controller action return types in ASP.NET Core Web API

Serialize data from PostGIS to JSON

I'm working on a .NET Core REST service which will have a controller that will send geometry data back a GeoJson so I can show it on my Leaflet map.
I'm having trouble converting my class to JSON.
I've tried this:
StringBuilder sb = new StringBuilder();
JsonTextWriter writer = new JsonTextWriter(new StringWriter(sb));
var jsonSerializer = GeoJsonSerializer.Create(new JsonSerializerSettings()
{NullValueHandling = NullValueHandling.Ignore});
jsonSerializer.Serialize(writer, _context.Locations);
writer.Flush();
return Ok(sb.ToString());
But then I get this error
Self referencing loop detected for property 'CoordinateValue' with type 'GeoAPI.Geometries.Coordinate'
I've been looking for examples and reading docs all day but can't get it to work.
The class I need to serialize:
public class SobekLocation
{
public int Id { get; set; }
public string LocationId { get; set; }
public bool Use { get; set; }
public Point Location { get; set; }
}
Any suggestions are much appreciated.
Edit:
I'm making my own featurecollection and am trying to serialize that. It is doing something but it is not valid GeoJSON.
public FeatureCollection GetLocationsAsFeatureCollection()
{
var featureCollection = new FeatureCollection();
foreach (var location in GetLocations().Take(5))
{
var attr = new AttributesTable
{
{"Id", location.Id},
{"Use", location.Use},
{"LocationId", location.LocationId}
};
var feature = new Feature(location.Location, attr);
featureCollection.Add(feature);
}
return featureCollection;
}
And de serialization part:
var fc = _service.GetLocationsAsFeatureCollection();
var sb = new StringBuilder();
var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings
{
// To prevent the Self referencing loop error:
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
using (var sw = new StringWriter(sb))
{
jsonSerializer.Serialize(sw, fc);
}
return Ok(sb.ToString());
The serialized data:
{
"Features":[
{
"Geometry":{
"CoordinateSequence":{
"Dimension":3,
"Ordinates":7,
"Count":1
},
"Coordinates":[
{
"X":191390.015,
"Y":523064.716597462,
"Z":"NaN"
}
],
"NumPoints":1,
"IsEmpty":false,
"Dimension":0,
"BoundaryDimension":-1,
"X":191390.015,
"Y":523064.716597462,
"Coordinate":{
"X":191390.015,
"Y":523064.716597462,
"Z":"NaN"
},
"GeometryType":"Point",
"OgcGeometryType":1,
"Boundary":[
],
"Z":"NaN",
"M":"NaN",
"Factory":{
"PrecisionModel":{
"IsFloating":true,
"MaximumSignificantDigits":16,
"Scale":0,
"PrecisionModelType":0,
"OffsetX":0,
"OffsetY":0
},
"CoordinateSequenceFactory":{
"Ordinates":7
},
"SRID":28992
},
"UserData":null,
"SRID":28992,
"PrecisionModel":{
"IsFloating":true,
"MaximumSignificantDigits":16,
"Scale":0,
"PrecisionModelType":0,
"OffsetX":0,
"OffsetY":0
},
"NumGeometries":1,
"IsSimple":true,
"IsValid":true,
"Area":0,
"Length":0,
"EnvelopeInternal":{
"IsNull":false,
"Width":0,
"Height":0,
"MinX":191390.015,
"MaxX":191390.015,
"MinY":523064.716597462,
"MaxY":523064.716597462,
"Area":0,
"MinExtent":0,
"MaxExtent":0,
"Centre":{
"X":191390.015,
"Y":523064.716597462,
"Z":"NaN"
}
},
"IsRectangle":false
},
"Attributes":[
{
"Key":"Id",
"Value":1
},
{
"Key":"Use",
"Value":false
},
{
"Key":"LocationId",
"Value":"KNP_1"
}
],
"BoundingBox":null
}
],
"Type":"FeatureCollection",
"CRS":null,
"Count":5,
"BoundingBox":null
}
As you can see all properties start with an uppercase but they should be in lowercase. And after changing the properties manually geojsonlint.com reports: GeoJSON features must have a type=feature member
Edit2:
I've made some progress. I'm using the GeoJSON.Net package now and I can now create valid GeoJSON accordin to http://geojsonlint.com/. But still my Leaflet map is complaining about invalid GeoJSON so the map is still not showing anything ;)
Here's my new converter:
public GeoJSON.Net.Feature.FeatureCollection GetLocationsAsGeoJsonFeatureCollection()
{
var featureCollection = new GeoJSON.Net.Feature.FeatureCollection();
foreach (var location in GetLocations())
{
var attr = new Dictionary<string, object>()
{
{"Id", location.Id},
{"Use", location.Use},
{"LocationId", location.LocationId}
};
var position = new Position(location.Location.Coordinate.X, location.Location.Coordinate.Y);
var geom = new GeoJSON.Net.Geometry.Point(position);
var feature = new GeoJSON.Net.Feature.Feature(geom, attr, location.Id.ToString());
featureCollection.Features.Add(feature);
}
return featureCollection;
}
And the GeoJSON it produces:
{
"type":"FeatureCollection",
"features":[
{
"type":"Feature",
"id":"1",
"geometry":{
"type":"Point",
"coordinates":[
523064.716597462,
191390.015
]
},
"properties":{
"id":1,
"use":false,
"locationId":"KNP_1"
}
},
{
"type":"Feature",
"id":"2",
"geometry":{
"type":"Point",
"coordinates":[
519349.860113963,
170162.249352578
]
},
"properties":{
"id":2,
"use":false,
"locationId":"CP_6"
}
},
{
"type":"Feature",
"id":"3",
"geometry":{
"type":"Point",
"coordinates":[
519952.507022603,
170402.383673312
]
},
"properties":{
"id":3,
"use":false,
"locationId":"CP_UR_5_1"
}
},
{
"type":"Feature",
"id":"4",
"geometry":{
"type":"Point",
"coordinates":[
519948.062527202,
170582.612655391
]
},
"properties":{
"id":4,
"use":false,
"locationId":"CP_UR_6_1"
}
},
{
"type":"Feature",
"id":"5",
"geometry":{
"type":"Point",
"coordinates":[
519902.432252114,
170602.894875503
]
},
"properties":{
"id":5,
"use":false,
"locationId":"CP_UR_6_2"
}
}
]
}
This is my code for Leaflet:
L.geoJson('http://localhost:5000/api/data/',
{
attribution: 'Mine'
}
).addTo(map);
I'm still looking for advice how to easily plot my PostGIS data on a Leaflet map.

Custom ASP.Net Core JSON model binder

My posted JSON object is this:
{{
"emails": [
{
"To": "info#gmail.com",
"Subject": "Subject",
"Body": "Body",
"ID": "d3d13242-6eff-4c57-b718-ef5ad49fe301"
},
{
"To": "hr#gmail.com",
"Subject": "Subject",
"Body": "Body",
"ID": "101edaf0-fcb4-48fc-9e9e-0d7492b591b0"
}
]
}}
By default ASP.NET model binder will not bind this JSON object and as you can see here I get always null when I send post request to the API:
[HttpPost, Route("Send")]
public async Task<IActionResult> Send(Email[] emails)
{
var toSave = from email in emails
select new EmailQueueItem
{
Html = true,
To = email.To,
Subject = email.Subject,
Body = email.Body
};
await Database.BulkInsert(toSave.ToArray());
return Ok();
}
emails property is always null. I would appreciate any help for creating custom model binder that handel this kind of JSON objects.
The issue is that you are actually sending an object containing one property named emails, not an array, to the controller
Option one:
The client object needs to contain just the array
[
{
"To": "info#gmail.com",
"Subject": "Subject",
"Body": "Body",
"ID": "d3d13242-6eff-4c57-b718-ef5ad49fe301"
},
{
"To": "hr#gmail.com",
"Subject": "Subject",
"Body": "Body",
"ID": "101edaf0-fcb4-48fc-9e9e-0d7492b591b0"
}
]
Then read the array from the request body
public async Task<IActionResult> Send([FromBody]Email[] emails)
Option 2:
When you define the array like this in the client
{
"emails":...
}
You need to match that object setup on the controller by defining a model which contains a property called emails
public class RequestModel
{
public Email[] emails { get; set; }
}
Then in the controller method, use the model and read it from the body
public async Task<IActionResult> Send([FromBody]RequestModel emails)

Generate links for collection resources for a specific single resource

I wrote a custom controller to handle a GET http://localhost:54000/api/v1/portfolios/{id}/evaluate request.
#RequestMapping(value = "/portfolios/{id}/evaluate", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> evaluate(#PathVariable Long id) {
Portfolio portfolio = portfolioService.evaluate(id);
if (portfolio == null) {
return ResponseEntity.notFound().build();
}
Resource<Portfolio> resource = new Resource<>(portfolio);
resource.add(entityLinks.linkForSingleResource(Portfolio.class, id).withSelfRel());
return ResponseEntity.ok(resource);
}
The current response is
{
"summary" : {
"count" : 24.166666666666668,
"yield" : 0.14921630094043895,
"minBankroll" : -6.090909090909091,
"sharpeRatio" : 0.7120933654645042,
"worstReturn" : -2.4545454545454533,
"losingSeason" : 3,
"return" : 3.6060606060606077
},
"_links" : {
"self" : {
"href" : "http://localhost:54000/api/v1/portfolios/4"
}
}
}
but I would like to add collection resources (summaries and systems) linked to that portfolio:
{
"summary": {
"count": 24.166666666666668,
"yield": 0.14921630094043895,
"minBankroll": -6.090909090909091,
"sharpeRatio": 0.7120933654645042,
"worstReturn": -2.4545454545454533,
"losingSeason": 3,
"return": 3.6060606060606077
},
"_links": {
"self": {
"href": "http://localhost:54000/api/v1/portfolios/4"
},
"portfolio": {
"href": "http://localhost:54000/api/v1/portfolios/4"
},
"summaries": {
"href": "http://localhost:54000/api/v1/portfolios/4/summaries"
},
"systems": {
"href": "http://localhost:54000/api/v1/portfolios/4/systems"
}
}
}
I did not find a way to generate those links with the RepositoryEntityLinks entityLinks object
You can always do something like this:
entityLinks.linkForSingleResource(Portfolio.class, id).slash("systems").withRel("systems");
And if your systems endpoint is implemented in a custom controller method you can use the ControllerLinkBuilder to generate a link to your controller method. Lets say you implemented the getSystems method with id parameter in MyControllerClass - then you can generate the link like this (linkTo and methodOn are static methods in ControllerLinkBuilder):
linkTo(methodOn(MyControllerClass.class).getSystems(id)).withRel("systems");