Is ApiController model validation different than TryValidateObject? - c#

I am using .NET Core 3.1 to develop REST API. This is the controller that I am using (stripped down to basics just to demonstrate the issue, it returns the same data as it received):
OrdersController.cs
[Route("Payments/[controller]")]
[ApiController]
public class OrdersController : Controller
{
[HttpPost]
[Route("AddOrder")]
public IActionResult AddOrder(Order order)
{
return Json(order);
}
}
Order.cs
public class Product
{
[Required]
public string Manufacturer { get; set; }
[Required]
public string Code { get; set; }
}
public class Order
{
[Required]
public string Recipient { get; set; }
[Required]
public Product Product { get; set; }
}
When I call Payments/Orders/AddOrder with Postman with the following body (notice empty nested Code field):
{
"Recipient": "John Doe",
"Product": {
"Manufacturer": "Company Inc.",
"Code": ""
}
}
... I get the following error which is expected since Code is annotaded with [Required]:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7558e077-4f9147019767a0cf.",
"errors": {
"Product.Code": [
"The Code field is required."
]
}
}
However, if I try to validate the Order object with the same data in one of the services, it doesn't detect that field Code is empty.
// manually initialized order with same data as in Postman request
Order order = new Order()
{
Recipient = "John Doe",
Product = new Product()
{
Manufacturer = "Company Inc.",
Code = string.Empty
}
};
ValidationContext context = new ValidationContext(order, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(order, context, validationResults, true);
Here, isValid is true which means that object is valid. How can it be valid if Code is empty? Why does controller automatically detect that nested property is invalid but Validator.TryValidateObject doesn't? Does controller validation work recursively and Validator.TryValidateObject does not? Can I use the same recursive (nested fields) validation that controller uses somewhere else in the code?
EDIT: Why do we even want to validate the object on the service layer?
We developed a shared project to be used in other solutions. It calls REST API with correctly formatted payload and headers. We want to validate object inside shared project's code (service) before it is even sent to REST API server. We want to avoid situations where request is sent out by the shared client but is then rejected at REST API's server.

Related

.net core request list parameter always gets null

I'm trying to send a list of object in http post request to .net core app but my list in the controller always gets null.
When I parse the json inside the code using JsonConvert it runs perfectly .
I cant find why it doesn't bind properly to the model .
My action :
public async Task<ActionResult> FileTypes([FromBody] FileTypesRequest data)
FileTypesRequest :
public class FileTypesRequest
{
public List<FileTypeChoice> FileTypes;
}
FileTypeChoice:
public class FileTypeChoice
{
public string Content { get; set; }
public string Type { get; set; }
}
Postman request body:
{
"fileTypes": [
{
"content": "test",
"type": "excel"
},
{
"content": "test2",
"type": "excel"
}
]
}
Any help ?
Thanks !
Ok I found the solution
public class FileTypesRequest
{
public List<FileTypeChoice> FileTypes;
}
to
public class FileTypesRequest
{
public List<FileTypeChoice> FileTypes { get; set; }
}
Made the FileTypes from variable to property.
NOT AN ANSWER JUST TRYING TO HELP DIAGNOSE
Enable HttpLogging
Using AddHttpLogging and UseHttpLogging methods in the Startup.cs file.
You should be able to see the request body in the console log.
Most time these values are null when the case doesn't match. When you add controllers to the service configure the Json to be match case. (camel or member casing)
services.AddControllers()
.AddJsonOptions(options =>
{
// ...
});

C# REST API - how to extend the model state error with an error code

I'm creating an ASP.net Core Web API (.net 5) to serve data to a Single Page Application (SPA). I'm using DataAnnotations (System.ComponentModel.DataAnnotations) to carry out model state validation in a very standard way in my Controller. I want to ensure my error responses come back in a very consistent manner (and are translatable in the front end!).
MVC Controller
Example controller is as follows:
[ApiController, Route("[controller]")]
public class AgencyController : Controller
{
private readonly IOptions<ApiBehaviorOptions> _apiBehaviorOptions;
public AgencyController(IOptions<ApiBehaviorOptions> apiBehaviorOptions)
{
_apiBehaviorOptions = apiBehaviorOptions;
}
[HttpPost]
[ProducesResponseType(typeof(void), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)]
public IActionResult Create([FromBody] ExampleCreateModel createModel)
{
// If the email already exists, add the custom error.
var emailExists = EmailAlreadyExists(createModel.Email);
if (emailExists)
{
ModelState.AddModelError("Email", "Email already exists");
return _apiBehaviorOptions.Value.InvalidModelStateResponseFactory(ControllerContext);
}
// Return some relevant status...
return Ok();
}
// Real verification implementation would go here....
private bool EmailAlreadyExists(string email) => true;
}
This sample code above demonstrates a controller that takes takes the POCO model (shown below) and if it passes the attribute validation, carries out additional inline validation to ensure the email address doesn't already exist (pseudo code for the email exists check).
Sample Model
public class ExampleCreateModel
{
[Required]
public string Name { get; set; } = "";
[Required]
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; } = "";
}
Error Response Format
Validation failure on the model shown above yields the standard error object from the MVC app in this format (sample):
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-6b30709ed71b7347afd41e0ed58e1ccb-e3ede2ff7591a24d-00",
"errors": {
"Email": [
"Email already exists"
]
}
}
this format is consistent for both regular attribute validation and the custom inline validation in the code above
My Question
The advantage of using the built in errors is consistency (and follows a standard pattern). BUT one down side for me is that I'd like to return error codes as well as text as part of the error object, so the UI can translate them but it doesn't seem to easily support that i.e. an "Email already exists" error could be a 3001 error and the UI could show a 3001 in various languages.
Is there any standard way to use the existing DataAnnotation attributes to include additional information? Such that the POCO model would become something like this:
public class ExampleCreateModel
{
[Required(ErrorCode = 3000)]
public string Name { get; set; } = "";
[Required]
[EmailAddress(ErrorMessage = "Invalid email format", ErrorCode = 3001)]
public string Email { get; set; } = "";
}
Resulting in an error object similar to this:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-6b30709ed71b7347afd41e0ed58e1ccb-e3ede2ff7591a24d-00",
"errors": {
"Email": [
{ "message": "Email already exists", "errorCode": 3002 }
]
}
}
Just to restate for clarity - my aim here is to give the UI an opportunity to easily translate error code, while achieving error consistency using the out-of-the-box error response.
Thanks in advance for any pointers!
Is there any standard way to use the existing DataAnnotation attributes to include additional information?
No.
I think that the simplest (but pretty hacky) approach could be to include the error code in the message string itself and then extract it client-side:
public class ExampleCreateModel
{
// ...
[Required]
[EmailAddress(ErrorMessage = "ErrorCode:3001 - Invalid email format")]
public string Email { get; set; } = "";
}
You could even go one step further and put a serialized JSON object in there to make it more structured (but pay attention to escaping). Of course you should centralize the logic of creating such 'custom' error messages into some kind of static utilty class in order to make it consistent throughout your application.

