I am using Breeze against a standard RPC style WebAPI. I have decorated the controller with the [BreezeController] attribute. I have defined entity metadata on the client for the entities being returned by the WebAPI actions. My Breeze DataService is setup as follows:
var dataService = new breeze.DataService({
serviceName: "http://localhost/api/PartReceiptPurchaseOrders",
hasServerMetadata: false
});
When calling the EntityManager's SaveChanges method after modifying an entity, the EntityInfo object on the server is empty. It appears the serialized entity passed to the SaveChanges method is not being deserialized properly into the expected entity on the server. I'm having a hard time understanding what I'm doing wrong.
When I inspect the JObject saveBundle argument passed to the SaveChanges method on the WebAPI controller, I get the expected entity details:
{
"entities": [{
"PurchaseOrderPartId": 1,
"PartNumber": "ABC",
"SupplierPartNumber": "12345",
"Description": "Some Part",
"Bin": "1",
"Qty": 24,
"QtyReceived": 24,
"QtyBackordered": 0,
"Cost": 60,
"Currency": "USD",
"PurchaseOrderId":1,
"entityAspect": {
"entityTypeName": "PurchaseOrderPart:#MyApp.Models",
"entityState": "Modified",
"originalValuesMap": {
"QtyReceived": 0
},
"autoGeneratedKey":{
"propertyName": "PurchaseOrderPartId",
"autoGeneratedKeyType": "Identity"
}
}
}],
"saveOptions": { "allowConcurrentSaves": false }
}
However, after the call to the base class method Breeze.WebApi.ContextProvider.SaveChanges() the entityInfo.Entity property contains an empty object as follows:
entityInfo {Breeze.WebApi.EntityInfo}
AutoGeneratedKey: null {Breeze.WebApi.AutoGeneratedKey}
Entity {MyApp.Models.PurchaseOrderPart}
Bin: null
Cost: 0
Currency: null
Description: null
PartNumber: null
PurchaseOrder: null {MyApp.Models.PurchaseOrder}
PurchaseOrderId: 0
PurchaseOrderPartId: 0
Qty: 0
QtyBackordered: 0
QtyReceived: 0
SupplierPartNumber: null
If I breakpoint into the CreateEntityInfoFromJson in the Breeze.WebApi.ContextProvider class, I see that the call to jsonSerializer.Deserialize(new JTokenReader(jo), entityType) sets entityInfo.Entity to an empty entity object. There is no error raised during the deserialization so I can't tell why this is happening.
Can anyone point me towards a possible resolution?
Thanks,
Richard
Ok, I figured this out and it was a dumb mistake on my part. My entity type on the server had been declared with internal setters like public decimal QtyReceived { get; **internal** set; }. This meant the JSON deserializer couldn't set the property value. Interestingly enough, the error is just ignored by the deserilizer.
Changing the setters to be public fixed this issue.
Related
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
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)
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});
I have an action which takes a viewmodel.
[HttpPost]
public JsonResult SearchAjax(JQueryDataTablesModel jQueryDataTablesModel, BloodSearchAjaxViewModel searchModel)
{
...
In that viewmodel there is an array
public ReadOnlyCollection<string> mDataProp_ { get; set; }
When I call the action I verify via fiddler that the array data is being passed
However, the array (as well as other arrays in the viewmodel) are null.
Also, if I put in the viewmodel a field calledmDataProp_0 it gets populated.
Updated based on comment. Here is the code from the view where the data is posted. I'm using a jQueryDataTable. I didn't think this code mattered since I verified that the data is in the http request.
/* Initialize table */
var oTable = $('#search-results-table').dataTableWithFilter({
"sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>",
"sPaginationType": "bootstrap",
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": 'SearchAjax',
"sServerMethod": "POST",
"aoColumns": [
{ "mDataProp": "BloodIdentificationNumber" },
{ "mDataProp": "Status" },
{ "mDataProp": "ExpirationDate" },
{ "mDataProp": "CompanyName" },
{ "mDataProp": "Location" },
{ "mDataProp": "City" }
],
// Initialize our custom filtering buttons and the container that the inputs live in
filterOptions: { searchButton: "search-button", clearSearchButton: "clear-search-button", searchContainer: "search-block" }
});
Any ideas? Thanks!
The default model binder is pretty picky. I think what you're seeing here is simply that the data isn't conforming to the conventions the binder expects. Your options are either 1) massage the data to what the default binder accepts or 2) write a custom model binder. Below are some links for both approaches.
ASP.NET MVC - Custom model binder able to process arrays
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
I found the solution. Studying the project I saw that you must include a line in the Global.asax file.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// This line
ModelBinders.Binders.Add(typeof(JQueryDataTablesModel), new JQueryDataTablesModelBinder());
}
Where JQueryDataTablesModel use as parameter the line below should be included in the Global.asax and the problem is solved.
My WebApi action returns a dynamic object built from JObject.parse(jsonString);
I have GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
But that object is wrapped inside a default result message in the response.
According to this article returning anonymous objects is fine and should give the expected result
I am using an async controller because I have to await some ReadAsStringAsync() here the protoype of my action:
public async Task<dynamic> Pics(string flavor, int pagesize)
Expected result :
{"flavor":"","maxFeedSize":0,"mediaContent":[]}
Result I have when returning the dynamic object:
{
"Result": {
"flavor": "",
"maxFeedSize": 0,
"mediaContent": []
},
"Id": 1,
"Exception": null,
"Status": 5,
"IsCanceled": false,
"IsCompleted": true,
"CreationOptions": 0,
"AsyncState": null,
"IsFaulted": false
}
As I thought and as mentioned in comments. I was returning a Task<Task<dynamic>> because of a naive method overload.
public async Task<dynamic> Pics(string flavor, string pagesize)
{
return Pics(flavor, pagesize, null);
}
Edit:
I tried this because unlike MVC routes ommit a string parameter throws an error even if string is nullable
public async Task<dynamic> Pics(string flavor, string pagesize, string startid =null)
works fine :)