Newtonsoft.Json.ReferenceLoopHandling.Ignore deserialization format .NET Core 3.1

I'm trying to make an API using .NET Core 3.1 with C# programming language which utilizes Firebase real-time database from Google through FireSharp Nu-Get package. When I ran it, I got this exception: 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. I read here that I can just install Microsoft.AspNetCore.Mvc.NewtonsoftJson package and ignore the reference loop handling, which works fine. But the data format looks unreadable, very hard to deserialize, and way too long:
{
"stateMachine": {
"<>1__state": 0,
"<>t__builder": {},
"bpjs": "12345678",
"reservationTime": "24-11-2020",
"<>4__this": {}
},
"context": {},
"moveNextAction": {
"method": {
"name": "MoveNext",
"declaringType": ...
...
((lots of irrelevant things))
...
}
},
"result": {
"queueNumber": "1",
"bpjs": "12345678",
"name": "Addi",
"poli": "Umum",
"reservationTime": "24-11-2020",
"status": "Queueing"
},
"id": 2,
"exception": null,
"status": 5,
"isCanceled": false,
"isCompleted": true,
"isCompletedSuccessfully": true,
"creationOptions": 0,
"asyncState": null,
"isFaulted": false}
Before this, I already tried to do some mapping and not directly use my data model by following this video's tutorial on doing DTOs, but it didn't work most likely because I don't use SQL Database.
Now I'm trying to use this API on a Flutter app. Is there anyway for me to maybe do some minor changes on my API code (anything that doesn't involve completely changing the database to SQL) to reformat my response? Or maybe a way to just partly deserialize my response, since I only need the "result" part and not anything else?
Edit:
This is my main code to get the data:
public async Task<QueueData> GetQueueData(string bpjs, string reservationTime)
{
//set up connection
IFirebaseConfig config = new FirebaseConfig
{
AuthSecret = myAuthSecret,
BasePath = myBasePath
};
IFirebaseClient client = new FireSharp.FirebaseClient(config);
//checkqueuenumber
string queueCounter = await QueueDbConnection.CheckQueueNumber(reservationTime, client);
//getresult
QueueData result = await QueueDbConnection.GetResult(bpjs, reservationTime, queueCounter, client);
return result;
}
this is my controller to call the function above:
[HttpGet("{bpjs}/{reservationTime}")]
public ActionResult<QueueData> GetQueueData(string bpjs, string reservationTime)
{
var queueData = _repository.GetQueueData(bpjs, reservationTime);
return Ok(queueData);
}
This is my model for the QueueData, which is the type I'm trying to return as response:
public class QueueData
{
[Required]
public string QueueNumber { get; set; }
[Required]
public string BPJS { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Poli { get; set; }
[Required]
public string ReservationTime { get; set; }
[Required]
public string Status { get; set; }
}
this is another model class called control to monitor the number of people queueing each day:
public class QueueCounter
{
[Required]
public string q { get; set; }
}
So, I solved this. I went back to here and apparently someone said the error I got in the first place was because not all async functions had been waited or handled properly. So I went through my codes once again and finally found the problem in my controller. After I made my controller async then changed the return type into Task<ActionResult>, everything went perfectly fine!

Web API 2 - Implementing a PATCH

I currently have a Web API that implements a RESTFul API. The model for my API looks like this:
public class Member
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Created { get; set; }
public DateTime BirthDate { get; set; }
public bool IsDeleted { get; set; }
}
I've implemented a PUT method for updating a row similar to this (for brevity, I've omitted some non-relevant stuff):
[Route("{id}")]
[HttpPut]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id,
[FromBody]Models.Member model)
{
// Do some error checking
// ...
// ...
var myDatabaseEntity = new BusinessLayer.Member(id);
myDatabaseEntity.FirstName = model.FirstName;
myDatabaseEntity.LastName = model.LastName;
myDatabaseEntity.Created = model.Created;
myDatabaseEntity.BirthDate = model.BirthDate;
myDatabaseEntity.IsDeleted = model.IsDeleted;
await myDatabaseEntity.SaveAsync();
}
Using PostMan, I can send the following JSON and everything works fine:
{
firstName: "Sara",
lastName: "Smith",
created: "2018/05/10",
birthDate: "1977/09/12",
isDeleted: false
}
If I send this as my body to http://localhost:8311/api/v1/Member/12 as a PUT request, the record in my data with ID of 12 gets updated to what you see in the JSON.
What I would like to do though is implement a PATCH verb where I can do partial updates. If Sara gets married, I would like to be able to send this JSON:
{
lastName: "Jones"
}
I would like to be able to send just that JSON and update JUST the LastName field and leave all the other fields alone.
I tried this:
[Route("{id}")]
[HttpPatch]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id,
[FromBody]Models.Member model)
{
}
My problem is that this returns all the fields in the model object (all of them are nulls except the LastName field), which makes sense since I am saying I want a Models.Member object. What I would like to know is if there is a way to detect which properties have actually been sent in the JSON request so I can update just those fields?
I hope this helps using Microsoft JsonPatchDocument:
.Net Core 2.1 Patch Action into a Controller:
[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody]JsonPatchDocument<Node> value)
{
try
{
//nodes collection is an in memory list of nodes for this example
var result = nodes.FirstOrDefault(n => n.Id == id);
if (result == null)
{
return BadRequest();
}
value.ApplyTo(result, ModelState);//result gets the values from the patch request
return NoContent();
}
catch (Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex);
}
}
Node Model class:
[DataContract(Name ="Node")]
public class Node
{
[DataMember(Name = "id")]
public int Id { get; set; }
[DataMember(Name = "node_id")]
public int Node_id { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "full_name")]
public string Full_name { get; set; }
}
A valid Patch JSon to update just the "full_name" and the "node_id" properties will be an array of operations like:
[
{ "op": "replace", "path": "full_name", "value": "NewNameWithPatch"},
{ "op": "replace", "path": "node_id", "value": 10}
]
As you can see "op" is the operation you would like to perform, the most common one is "replace" which will just set the existing value of that property for the new one, but there are others:
[
{ "op": "test", "path": "property_name", "value": "value" },
{ "op": "remove", "path": "property_name" },
{ "op": "add", "path": "property_name", "value": [ "value1", "value2" ] },
{ "op": "replace", "path": "property_name", "value": 12 },
{ "op": "move", "from": "property_name", "path": "other_property_name" },
{ "op": "copy", "from": "property_name", "path": "other_property_name" }
]
Here is an extensions method I built based on the Patch ("replace") specification in C# using reflection that you can use to serialize any object to perform a Patch ("replace") operation, you can also pass the desired Encoding and it will return the HttpContent (StringContent) ready to be sent to httpClient.PatchAsync(endPoint, httpContent):
public static StringContent ToPatchJsonContent(this object node, Encoding enc = null)
{
List<PatchObject> patchObjectsCollection = new List<PatchObject>();
foreach (var prop in node.GetType().GetProperties())
{
var patch = new PatchObject{ Op = "replace", Path = prop.Name , Value = prop.GetValue(node) };
patchObjectsCollection.Add(patch);
}
MemoryStream payloadStream = new MemoryStream();
DataContractJsonSerializer serializer = new DataContractJsonSerializer(patchObjectsCollection.GetType());
serializer.WriteObject(payloadStream, patchObjectsCollection);
Encoding encoding = enc ?? Encoding.UTF8;
var content = new StringContent(Encoding.UTF8.GetString(payloadStream.ToArray()), encoding, "application/json");
return content;
}
}
Noticed that tt also uses this class I created to serialize the PatchObject using DataContractJsonSerializer:
[DataContract(Name = "PatchObject")]
class PatchObject
{
[DataMember(Name = "op")]
public string Op { get; set; }
[DataMember(Name = "path")]
public string Path { get; set; }
[DataMember(Name = "value")]
public object Value { get; set; }
}
A C# example of how to use the extension method and invoking the Patch request using HttpClient:
var nodeToPatch = new { Name = "TestPatch", Private = true };//You can use anonymous type
HttpContent content = nodeToPatch.ToPatchJsonContent();//Invoke the extension method to serialize the object
HttpClient httpClient = new HttpClient();
string endPoint = "https://localhost:44320/api/nodes/1";
var response = httpClient.PatchAsync(endPoint, content).Result;
Thanks
PATCH operations aren't usually defined using the same model as the POST or PUT operations exactly for that reason: How do you differentiate between a null, and a don't change. From the IETF:
With PATCH, however, the enclosed entity contains a set of
instructions describing how a resource currently residing on the
origin server should be modified to produce a new version.
You can look here for their PATCH suggestion, but sumarilly is:
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
#Tipx's answer re using PATCH is spot on, but as you've probably already found, actually achieving that in a statically typed language like C# is a non-trivial exercise.
In the case where you're using a PATCH to represent a set of partial updates for a single domain entity (e.g. to update the first name and last name only for a contact with many more properties) you need to do something along the lines of looping each instruction in the 'PATCH' request and then applying that instruction to an instance of your class.
Applying an individual instruction will then comprise of
Finding the property of the instance that matches the name in the
instruction, or handling property names you weren't expecting
For an update: Trying to parse the value submitted in the patch into the instance property and handling the error if e.g. the instance property is a bool but the patch instruction contains a date
Deciding what to do with Add instructions as you can't add new properties to a statically typed C# class. One approach is to say that Add means "set the value of the instance's property only if property's existing value is null"
For Web API 2 on the full .NET Framework the JSONPatch github project looks to make a stab at providing this code, although it doesn't look like there's been a lot of development on that repo recently and the readme does state:
This is still very much an early project, don't use it in production
yet unless you understand the source and don't mind fixing a few bugs
;)
Things are simpler on .NET Core as that has a set of functionality to support this in the Microsoft.AspNetCore.JsonPatch namespace.
The rather useful jsonpatch.com site also lists out a few more options for Patch in .NET:
Asp.Net Core JsonPatch (Microsoft official implementation)
Ramone (a framework for consuming REST services, includes a JSON Patch implementation)
JsonPatch (Adds JSON Patch support to ASP.NET Web API)
Starcounter (In-memory Application Engine, uses JSON Patch with OT for client-server sync)
Nancy.JsonPatch (Adds JSON Patch support to NancyFX)
Manatee.Json (JSON-everything, including JSON Patch)
I need to add this functionality to an existing Web API 2 project of ours, so I'll update this answer if I find anything else that's useful while doing that.
I wanted to achieve exactly the same thing, but used a different method to others described here. I've created a working repo using this if you want to check it out:
https://github.com/emab/patch-example
If you have the following two models:
Database model
public class WeatherDBModel
{
[Key]
public int Id { get; set; }
public string City { get; set; }
public string Country { get; set; }
public double Temperature { get; set; }
public double WindSpeed { get; set; }
public double Rain { get; set; }
public Weather(int id, string city, string country, double temperature, double windSpeed, double rain)
{
Id = id;
City = city;
Country = country;
Temperature = temperature;
WindSpeed = windSpeed;
Rain = rain;
}
}
Update model
Containing exact names of database model properties. Includes properties which can be updated
public class WeatherUpdateModel
{
public string? City { get; set; }
public string? Country { get; set; }
public double Temperature { get; set; }
public double WindSpeed { get; set; }
public double Rain { get; set; }
}
This update model is sent to the service layer along with the id of the object you'd like to update.
You can then implement the following method in your repository layer which maps any non-null values from the updateModel into an existing entity if it has been found:
public Weather Update(int id, WeatherUpdate updateObject)
{
// find existing entity
var existingEntity = _context.Weather.Find(id);
// handle not found
if (existingEntity == null)
{
throw new EntityNotFoundException(id);
}
// iterate through all of the properties of the update object
// in this example it includes all properties apart from `id`
foreach (PropertyInfo prop in updateObject.GetType().GetProperties())
{
// check if the property has been set in the updateObject
// if it is null we ignore it. If you want to allow null values to be set, you could add a flag to the update object to allow specific nulls
if (prop.GetValue(updateObject) != null)
{
// if it has been set update the existing entity value
existingEntity.GetType().GetProperty(prop.Name)?.SetValue(existingEntity, prop.GetValue(updateObject));
}
}
_context.SaveChanges();
return existingEntity;
}
Using this method you can change your models without worrying about the update logic, as long as you ensure that the UpdateModel is kept up-to-date with the database model.
If a property of your object was omitted in your JSON, ASP.NET won't "set" that property on the object, the property will have its default value. In order to know which properties were sent with the JSON object you need to have a way to detect which properties of the object were set.
In order to detect which properties have "actually been sent" with the JSON object, you can modify your Member class to contain a collection of property names that were "set". Then, for all properties that you want to be able to know if they were sent in the JSON object make that when the property is set the name of the property should be added to the collection of set properties.
public class Member
{
private string _firstName;
private string _lastName;
...
private bool _isDeleted;
public string FirstName
{
get => _firstName;
set
{
_firstName = value;
_setProperties.Add(nameof(FirstName));
}
}
public string LastName
{
get => _lastName;
set
{
_lastName = value;
_setProperties.Add(nameof(LastName));
}
}
...
public bool IsDeleted
{
get => _isDeleted;
set
{
_isDeleted= value;
_setProperties.Add(nameof(IsDeleted));
}
}
private readonly HashSet<string> _setProperties = new HashSet<string>();
public HashSet<string> GetTheSetProperties()
{
return new HashSet<string>(_setProperties);
}
}
In the UpdateRow method you can now check whether a property was sent in the JSON by checking if it is in the _setProperties collection. So if you want to see if the LastName was sent in the JSON just do
bool lastNameWasInJson = model.Contains(nameof(model.LastName));
Following up to Avid Learners approach. I found this easy to add to an existing PUT method.
Alternatively to avoid loading twice you could apply update operations and then before saving apply the patch, but I'd rather load twice and have simple code.
public ResultModel Patch(UpdateModel model)
{
var record = LoadAsUpdateModel(model.Id);
if (record == null) return null;
foreach(var propertyName in model.SetProperties())
{
var property = model.GetType().GetProperty(propertyName);
property.SetValue(record, property.GetValue(model));
}
return Update(record);
}

Parsing Json .Net Web Api

I'm new with Web API 2 / Entity Framework 6 project, I'm making REST services, but for one specific service I'm going to receive (via Post) a JSON before making any CRUD operations over any entity of the model, (have to make some business validations over the data, add or complement some things and decide on wich entity to save), the JSON is:
{
"head": {
"action": "create",
"object": "oneobject",
"user": "theuser"
},
"object": {
"name1": "a name 1",
"name2": "a name 2",
"description": "a description here"
},
"rule": [{
"name": "any name",
"value": "any value"
}, {
"name": "another name",
"value": "another value"
}]
}
So the json not maps directly to an entity, in fact I have no model or object for this. What would be the better way to work with it? I mean how to receive and parse the json. I'm new with web api and rest services, and I would appreciate you can help me and explain me with good details. Thanks guys.
Edit:
Any idea of the POCO or class that match this kind of json. ("rule" list It's variable, can be one or more).
After create this poco or class I would have to make a controller based on this?
As others have said, what you need is a POCO object to represent your request. Based on the information you have provided the following should achieve close enough to what you are after:
public enum CrudAction
{
Create,
Read,
Update,
Delete
}
public sealed class CrudRequestHeader
{
public CrudAction Action { get; set; }
public string Object { get; set; }
public string User { get; set; }
}
public sealed class RuleDefinition
{
public string Name { get; set; }
public string Value { get; set; }
}
public sealed class CrudRequest
{
public CrudRequestHeader Head { get; set;}
public Dictionary<string, string> Object { get; set; }
public List<RuleDefinition> Rule { get; set; }
}
In your Web API controller method you can then take a parameter of type CrudRequest and your JSON will be deserialized to this object, e.g:
public IHttpActionResult Post(CrudRequest crudRequest)
{
// TODO Implementation
}
You may notice I have used Dictionary<string, string> for CrudRequest.Object as it is variable how many key/values we will be supplied with, I have made the assumption that all values are strings, you can use an object value if you prefer but you will then need to handle the type of value. In the same principle I have used List<RuleDefinition> for CrudRequest.Rule to cater for the variable number of rules which may be supplied.
A LINQPad sample containing the above definitions and use with your input can be found here: http://share.linqpad.net/7rvmhh.linq
Although the JSON may not represent a logical entity in your model, you clearly have a mental model of the "shape" of the JSON data - I say this because you define it in your code snippet. You should create a POCO (plain old C# object) to represent this model, and deserialize the incoming JSON request to an object of that type. Once you've deserialized it to your object, it will be trivial to work with this data using object properties and such.
The best thing to do would be to create a class that models the object you expect back.
This way in your Web API method you can use the [FromBody] attribute to automatically parse the body of the request.
Example -
Your data contract would look like this:
public class MyContract
{
public string MyData { get; set;}
}
In your ApiController
[HttpPost]
[Route("api/myobject")]
public async Task ReceiveMyObject([FromBody]MyContract object) {
var data = object.MyData;
// Do whatever you need to do here.
}
This may seem tedious but this will let you keep your code organized.
Edit
So to create a contract out of this:
{
"head": {
"action": "create",
"object": "oneobject",
"user": "theuser"
},
"object": {
"name1": "a name 1",
"name2": "a name 2",
"description": "a description here"
},
"rule": [{
"name": "any name",
"value": "any value"
}, {
"name": "another name",
"value": "another value"
}]
}
You would do something like this:
public class MyContract
{
[JsonProperty("head")]
public MetaObject Head
{
get; set;
}
// Not sure if this will work, but it probably will
[JsonProperty("object")]
public JObject ExtendedInformation
{
get;
set;
}
[JsonProperty("rule")]
public Rule[] Rules
{
get;
set;
}
}
// "MetaObject" definition omitted but you can understand my point with the below
public class Rule
{
[JsonProperty("name")]
public string Name
{
get;
set;
}
[JsonProperty("value")]
public string Value
{
get;
set;
}
}
Usually a REST service issue a contract, which means some kind of metadata to describe the content of its messages, otherwise you cannot call this as a RESTful Web API. Take a look at this post from Roy Fielding, who created the term of REST API, if you want to know better what is REST and what is not.
So if your service is a true REST service you should be able to have a description somewhere that give you the possibility to parse the media.
However, if you still cannot find any way to understand how the JSON should be interpreted you may be able to use it inside a C# class anyway: take a look at the JObject class from Newtonsoft.Json that enables to use a dynamic object at runtime.
Basically, it is used like this:
using Newtonsoft.Json.Linq; // This needs the Newtonsoft.Json package
dynamic entity = JObject.Parse(jsonString);
string value = entity.key1;
string value2 = entity["key2"];
I did this simple demo with your data.
You can also check the full documentation of the class on the Newtonsoft website.

Categories